for(int i=0; i<nInitialCandidates; i++)
{
// 步骤1:从筛选的闭环候选帧中取出一帧关键帧pKF
KeyFrame* pKF = mvpEnoughConsistentCandidates[i];
// 防止在LocalMapping中KeyFrameCulling函数将此关键帧作为冗余帧剔除
pKF->SetNotErase();
if(pKF->isBad())
{
vbDiscarded[i] = true;// 直接将该候选帧舍弃
continue;
}
// 步骤2:将当前帧mpCurrentKF与闭环候选关键帧pKF匹配
// 通过bow加速得到mpCurrentKF与pKF之间的匹配特征点,vvpMapPointMatches是匹配特征点对应的MapPoints
int nmatches = matcher.SearchByBoW(mpCurrentKF,pKF,vvpMapPointMatches[i]);
// 匹配的特征点数太少,该候选帧剔除
if(nmatches<20)
{
vbDiscarded[i] = true;
continue;
}
else
{
// 构造Sim3求解器
// 如果mbFixScale为true,则是6DoFf优化(双目 RGBD),如果是false,则是7DoF优化(单目)
Sim3Solver* pSolver = new Sim3Solver(mpCurrentKF,pKF,vvpMapPointMatches[i],mbFixScale);
pSolver->SetRansacParameters(0.99,20,300);// 至少20个inliers 300次迭代
vpSim3Solvers[i] = pSolver;
}
// 参与Sim3计算的候选关键帧数加1
nCandidates++;
}
bool bMatch = false;// 用于标记是否有一个候选帧通过Sim3的求解与优化
// 一直循环所有的候选帧,每个候选帧迭代5次,如果5次迭代后得不到结果,就换下一个候选帧
// 直到有一个候选帧首次迭代成功bMatch为true,或者某个候选帧总的迭代次数超过限制,直接将它剔除
while(nCandidates>0 && !bMatch)
{
for(int i=0; i<nInitialCandidates; i++)
{
if(vbDiscarded[i])
continue;
KeyFrame* pKF = mvpEnoughConsistentCandidates[i];
vector<bool> vbInliers;
int nInliers;
bool bNoMore;// 这是局部变量,在pSolver->iterate(...)内进行初始化
// 步骤3:对步骤2中有较好的匹配的关键帧求取Sim3变换
Sim3Solver* pSolver = vpSim3Solvers[i];
// 最多迭代5次,返回的Scm是候选帧pKF到当前帧mpCurrentKF的Sim3变换(T12)
cv::Mat Scm = pSolver->iterate(5,bNoMore,vbInliers,nInliers);
// 经过n次循环,每次迭代5次,总共迭代 n*5 次
// 总迭代次数达到最大限制还没有求出合格的Sim3变换,该候选帧剔除
if(bNoMore)
{
vbDiscarded[i]=true;
nCandidates--;
}
// If RANSAC returns a Sim3, perform a guided matching and optimize with all correspondences // If RANSAC returns a Sim3, perform a guided matching and optimize with all correspondences
if(!Scm.empty())
{
vector<MapPoint*> vpMapPointMatches(vvpMapPointMatches[i].size(), static_cast<MapPoint*>(NULL));
for(size_t j=0, jend=vbInliers.size(); j<jend; j++)
{
// 保存inlier的MapPoint
if(vbInliers[j])
vpMapPointMatches[j]=vvpMapPointMatches[i][j];
}
// 步骤4:通过步骤3求取的Sim3变换引导关键帧匹配弥补步骤2中的漏匹配
// [sR t;0 1]
cv::Mat R = pSolver->GetEstimatedRotation();// 候选帧pKF到当前帧mpCurrentKF的R(R12)
cv::Mat t = pSolver->GetEstimatedTranslation();// 候选帧pKF到当前帧mpCurrentKF的t(t12),当前帧坐标系下,方向由pKF指向当前帧
const float s = pSolver->GetEstimatedScale();// 候选帧pKF到当前帧mpCurrentKF的变换尺度s(s12)
// 查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数,之前使用SearchByBoW进行特征点匹配时会有漏匹配)
// 通过Sim3变换,确定pKF1的特征点在pKF2中的大致区域,同理,确定pKF2的特征点在pKF1中的大致区域
// 在该区域内通过描述子进行匹配捕获pKF1和pKF2之前漏匹配的特征点,更新匹配vpMapPointMatches
matcher.SearchBySim3(mpCurrentKF,pKF,vpMapPointMatches,s,R,t,7.5);
// 步骤5:Sim3优化,只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断
// OpenCV的Mat矩阵转成Eigen的Matrix类型
g2o::Sim3 gScm(Converter::toMatrix3d(R),Converter::toVector3d(t),s);
// 如果mbFixScale为true,则是6DoFf优化(双目 RGBD),如果是false,则是7DoF优化(单目)
// 优化mpCurrentKF与pKF对应的MapPoints间的Sim3,得到优化后的量gScm
const int nInliers = Optimizer::OptimizeSim3(mpCurrentKF, pKF, vpMapPointMatches, gScm, 10, mbFixScale);// 卡方chi2检验阈值
if(nInliers>=20)
{
bMatch = true;
// mpMatchedKF就是最终闭环检测出来与当前帧形成闭环的关键帧
mpMatchedKF = pKF;
// 得到从世界坐标系到该候选帧的Sim3变换,Scale=1
g2o::Sim3 gSmw(Converter::toMatrix3d(pKF->GetRotation()),Converter::toVector3d(pKF->GetTranslation()),1.0);
// 得到g2o优化后从世界坐标系到当前帧的Sim3变换
mg2oScw = gScm*gSmw;
mScw = Converter::toCvMat(mg2oScw);
mvpCurrentMatchedPoints = vpMapPointMatches;
break;// 只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断
}
}
}
}
// 没有一个闭环匹配候选帧通过Sim3的求解与优化
if(!bMatch)
{
// 清空mvpEnoughConsistentCandidates
for(int i=0; i<nInitialCandidates; i++)
mvpEnoughConsistentCandidates[i]->SetErase();
mpCurrentKF->SetErase();
return false;
}
// 步骤6:取出闭环匹配上关键帧的相连关键帧,得到它们的MapPoints放入mvpLoopMapPoints
// 注意是匹配上的那个关键帧:mpMatchedKF
// 将mpMatchedKF相连的关键帧全部取出来放入vpLoopConnectedKFs
// 将vpLoopConnectedKFs的MapPoints取出来放入mvpLoopMapPoints
vector<KeyFrame*> vpLoopConnectedKFs = mpMatchedKF->GetVectorCovisibleKeyFrames();
// 包含闭环匹配关键帧本身
vpLoopConnectedKFs.push_back(mpMatchedKF);
mvpLoopMapPoints.clear();
for(vector<KeyFrame*>::iterator vit=vpLoopConnectedKFs.begin(); vit!=vpLoopConnectedKFs.end(); vit++)
{
KeyFrame* pKF = *vit;
vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();
for(size_t i=0, iend=vpMapPoints.size(); i<iend; i++)
{
MapPoint* pMP = vpMapPoints[i];
if(pMP)
{
if(!pMP->isBad() && pMP->mnLoopPointForKF!=mpCurrentKF->mnId)
{
mvpLoopMapPoints.push_back(pMP);
// 标记该MapPoint被mpCurrentKF闭环时观测到并添加,避免重复添加
pMP->mnLoopPointForKF=mpCurrentKF->mnId;
}
}
}
}
// Find more matches projecting with the computed Sim3
// 步骤7:将闭环匹配上关键帧以及相连关键帧的MapPoints投影到当前关键帧进行投影匹配
// 根据投影查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数)
// 根据Sim3变换,将每个mvpLoopMapPoints投影到mpCurrentKF上,并根据尺度确定一个搜索区域,
// 根据该MapPoint的描述子与该区域内的特征点进行匹配,如果匹配误差小于TH_LOW即匹配成功,更新mvpCurrentMatchedPoints
// mvpCurrentMatchedPoints将用于SearchAndFuse中检测当前帧MapPoints与匹配的MapPoints是否存在冲突
matcher.SearchByProjection(mpCurrentKF, mScw, mvpLoopMapPoints, mvpCurrentMatchedPoints,10);// 搜索范围系数为10
// 步骤8:判断当前帧与检测出的所有闭环关键帧是否有足够多的MapPoints匹配
int nTotalMatches = 0;
for(size_t i=0; i<mvpCurrentMatchedPoints.size(); i++)
{
if(mvpCurrentMatchedPoints[i])
nTotalMatches++;
}
// 步骤9:清空mvpEnoughConsistentCandidates
if(nTotalMatches>=40)
{
for(int i=0; i<nInitialCandidates; i++)
if(mvpEnoughConsistentCandidates[i]!=mpMatchedKF)
mvpEnoughConsistentCandidates[i]->SetErase();
return true;
}
else
{
for(int i=0; i<nInitialCandidates; i++)
mvpEnoughConsistentCandidates[i]->SetErase();
mpCurrentKF->SetErase();
return false;
}
}
orb-slam2回环检测3
最新推荐文章于 2023-08-15 10:00:02 发布