ORB-SLAM2的前端VO部分分为:Tracking和LocalMapping,
Tracking线程负责根据输入的Frame恢复出相机位姿T并跟踪局部地图,最后生成关键帧传给LocalMapping线程。
LocalMapping线程负责对新加入的KeyFrames和MapPoints筛选融合,剔除冗余的KeyFrames和MapPoints,维护稳定的KeyFrame集合,传给后续的LoopClosing线程。
主要的功能点在:
告诉Tracking线程,LocalMapping正处于繁忙状态,不要再添加新的关键帧了。
(1)从缓冲队列中取出一帧关键帧
// Tracking线程向LocalMapping中插入关键帧存在该队列中
{
unique_lock lock(mMutexNewKFs);
// 从列表最前面中获得一个等待被插入的关键帧,原列表少一个关键帧
mpCurrentKeyFrame = mlNewKeyFrames.front();
mlNewKeyFrames.pop_front();
}
(2)计算当前关键帧的Bow向量,有助于加速三角化新的MapPoints
mpCurrentKeyFrame->ComputeBoW();
(3)更新当前关键帧所看到的地图点的属性(平均观测方向,最优描述子)
const vector vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();
for(size_t i=0; iisBad())
{
// 非当前帧生成的MapPoints
// 为当前帧在tracking过程跟踪到的MapPoints更新属性
if(!pMP->IsInKeyFrame(mpCurrentKeyFrame))
{
// 添加观测
pMP->AddObservation(mpCurrentKeyFrame, i);
// 获得该点的平均观测方向和观测距离范围
pMP->UpdateNormalAndDepth();
// 加入关键帧后,更新3d点的最佳描述子
pMP->ComputeDistinctiveDescriptors();
}
else // this can only happen for new stereo points inserted by the Tracking
{
mlpRecentAddedMapPoints.push_back(pMP);
}
}
}
}
(4) 更新Covisibility图的边,并将该关键帧插入到地图中
// Update links in the Covisibility Graph
// 步骤4:更新关键帧间的连接关系,Covisibility图和Essential图(tree)
mpCurrentKeyFrame->UpdateConnections();
// Insert Keyframe in Map
// 步骤5:将该关键帧插入到地图中
mpMap->AddKeyFrame(mpCurrentKeyFrame);
void LocalMapping::MapPointCulling()
{
// Check Recent Added MapPoints
list::iterator lit = mlpRecentAddedMapPoints.begin();
const unsigned long int nCurrentKFid = mpCurrentKeyFrame->mnId;
int nThObs;
if(mbMonocular)
nThObs = 2;
else
nThObs = 3;
const int cnThObs = nThObs;
// 遍历等待检查的MapPoints
while(lit!=mlpRecentAddedMapPoints.end())
{
MapPoint* pMP = *lit;
if(pMP->isBad())
{
// 步骤1:已经是坏点的MapPoints直接从检查链表中删除
lit = mlpRecentAddedMapPoints.erase(lit);
}
else if(pMP->GetFoundRatio()<0.25f)
{
// 步骤2:将不满足VI-B条件的MapPoint剔除
// VI-B 条件1:
// 跟踪到该MapPoint的Frame数相比预计可观测到该MapPoint的Frame数的比例需大于25%
// IncreaseFound / IncreaseVisible < 25%,注意不一定是关键帧。
pMP->SetBadFlag();
lit = mlpRecentAddedMapPoints.erase(lit);
}
else if(((int)nCurrentKFid-(int)pMP->mnFirstKFid)>=2 && pMP->Observations()<=cnThObs)
{
// 步骤3:将不满足VI-B条件的MapPoint剔除
// VI-B 条件2:从该点建立开始,到现在已经过了不小于2个关键帧
// 但是观测到该点的关键帧数却不超过cnThObs帧,那么该点检验不合格
pMP->SetBadFlag();
lit = mlpRecentAddedMapPoints.erase(lit);
}
else if(((int)nCurrentKFid-(int)pMP->mnFirstKFid)>=3)
// 步骤4:从建立该点开始,已经过了3个关键帧而没有被剔除,则认为是质量高的点
// 因此没有SetBadFlag(),仅从队列中删除,放弃继续对该MapPoint的检测
lit = mlpRecentAddedMapPoints.erase(lit);
else
lit++;
}
}
整体利用迭代器遍历所有地图点:list
在循环函数内新建pMP表示地图点:MapPoint* pMP = *lit;
剔除时使用erase:lit = mlpRecentAddedMapPoints.erase(lit);
(1)在当前关键帧的共视关键帧中找到共视程度最高的nn帧相邻帧vpNeighKFs
const vector vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);
(2)遍历相邻关键帧vpNeighKFs,得到基线向量vBaseline = Ow2-Ow1
(3)判断相机运动的基线是不是足够长,邻接关键帧的场景深度中值medianDepthKF2,baseline与景深的比例,如果特别远(比例特别小),那么不考虑当前邻接的关键帧,不生成3D点
(4)根据两个关键帧的位姿计算它们之间的基本矩阵F
cv::Mat F12 = ComputeF12(mpCurrentKeyFrame,pKF2);
(5) 通过极线约束限制匹配时的搜索范围,对满足对极约束的特征点进行特征点匹配
vector > vMatchedIndices;
matcher.SearchForTriangulation(mpCurrentKeyFrame,pKF2,F12,vMatchedIndices,false);
SearchForTriangulation通过基础矩阵找到匹配的特征点。
(6)对每对匹配通过三角化生成3D点,和Triangulate函数差不多
(7)接着分别检查新得到的点在两个平面上的重投影误差,如果大于一定的值,直接抛弃该点。
(8)检查尺度连续性
(9)如果满足对极约束则建立当前帧的地图点及其属性(a.观测到该MapPoint的关键帧 b.该MapPoint的描述子 c.该MapPoint的平均观测方向和深度范围),
(10)将地图点加入关键帧,加入全局map
(1)获得当前关键帧在covisibility图中权重排名前nn的邻接关键帧,找到当前帧一级相邻与二级相邻关键帧
(2)将当前帧的MapPoints分别与一级二级相邻帧(的MapPoints)进行融合
matcher.Fuse(pKFi,vpMapPointMatches);
// 投影当前帧的MapPoints到相邻关键帧pKFi中,并判断是否有重复的MapPoints
// 1.如果MapPoint能匹配关键帧的特征点,并且该点有对应的MapPoint,那么将两个MapPoint合并(选择观测数多的)
// 2.如果MapPoint能匹配关键帧的特征点,并且该点没有对应的MapPoint,那么为该点添加MapPoint
(3) 将一级二级相邻帧的MapPoints分别与当前帧(的MapPoints)进行融合
(4)更新当前帧MapPoints的描述子,深度,观测主方向等属性
// 在所有找到pMP的关键帧中,获得最佳的描述子
pMP->ComputeDistinctiveDescriptors();
// 更新平均观测方向和观测距离
pMP->UpdateNormalAndDepth();
(5)更新当前帧的MapPoints后更新与其它帧的连接关系
mpCurrentKeyFrame->UpdateConnections();
局部集束调整(local BA)会将当前处理的关键帧Ki进行优化,优化时如下图所示:现在优化Pos3位置的关键帧。同时参与优化的还有:
所有在Covibility Graph中与该关键帧相连的关键帧Kc,即下图中的Pos2;
所以被这些关键帧观察到的地图点,即X1和X2。
另外还有能观察到地图点的但并未与当前处理的关键帧相连的关键帧,即下图中的Pos1。
但要注意的是,诸如Pos1的关键帧,参与优化中的约束,但不作为变量去改变它们的值。优化时得到的外点会在优化的中期或后期被剔除。
在Covisibility Graph中的关键帧,其90%以上的MapPoints能被其他关键帧(至少3个)观测到,则认为该关键帧为冗余关键帧。
(1)根据Covisibility Graph提取当前帧的共视关键帧
vector vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();
(2)对所有的局部关键帧进行遍历,提取每个共视关键帧的MapPoints
const vector vpMapPoints = pKF->GetMapPointMatches();
(3)遍历该局部关键帧的MapPoints,判断是否90%以上的MapPoints能被其它关键帧(至少3个)观测到
for(size_t i=0, iend=vpMapPoints.size(); iisBad())
{
if(!mbMonocular)
{
// 对于双目,仅考虑近处的MapPoints,不超过mbf * 35 / fx
if(pKF->mvDepth[i]>pKF->mThDepth || pKF->mvDepth[i]<0)
continue;
}
nMPs++;
// MapPoints至少被三个关键帧观测到
if(pMP->Observations()>thObs)
{
const int &scaleLevel = pKF->mvKeysUn[i].octave;//当前关键点所处层级
const map observations = pMP->GetObservations();//当前地图点被观察数
// 判断该MapPoint是否同时被三个关键帧观测到
int nObs=0;
for(map::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
{
KeyFrame* pKFi = mit->first;
if(pKFi==pKF)
continue;
const int &scaleLeveli = pKFi->mvKeysUn[mit->second].octave;
// Scale Condition
// 尺度约束,要求MapPoint在该局部关键帧的特征尺度大于(或近似于)其它关键帧的特征尺度
if(scaleLeveli<=scaleLevel+1)
{
nObs++;
// 已经找到三个同尺度的关键帧可以观测到该MapPoint,不用继续找了
if(nObs>=thObs)
break;
}
}
// 该MapPoint至少被三个关键帧观测到
if(nObs>=thObs)
{
nRedundantObservations++;
}
}
(4)该局部关键帧90%以上的MapPoints能被其它关键帧(至少3个)观测到,则认为是冗余关键帧
if(nRedundantObservations>0.9*nMPs)
pKF->SetBadFlag();