LoopClosing::Run()

// 线程主函数
void LoopClosing::Run()
{mbFinished =false;// 线程主循环while(1){// Check if there are keyframes in the queue// Loopclosing中的关键帧是LocalMapping发送过来的,LocalMapping是Tracking中发过来的// 在LocalMapping中通过 InsertKeyFrame 将关键帧插入闭环检测队列mlpLoopKeyFrameQueue// 闭环检测队列mlpLoopKeyFrameQueue中的关键帧不为空if(CheckNewKeyFrames()){// Detect loop candidates and check covisibility consistencyif(DetectLoop()){// Compute similarity transformation [sR|t]// In the stereo/RGBD case s=1if(ComputeSim3()){// Perform loop fusion and pose graph optimizationCorrectLoop();}}}// 查看是否有外部线程请求复位当前线程ResetIfRequested();// 查看外部线程是否有终止当前线程的请求,如果有的话就跳出这个线程的主函数的主循环if(CheckFinish())break;//usleep(5000);std::this_thread::sleep_for(std::chrono::milliseconds(5));}// 运行到这里说明有外部线程请求终止当前线程,在这个函数中执行终止当前线程的一些操作SetFinish();
}

LoopClosing::CheckNewKeyFrames()

/** 查看列表中是否有等待被插入的关键帧* @return 如果存在,返回true*/
bool LoopClosing::CheckNewKeyFrames()
{unique_lock<mutex> lock(mMutexLoopQueue);return(!mlpLoopKeyFrameQueue.empty());
}

LoopClosing::DetectLoop()

// 检测回环
bool LoopClosing::DetectLoop()
{{// 从队列中取出一个关键帧,作为当前关键帧unique_lock<mutex> lock(mMutexLoopQueue);mpCurrentKF = mlpLoopKeyFrameQueue.front();mlpLoopKeyFrameQueue.pop_front();// Avoid that a keyframe can be erased while it is being process by this threadmpCurrentKF->SetNotErase();}//If the map contains less than 10 KF or less than 10 KF have passed from last loop detection// STEP 1:如果距离上次闭环没多久(小于10帧),或者map中关键帧总共还没有10帧,则不进行闭环检测// 后者的体现是当mLastLoopKFid为0的时候if(mpCurrentKF->mnId<mLastLoopKFid+10){mpKeyFrameDB->add(mpCurrentKF);mpCurrentKF->SetErase();return false;}// Compute reference BoW similarity score// This is the lowest score to a connected keyframe in the covisibility graph// We will impose loop candidates to have a higher similarity than this// VIII-A// STEP 2:遍历所有共视关键帧,计算当前关键帧与每个共视关键的bow相似度得分,并得到最低得分minScoreconst vector<KeyFrame*> vpConnectedKeyFrames = mpCurrentKF->GetVectorCovisibleKeyFrames();const DBoW2::BowVector &CurrentBowVec = mpCurrentKF->mBowVec;float minScore = 1;for(size_t i=0; i<vpConnectedKeyFrames.size(); i++){KeyFrame* pKF = vpConnectedKeyFrames[i];if(pKF->isBad())continue;const DBoW2::BowVector &BowVec = pKF->mBowVec;// 计算当前遍历到的这个关键帧,和前面的这个当前关键帧计算相似度得分;得分越低,相似度越低float score = mpORBVocabulary->score(CurrentBowVec, BowVec);// 更新最低得分if(score<minScore)minScore = score;}// Query the database imposing the minimum score// STEP 3:在所有关键帧中找出闭环备选帧// 说白了就是认为和当前关键帧具有回环关系的关键帧,不应该低于当前关键帧的相邻关键帧的最低的相似度// 得到的这些关键帧,和当前关键帧具有较多的共视单词,并且相似度评分都挺高vector<KeyFrame*> vpCandidateKFs = mpKeyFrameDB->DetectLoopCandidates(mpCurrentKF, minScore);// If there are no loop candidates, just add new keyframe and return falseif(vpCandidateKFs.empty()){mpKeyFrameDB->add(mpCurrentKF);mvConsistentGroups.clear();mpCurrentKF->SetErase();return false;           // 没有检测到回环}// 接下来就属于检测到回环啦// For each loop candidate check consistency with previous loop candidates// Each candidate expands a covisibility group (keyframes connected to the loop candidate in the covisibility graph)// A group is consistent with a previous group if they share at least a keyframe// We must detect a consistent loop in several consecutive keyframes to accept it// STEP 4:在候选帧中检测具有连续性的候选帧// 1、每个候选帧将与自己相连的关键帧构成一个“子候选组spCandidateGroup”, vpCandidateKFs-->spCandidateGroup// 2、检测“子候选组”中每一个关键帧是否存在于“连续组”,如果存在 nCurrentConsistency++,则将该“子候选组”放入“当前连续组vCurrentConsistentGroups”// 3、如果nCurrentConsistency大于等于3,那么该”子候选组“代表的候选帧过关,进入mvpEnoughConsistentCandidates// 相关的概念说明:// 组(group): 对于某个关键帧, 其和其具有共视关系的关键帧组成了一个"组";// 子候选组(CandidateGroup): 对于某个候选的回环关键帧, 其和其具有共视关系的关键帧组成的一个"组";// 连续(Consistent):  不同的组之间如果共同拥有一个及以上的关键帧,那么称这两个组之间具有连续关系// 连续性(Consistency):称之为连续长度可能更合适,表示累计的连续的链的长度:A--B 为1, A--B--C--D 为3等;具体反映在数据类型 ConsistentGroup.second上// 连续组(Consistent group): mvConsistentGroups存储了上次执行回环检测时, 新的被检测出来的具有连续性的多个组的集合.由于组之间的连续关系是个网状结构,因此可能存在//                          一个组因为和不同的连续组链都具有连续关系,而被添加两次的情况(当然连续性度量是不相同的)// 连续组链:我guoqing自造的称呼,类似于菊花链A--B--C--D这样形成了一条连续组链.对于这个例子中,由于可能E,F都和D有连续关系,因此连续组链会产生分叉;为了简化计算,连续组中将只会保存//         最后形成连续关系的连续组们(见下面的连续组的更新)// 子连续组: 上面的连续组中的一个组// 连续组的初始值: 在遍历某个候选帧的过程中,如果该子候选组没有能够和任何一个上次的子连续组产生连续关系,那么就将添加自己组为连续组,并且连续性为0(相当于新开了一个连续链)// 连续组的更新: 当前次回环检测过程中,所有被检测到和之前的连续组链有连续的关系的组,都将在对应的连续组链后面+1,这些子候选组(可能有重复,见上)都将会成为新的连续组;//              换而言之连续组mvConsistentGroups中只保存连续组链中末尾的组mvpEnoughConsistentCandidates.clear();// 最终筛选后得到的闭环帧// ConsistentGroup数据类型为pair<set<KeyFrame*>,int>// ConsistentGroup.first对应每个“连续组”中的关键帧,ConsistentGroup.second为每个“连续组”的序号// 这个下标是每个"子连续组"的下标,bool表示当前的候选组中是否有和该组相同的一个关键帧vector<ConsistentGroup> vCurrentConsistentGroups;vector<bool> vbConsistentGroup(mvConsistentGroups.size(),false);// 遍历刚才得到的每一个候选关键帧for(size_t i=0, iend=vpCandidateKFs.size(); i<iend; i++){KeyFrame* pCandidateKF = vpCandidateKFs[i];// 将自己以及与自己相连的关键帧构成一个“子候选组”set<KeyFrame*> spCandidateGroup = pCandidateKF->GetConnectedKeyFrames();spCandidateGroup.insert(pCandidateKF);bool bEnoughConsistent = false;bool bConsistentForSomeGroup = false;// 遍历之前的“子连续组s”for(size_t iG=0, iendG=mvConsistentGroups.size(); iG<iendG; iG++){// 取出一个之前的子连续组set<KeyFrame*> sPreviousGroup = mvConsistentGroups[iG].first;// 遍历每个“子候选组”,检测候选组中每一个关键帧在“子连续组”中是否存在// 如果有一帧共同存在于“子候选组”与之前的“子连续组”,那么“子候选组”与该“子连续组”连续bool bConsistent = false;for(set<KeyFrame*>::iterator sit=spCandidateGroup.begin(), send=spCandidateGroup.end(); sit!=send;sit++){if(sPreviousGroup.count(*sit)){bConsistent=true;// 该“子候选组”与该“子连续组”相连bConsistentForSomeGroup=true;// 该“子候选组”至少与一个”子连续组“相连break;}}if(bConsistent){// 和当前的候选组发生"连续"关系的子连续组的"已连续id"int nPreviousConsistency = mvConsistentGroups[iG].second;int nCurrentConsistency = nPreviousConsistency + 1;// 如果当前遍历到的"子连续组"还没有和"子候选组有相同的关键帧的记录,那么就+if(!vbConsistentGroup[iG])// 这里作者原本意思是不是应该是vbConsistentGroup[i]而不是vbConsistentGroup[iG]呢?(wubo???)//! 应该就是iG,vbConsistentGroup声明时候的大小和mvConsistentGroups是相同的,而在遍历mvConsistentGroups中的每个元素的时候使用的下标是iG//! 而i是在遍历vpCandidateKFs也就是经过KeyFrameDatabase得到的一堆初始的闭环候选帧的时候才使用的  --- guoqing{// 将该“子候选组”的该关键帧打上连续编号加入到“当前连续组”ConsistentGroup cg = make_pair(spCandidateGroup,nCurrentConsistency);vCurrentConsistentGroups.push_back(cg);vbConsistentGroup[iG]=true; //this avoid to include the same group more than once}// 如果已经连续得足够多了,那么当前的这个候选关键帧是足够靠谱的if(nCurrentConsistency>=mnCovisibilityConsistencyTh && !bEnoughConsistent){// 在遍历每个候选关键帧的过程中的、遍历之前的“子连续组”过程中,如果遍历到某个"子连续组"中符合要求的关键帧,就把当前的候选关键帧也添加进去mvpEnoughConsistentCandidates.push_back(pCandidateKF);bEnoughConsistent=true; //this avoid to insert the same candidate more than once}//这里是不是缺一个break来提高效率呢?(wubo???)// 师兄说的对 (guoqing)}}// If the group is not consistent with any previous group insert with consistency counter set to zero// 如果该“子候选组”的所有关键帧都不存在于“子连续组”,那么 vCurrentConsistentGroups 将为空,// 于是就把“子候选组”全部拷贝到 vCurrentConsistentGroups, 并最终用于更新mvConsistentGroups,计数器设为0,重新开始if(!bConsistentForSomeGroup){// 这个地方是不是最好clear一下 vCurrentConsistentGroups 呢?(wubo???)//! 师兄不可以啊! 因为上面的if条件满足的时候,只能够说明当前遍历到的这个候选关键帧形成的子候选组和所有的子连续组没有连续关系,所以当前子候选组是一个新出现的连续组链;//! 但是这不能够代表其他的候选关键帧的子候选组和那些子连续组们没有连续关系!所以 vCurrentConsistentGroups 不能清空//! 如果清空了的话,那么只要候选关键帧中有一帧比较孤立的,和当前已有的所有连续组都不产生共视关系,那么完蛋了,在遍历当前候选关键帧之前的所有连续关系//! 都会被清空    ----guoqingConsistentGroup cg = make_pair(spCandidateGroup,0);vCurrentConsistentGroups.push_back(cg);}}// 遍历得到的初级的候选关键帧// Update Covisibility Consistent Groups// 当前的变上一次的mvConsistentGroups = vCurrentConsistentGroups;// Add Current Keyframe to databasempKeyFrameDB->add(mpCurrentKF);// 如果不过关if(mvpEnoughConsistentCandidates.empty()){mpCurrentKF->SetErase();return false;}else // 过关{return true;}// 多余的代码,执行不到mpCurrentKF->SetErase();return false;
}

KeyFrameDatabase::DetectLoopCandidates()

/** @brief 在闭环检测中找到与该关键帧可能闭环的关键帧* 1. 找出和当前帧具有公共单词的所有关键帧(不包括与当前帧相连的关键帧)* 2. 只和具有共同单词较多的关键帧进行相似度计算* 3. 将与关键帧相连(权值最高)的前十个关键帧归为一组,计算累计得分* 4. 只返回累计得分较高的组中分数最高的关键帧* @param pKF      需要闭环的关键帧* @param minScore 相似性分数最低要求* @return         可能闭环的关键帧* @see III-E Bags of Words Place Recognition*/
vector<KeyFrame*> KeyFrameDatabase::DetectLoopCandidates(KeyFrame* pKF, float minScore)
{// 提出所有与该pKF相连的KeyFrame,这些相连Keyframe都是局部相连,在闭环检测的时候将被剔除set<KeyFrame*> spConnectedKeyFrames = pKF->GetConnectedKeyFrames();list<KeyFrame*> lKFsSharingWords;// 用于保存可能与pKF形成回环的候选帧(只要有相同的word,且不属于局部相连帧)//这里的局部相连帧,就是和当前关键帧具有共视关系的关键帧// Search all keyframes that share a word with current keyframes// Discard keyframes connected to the query keyframe//. 步骤1:找出和当前帧具有公共单词的所有关键帧(不包括与当前帧链接的关键帧){unique_lock<mutex> lock(mMutex);// words是检测图像是否匹配的枢纽,遍历该pKF的每一个wordfor(DBoW2::BowVector::const_iterator vit=pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit != vend; vit++){// 提取所有包含该word的KeyFramelist<KeyFrame*> &lKFs =   mvInvertedFile[vit->first];// 然后对这些关键帧展开遍历for(list<KeyFrame*>::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++){KeyFrame* pKFi=*lit;if(pKFi->mnLoopQuery!=pKF->mnId)// pKFi还没有标记为pKF的候选帧{pKFi->mnLoopWords=0;if(!spConnectedKeyFrames.count(pKFi))// 与pKF局部链接的关键帧不进入闭环候选帧{pKFi->mnLoopQuery=pKF->mnId;// pKFi标记为pKF的候选帧,之后直接跳过判断lKFsSharingWords.push_back(pKFi);}}pKFi->mnLoopWords++;// 记录pKFi与pKF具有相同word的个数}}}// 如果没有关键帧和这个关键帧具有相同的单词,那么就返回空if(lKFsSharingWords.empty())return vector<KeyFrame*>();list<pair<float,KeyFrame*> > lScoreAndMatch;// Only compare against those keyframes that share enough words//. 步骤2:统计所有闭环候选帧中与pKF具有共同单词最多的单词数int maxCommonWords=0;for(list<KeyFrame*>::iterator lit=lKFsSharingWords.begin(), lend= lKFsSharingWords.end(); lit!=lend; lit++){if((*lit)->mnLoopWords>maxCommonWords)maxCommonWords=(*lit)->mnLoopWords;}int minCommonWords = maxCommonWords*0.8f;int nscores=0;// Compute similarity score. Retain the matches whose score is higher than minScore//. 步骤3:遍历所有闭环候选帧,挑选出共有单词数大于minCommonWords且单词匹配度大于minScore存入lScoreAndMatchfor(list<KeyFrame*>::iterator lit=lKFsSharingWords.begin(), lend= lKFsSharingWords.end(); lit!=lend; lit++){KeyFrame* pKFi = *lit;// pKF只和具有共同单词较多的关键帧进行比较,需要大于minCommonWordsif(pKFi->mnLoopWords>minCommonWords){nscores++;// 这个变量后面没有用到// 相似度评分就是在这里计算的float si = mpVoc->score(pKF->mBowVec,pKFi->mBowVec);pKFi->mLoopScore = si;if(si>=minScore)lScoreAndMatch.push_back(make_pair(si,pKFi));}}// 如果没有超过指定相似度阈值的,那么也就直接跳过去if(lScoreAndMatch.empty())return vector<KeyFrame*>();list<pair<float,KeyFrame*> > lAccScoreAndMatch;float bestAccScore = minScore;// Lets now accumulate score by covisibility// 单单计算当前帧和某一关键帧的相似性是不够的,这里将与关键帧相连(权值最高,共视程度最高)的前十个关键帧归为一组,计算累计得分//. 步骤4:具体而言:lScoreAndMatch中每一个KeyFrame都把与自己共视程度较高的帧归为一组,每一组会计算组得分并记录该组分数最高的KeyFrame,记录于lAccScoreAndMatchfor(list<pair<float,KeyFrame*> >::iterator it=lScoreAndMatch.begin(), itend=lScoreAndMatch.end(); it!=itend; it++){KeyFrame* pKFi = it->second;vector<KeyFrame*> vpNeighs = pKFi->GetBestCovisibilityKeyFrames(10);float bestScore = it->first; // 该组最高分数float accScore = it->first;  // 该组累计得分KeyFrame* pBestKF = pKFi;    // 该组最高分数对应的关键帧for(vector<KeyFrame*>::iterator vit=vpNeighs.begin(), vend=vpNeighs.end(); vit!=vend; vit++){KeyFrame* pKF2 = *vit;if(pKF2->mnLoopQuery==pKF->mnId && pKF2->mnLoopWords>minCommonWords){accScore+=pKF2->mLoopScore;// 因为pKF2->mnLoopQuery==pKF->mnId,所以只有pKF2也在闭环候选帧中,才能贡献分数if(pKF2->mLoopScore>bestScore)// 统计得到组里分数最高的KeyFrame{pBestKF=pKF2;bestScore = pKF2->mLoopScore;}}}lAccScoreAndMatch.push_back(make_pair(accScore,pBestKF));if(accScore>bestAccScore)// 记录所有组中组得分最高的组bestAccScore=accScore;}// Return all those keyframes with a score higher than 0.75*bestScorefloat minScoreToRetain = 0.75f*bestAccScore;set<KeyFrame*> spAlreadyAddedKF;vector<KeyFrame*> vpLoopCandidates;vpLoopCandidates.reserve(lAccScoreAndMatch.size());//. 步骤5:得到组得分大于minScoreToRetain的组,得到组中分数最高的关键帧 0.75*bestScorefor(list<pair<float,KeyFrame*> >::iterator it=lAccScoreAndMatch.begin(), itend=lAccScoreAndMatch.end(); it!=itend; it++){if(it->first>minScoreToRetain){KeyFrame* pKFi = it->second;if(!spAlreadyAddedKF.count(pKFi))// 判断该pKFi是否已经在队列中了{vpLoopCandidates.push_back(pKFi);spAlreadyAddedKF.insert(pKFi);}}}return vpLoopCandidates;
}

LoopClosing::ComputeSim3()

bool LoopClosing::ComputeSim3()
{// 说明:计算当前帧与闭环帧的Sim3变换等// 1. 通过Bow加速描述子的匹配,利用RANSAC粗略地计算出当前帧与闭环帧的Sim3(当前帧---闭环帧)          \n// 2. 根据估计的Sim3,对3D点进行投影找到更多匹配,通过优化的方法计算更精确的Sim3(当前帧---闭环帧)     \n// 3. 将闭环帧以及闭环帧相连的关键帧的MapPoints与当前帧的点进行匹配(当前帧---闭环帧+相连关键帧)      \n// 注意以上匹配的结果均都存在成员变量mvpCurrentMatchedPoints中,实际的更新步骤见CorrectLoop()步骤3:Start Loop Fusion \n// 对于双目或者是RGBD输入的情况,计算得到的尺度=1// Input:// do   : //       1. 遍历闭环候选帧集,筛选出与当前帧的匹配特征点数大于20的候选帧集合,并为每一个候选帧构造一个Sim3Solver//       2. 对每一个候选帧进行 RANSAC 迭代匹配,直到有一个候选帧匹配成功,或者全部失败//       3. 取出闭环匹配上关键帧的相连关键帧,得到它们的MapPoints放入 mvpLoopMapPoints//       4. 将闭环匹配上关键帧以及相连关键帧的MapPoints投影到当前关键帧进行投影匹配//       5. 判断当前帧与检测出的所有闭环关键帧是否有足够多的MapPoints匹配//       6. 清空mvpEnoughConsistentCandidates// output:// Step 0. 准备工作// For each consistent loop candidate we try to compute a Sim3const int nInitialCandidates = mvpEnoughConsistentCandidates.size();// We compute first ORB matches for each candidate// If enough matches are found, we setup a Sim3SolverORBmatcher matcher(0.75,true);// 用 vector 存储每一个候选帧的Sim3Solver求解器vector<Sim3Solver*> vpSim3Solvers;vpSim3Solvers.resize(nInitialCandidates);// 用 vector 存储每个候选帧的匹配地图点信息vector<vector<MapPoint*> > vvpMapPointMatches;vvpMapPointMatches.resize(nInitialCandidates);// 用 vector 存储每个候选帧应该被放弃(True)或者 保留(False)vector<bool> vbDiscarded;vbDiscarded.resize(nInitialCandidates);// 完成 Step 1.0 的匹配后,被保留的候选帧数量int nCandidates=0;// Step 1. 遍历闭环候选帧集,筛选出与当前帧的匹配特征点数大于20的候选帧集合,并为每一个候选帧构造一个Sim3Solverfor(int i=0; i<nInitialCandidates; i++){// Step 1.1 从筛选的闭环候选帧中取出一帧关键帧pKFKeyFrame* pKF = mvpEnoughConsistentCandidates[i];// 避免在LocalMapping中KeyFrameCulling函数将此关键帧作为冗余帧剔除pKF->SetNotErase();// 如果候选帧质量不高,直接PASSif(pKF->isBad()){vbDiscarded[i] = true;continue;}// Step 1.2 将当前帧 mpCurrentKF 与闭环候选关键帧pKF匹配// 通过bow加速得到 mpCurrentKF 与 pKF 之间的匹配特征点// vvpMapPointMatches 是匹配特征点对应的 MapPoints,本质上来自于候选闭环帧int nmatches = matcher.SearchByBoW(mpCurrentKF,pKF,vvpMapPointMatches[i]);// 匹配的特征点数太少,该候选帧剔除if(nmatches<20){vbDiscarded[i] = true;continue;}else{// Step 1.3 为保留的候选帧构造Sim3求解器// 如果 mbFixScale 为 true,则是6DoFf优化(双目 RGBD)// 如果是false,则是7DoF优化(单目)Sim3Solver* pSolver = new Sim3Solver(mpCurrentKF,pKF,vvpMapPointMatches[i],mbFixScale);// Sim3Solver Ransac 过程至少20个inliers 300次迭代pSolver->SetRansacParameters(0.99,20,300);vpSim3Solvers[i] = pSolver;}// 保留的候选帧数量nCandidates++;}// 用于标记是否有一个候选帧通过Sim3Solver的求解与优化bool bMatch = false;// Step 2.0 对每一个候选帧进行 RANSAC 迭代匹配,直到有一个候选帧匹配成功,或者全部失败while(nCandidates>0 && !bMatch){// 遍历每一个候选帧for(int i=0; i<nInitialCandidates; i++){if(vbDiscarded[i])continue;KeyFrame* pKF = mvpEnoughConsistentCandidates[i];// 内点(Inliers)标志// 即标记经过RANSAC sim3 求解后,vvpMapPointMatches中的哪些作为内点vector<bool> vbInliers; // 内点(Inliers)数量int nInliers;// 是否到达了最优解bool bNoMore;// Step 2.1 取出从 Step 1.3 中为当前候选帧构建的 Sim3Solver 并开始迭代Sim3Solver* pSolver = vpSim3Solvers[i];// 最多迭代5次,返回的Scm是候选帧pKF到当前帧mpCurrentKF的Sim3变换(T12)cv::Mat Scm  = pSolver->iterate(5,bNoMore,vbInliers,nInliers);// If Ransac reachs max. iterations discard keyframe// 经过n次循环,每次迭代5次,总共迭代 n*5 次// 总迭代次数达到最大限制还没有求出合格的Sim3变换,该候选帧剔除if(bNoMore){vbDiscarded[i]=true;nCandidates--;}// If RANSAC returns a Sim3, perform a guided matching and optimize with all correspondencesif(!Scm.empty()){vector<MapPoint*> vpMapPointMatches(vvpMapPointMatches[i].size(), static_cast<MapPoint*>(NULL));for(size_t j=0, jend=vbInliers.size(); j<jend; j++){// 保存inlier的MapPointif(vbInliers[j])vpMapPointMatches[j]=vvpMapPointMatches[i][j];}// Step 2.2 通过步骤2.1求取的Sim3变换引导关键帧匹配弥补步骤1中的漏匹配// 候选帧pKF到当前帧mpCurrentKF的R(R12),t(t12),变换尺度s(s12)cv::Mat R = pSolver->GetEstimatedRotation();cv::Mat t = pSolver->GetEstimatedTranslation();const float s = pSolver->GetEstimatedScale();// 查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数,之前使用SearchByBoW进行特征点匹配时会有漏匹配)// 通过Sim3变换,确定pKF1的特征点在pKF2中的大致区域,同理,确定pKF2的特征点在pKF1中的大致区域// 在该区域内通过描述子进行匹配捕获pKF1和pKF2之前漏匹配的特征点,更新匹配vpMapPointMatchesmatcher.SearchBySim3(mpCurrentKF,pKF,vpMapPointMatches,s,R,t,7.5);// Step 2.3 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,得到优化后的量gScmconst int nInliers = Optimizer::OptimizeSim3(mpCurrentKF, pKF, vpMapPointMatches, gScm, 10, mbFixScale);// 如果优化成功,则停止 ransacs(即while循环)并继续下一步if(nInliers>=20){// 为True时将不再进入 while循环bMatch = true;// mpMatchedKF就是最终闭环检测出来与当前帧形成闭环的关键帧mpMatchedKF = pKF;// 得到从世界坐标系到该候选帧的Sim3变换,Scale=1g2o::Sim3 gSmw(Converter::toMatrix3d(pKF->GetRotation()),Converter::toVector3d(pKF->GetTranslation()),1.0);// 得到g2o优化后从世界坐标系到当前帧的Sim3变换mg2oScw = gScm*gSmw;mScw = Converter::toCvMat(mg2oScw);mvpCurrentMatchedPoints = vpMapPointMatches;// 只要有一个候选帧通过Sim3的求解与优化,就跳出停止对其它候选帧的判断break;}}}}// 退出上面while循环的原因有两种,一种是求解到了bMatch置位后出的,另外一种是nCandidates耗尽为0// 没有一个闭环匹配候选帧通过Sim3的求解与优化if(!bMatch){// 清空mvpEnoughConsistentCandidatesfor(int i=0; i<nInitialCandidates; i++)// 卧槽,这么严格吗?这些关键帧以后都不会在再参加回环检测过程了mvpEnoughConsistentCandidates[i]->SetErase();// 当前关键帧也是,将不会再参加回环检测的过程了mpCurrentKF->SetErase();return false;}// Step 3.0:取出闭环匹配上关键帧的相连关键帧,得到它们的MapPoints放入 mvpLoopMapPoints// 注意是匹配上的那个关键帧:mpMatchedKF// 将mpMatchedKF相连的关键帧全部取出来放入 vpLoopConnectedKFs// 将vpLoopConnectedKFs的MapPoints取出来放入mvpLoopMapPointsvector<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// Step 4.0:将闭环匹配上关键帧以及相连关键帧的MapPoints投影到当前关键帧进行投影匹配// 根据投影查找更多的匹配(成功的闭环匹配需要满足足够多的匹配特征点数)// 根据Sim3变换,将每个mvpLoopMapPoints投影到mpCurrentKF上,并根据尺度确定一个搜索区域,// 根据该MapPoint的描述子与该区域内的特征点进行匹配,如果匹配误差小于TH_LOW即匹配成功,更新mvpCurrentMatchedPoints// mvpCurrentMatchedPoints将用于SearchAndFuse中检测当前帧MapPoints与匹配的MapPoints是否存在冲突// 搜索范围系数为10matcher.SearchByProjection(mpCurrentKF, mScw, mvpLoopMapPoints, mvpCurrentMatchedPoints,10);// If enough matches accept Loop// Step 5.0 判断当前帧与检测出的所有闭环关键帧是否有足够多的MapPoints匹配int nTotalMatches = 0;for(size_t i=0; i<mvpCurrentMatchedPoints.size(); i++){if(mvpCurrentMatchedPoints[i])nTotalMatches++;}// Step 6.0 清空mvpEnoughConsistentCandidatesif(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;}
}

LoopClosing::CorrectLoop()

/* * @brief 闭环纠正* @detials \n* 1. 通过求解的Sim3以及相对姿态关系,调整与当前帧相连的关键帧位姿以及这些关键帧观测到的MapPoints的位置(相连关键帧---当前帧) \n* 2. 将闭环帧以及闭环帧相连的关键帧的MapPoints和与当前帧相连的关键帧的点进行匹配(相连关键帧+当前帧---闭环帧+相连关键帧)     \n* 3. 通过MapPoints的匹配关系更新这些帧之间的连接关系,即更新covisibility graph                                      \n* 4. 对Essential Graph(Pose Graph)进行优化,MapPoints的位置则根据优化后的位姿做相对应的调整                         \n* 5. 创建线程进行全局Bundle Adjustment*/
void LoopClosing::CorrectLoop()
{cout << "Loop detected!" << endl;// STEP 0:请求局部地图停止,防止在回环矫正时局部地图线程中InsertKeyFrame函数插入新的关键帧// STEP 1:根据共视关系更新当前帧与其它关键帧之间的连接// STEP 2:通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的MapPoints// STEP 3:检查当前帧的MapPoints与闭环匹配帧的MapPoints是否存在冲突,对冲突的MapPoints进行替换或填补// STEP 4:通过将闭环时相连关键帧的mvpLoopMapPoints投影到这些关键帧中,进行MapPoints检查与替换// STEP 5:更新当前关键帧之间的共视相连关系,得到因闭环时MapPoints融合而新得到的连接关系// STEP 6:进行EssentialGraph优化,LoopConnections是形成闭环后新生成的连接关系,不包括步骤7中当前帧与闭环匹配帧之间的连接关系// STEP 7:添加当前帧与闭环匹配帧之间的边(这个连接关系不优化)// STEP 8:新建一个线程用于全局BA优化// Send a stop signal to Local Mapping// Avoid new keyframes are inserted while correcting the loop// STEP 0:请求局部地图停止,防止在回环矫正时局部地图线程中InsertKeyFrame函数插入新的关键帧mpLocalMapper->RequestStop();// 终止全局BA线程if(isRunningGBA()){// 这个标志位仅用于控制输出提示,可忽略unique_lock<mutex> lock(mMutexGBA);mbStopGBA = true;mnFullBAIdx++;if(mpThreadGBA){// 停止全局BA线程mpThreadGBA->detach();delete mpThreadGBA;}}// Wait until Local Mapping has effectively stoppedwhile(!mpLocalMapper->isStopped()){//usleep(1000);std::this_thread::sleep_for(std::chrono::milliseconds(1));}// Ensure current keyframe is updated// STEP 1:根据共视关系更新当前帧与其它关键帧之间的连接// 猜测这里还要更新的原因应该是,当前处理的这个关键帧应该不是当前时刻最新插入的关键帧,它可能和后面新来的一些关键帧也产生了共视关系,虽然// 在那些关键帧中也在这个中添加了更新的操作但是,有些图(本征图?)没法被他们更新吧,还是得这个关键帧自己来mpCurrentKF->UpdateConnections();// Retrive keyframes connected to the current keyframe and compute corrected Sim3 pose by propagation// STEP 2:通过位姿传播,得到Sim3优化后,与当前帧相连的关键帧的位姿,以及它们的MapPoints// 当前帧与世界坐标系之间的Sim变换在ComputeSim3函数中已经确定并优化,// 通过相对位姿关系,可以确定这些相连的关键帧与世界坐标系之间的Sim3变换// 取出与当前帧相连的关键帧,包括当前关键帧 -- 获取当前关键帧组mvpCurrentConnectedKFs = mpCurrentKF->GetVectorCovisibleKeyFrames();mvpCurrentConnectedKFs.push_back(mpCurrentKF);KeyFrameAndPose CorrectedSim3, NonCorrectedSim3;// 先将mpCurrentKF的Sim3变换存入,固定不动CorrectedSim3[mpCurrentKF]=mg2oScw;cv::Mat Twc = mpCurrentKF->GetPoseInverse();// 对地图点操作的临界区{// Get Map Mutexunique_lock<mutex> lock(mpMap->mMutexMapUpdate);// STEP 2.1:通过位姿传播,得到Sim3调整后其它与当前帧相连关键帧的位姿(只是得到,还没有修正)// 遍历"当前关键帧组""for(vector<KeyFrame*>::iterator vit=mvpCurrentConnectedKFs.begin(), vend=mvpCurrentConnectedKFs.end(); vit!=vend; vit++){KeyFrame* pKFi = *vit;cv::Mat Tiw = pKFi->GetPose();// currentKF在前面已经添加if(pKFi!=mpCurrentKF){// 得到当前帧到pKFi帧的相对变换cv::Mat Tic = Tiw*Twc;cv::Mat Ric = Tic.rowRange(0,3).colRange(0,3);cv::Mat tic = Tic.rowRange(0,3).col(3);g2o::Sim3 g2oSic(Converter::toMatrix3d(Ric),Converter::toVector3d(tic),1.0);// 当前帧的位姿固定不动,其它的关键帧根据相对关系得到Sim3调整的位姿g2o::Sim3 g2oCorrectedSiw = g2oSic*mg2oScw;// Pose corrected with the Sim3 of the loop closure// 得到闭环g2o优化后各个关键帧的位姿CorrectedSim3[pKFi]=g2oCorrectedSiw;}cv::Mat Riw = Tiw.rowRange(0,3).colRange(0,3);cv::Mat tiw = Tiw.rowRange(0,3).col(3);g2o::Sim3 g2oSiw(Converter::toMatrix3d(Riw),Converter::toVector3d(tiw),1.0);// Pose without correctionNonCorrectedSim3[pKFi]=g2oSiw;}// Correct all MapPoints obsrved by current keyframe and neighbors, so that they align with the other side of the loop// STEP 2.2:得到调整相连帧位姿后,修正这些关键帧的MapPoints// 遍历当前关键帧组中的每一个关键帧for(KeyFrameAndPose::iterator mit=CorrectedSim3.begin(), mend=CorrectedSim3.end(); mit!=mend; mit++){KeyFrame* pKFi = mit->first;g2o::Sim3 g2oCorrectedSiw = mit->second;g2o::Sim3 g2oCorrectedSwi = g2oCorrectedSiw.inverse();g2o::Sim3 g2oSiw =NonCorrectedSim3[pKFi];vector<MapPoint*> vpMPsi = pKFi->GetMapPointMatches();// 遍历这个关键帧中的每一个地图点for(size_t iMP=0, endMPi = vpMPsi.size(); iMP<endMPi; iMP++){MapPoint* pMPi = vpMPsi[iMP];if(!pMPi)continue;if(pMPi->isBad())continue;if(pMPi->mnCorrectedByKF==mpCurrentKF->mnId) // 防止重复修正continue;// 修正过程开始,本质上也是基于当前关键帧的优化后的位姿展开的// Project with non-corrected pose and project back with corrected pose// 将该未校正的eigP3Dw先从世界坐标系映射到未校正的pKFi相机坐标系,然后再反映射到校正后的世界坐标系下cv::Mat P3Dw = pMPi->GetWorldPos();Eigen::Matrix<double,3,1> eigP3Dw = Converter::toVector3d(P3Dw);Eigen::Matrix<double,3,1> eigCorrectedP3Dw = g2oCorrectedSwi.map(g2oSiw.map(eigP3Dw));cv::Mat cvCorrectedP3Dw = Converter::toCvMat(eigCorrectedP3Dw);pMPi->SetWorldPos(cvCorrectedP3Dw);pMPi->mnCorrectedByKF = mpCurrentKF->mnId;pMPi->mnCorrectedReference = pKFi->mnId;// 勿忘更新pMPi->UpdateNormalAndDepth();}// Update keyframe pose with corrected Sim3. First transform Sim3 to SE3 (scale translation)// STEP 2.3:将Sim3转换为SE3,根据更新的Sim3,更新关键帧的位姿// 其实是现在已经有了更新后的关键帧组中关键帧的位姿,但是在上面的操作时只是暂时存储到了 KeyFrameAndPose 类型的变量中,还没有写回到关键帧对象中Eigen::Matrix3d eigR = g2oCorrectedSiw.rotation().toRotationMatrix();  //调用toRotationMatrix来去除尺度信息的Eigen::Vector3d eigt = g2oCorrectedSiw.translation();                   //目测是其中还包含有尺度信息,还得去除double s = g2oCorrectedSiw.scale();eigt *=(1./s); //[R t/s;0 1]cv::Mat correctedTiw = Converter::toCvSE3(eigR,eigt);pKFi->SetPose(correctedTiw);// Make sure connections are updated// STEP 2.4:根据共视关系更新当前帧与其它关键帧之间的连接// 地图点的位置改变了,可能会引起共视关系\权值的改变 pKFi->UpdateConnections();}// Start Loop Fusion// Update matched map points and replace if duplicated// STEP 3:检查当前帧的MapPoints与闭环匹配帧的MapPoints是否存在冲突,对冲突的MapPoints进行替换或填补// mvpCurrentMatchedPoints 是刚才当前关键帧和闭环关键帧组的所有地图点进行投影得到的匹配点for(size_t i=0; i<mvpCurrentMatchedPoints.size(); i++){if(mvpCurrentMatchedPoints[i]){MapPoint* pLoopMP = mvpCurrentMatchedPoints[i];//下标越界问题应该不存在的吧,存储地图点的数组应该是和特征点的数目长度是相同的,即使是mvpCurrentMatchedPoints中也不会有超过特征点个数的地图点啊MapPoint* pCurMP = mpCurrentKF->GetMapPoint(i); if(pCurMP)// 如果有重复的MapPoint(当前帧和匹配帧各有一个),则用匹配帧的代替现有的// 因为现有的帧往往已经有累计的误差了pCurMP->Replace(pLoopMP);else// 如果当前帧没有该MapPoint,则直接添加{mpCurrentKF->AddMapPoint(pLoopMP,i);pLoopMP->AddObservation(mpCurrentKF,i);pLoopMP->ComputeDistinctiveDescriptors();}}} }// Project MapPoints observed in the neighborhood of the loop keyframe// into the current keyframe and neighbors using corrected poses.// Fuse duplications.// STEP 4:通过将闭环时相连关键帧的mvpLoopMapPoints投影到这些关键帧中,进行MapPoints检查与替换// 当前线程中的函数SearchAndFuse(CorrectedSim3);// After the MapPoint fusion, new links in the covisibility graph will appear attaching both sides of the loop// STEP 5:更新当前关键帧之间的共视相连关系,得到因闭环时MapPoints融合而新得到的连接关系// 这个变量中将会存储那些因为闭环关系的形成,而新形成的链接关系map<KeyFrame*, set<KeyFrame*> > LoopConnections;// STEP 5.1:遍历当前帧相连关键帧(一级相连)for(vector<KeyFrame*>::iterator vit=mvpCurrentConnectedKFs.begin(), vend=mvpCurrentConnectedKFs.end(); vit!=vend; vit++){KeyFrame* pKFi = *vit;// STEP 5.2:得到与当前帧相连关键帧的相连关键帧(二级相连)vector<KeyFrame*> vpPreviousNeighbors = pKFi->GetVectorCovisibleKeyFrames();// Update connections. Detect new links.// STEP 5.3:更新一级相连关键帧的连接关系(会把当前关键帧添加进去,因为地图点已经更新和替换了)pKFi->UpdateConnections();// STEP 5.4:取出该帧更新后的连接关系LoopConnections[pKFi]=pKFi->GetConnectedKeyFrames();// STEP 5.5:从连接关系中去除闭环之前的二级连接关系,剩下的连接就是由闭环得到的连接关系for(vector<KeyFrame*>::iterator vit_prev=vpPreviousNeighbors.begin(), vend_prev=vpPreviousNeighbors.end(); vit_prev!=vend_prev; vit_prev++){LoopConnections[pKFi].erase(*vit_prev);}// STEP 5.6:从连接关系中去除闭环之前的一级连接关系,剩下的连接就是由闭环得到的连接关系for(vector<KeyFrame*>::iterator vit2=mvpCurrentConnectedKFs.begin(), vend2=mvpCurrentConnectedKFs.end(); vit2!=vend2; vit2++){LoopConnections[pKFi].erase(*vit2);}}// Optimize graph// STEP 6:进行EssentialGraph优化,LoopConnections是形成闭环后新生成的连接关系,不包括步骤7中当前帧与闭环匹配帧之间的连接关系Optimizer::OptimizeEssentialGraph(mpMap, mpMatchedKF, mpCurrentKF, NonCorrectedSim3, CorrectedSim3, LoopConnections, mbFixScale);// Add loop edge// STEP 7:添加当前帧与闭环匹配帧之间的边(这个连接关系不优化)// 这两句话应该放在OptimizeEssentialGraph之前,因为OptimizeEssentialGraph的步骤4.2中有优化mpMatchedKF->AddLoopEdge(mpCurrentKF);mpCurrentKF->AddLoopEdge(mpMatchedKF);// Launch a new thread to perform Global Bundle Adjustment// STEP 8:新建一个线程用于全局BA优化// OptimizeEssentialGraph只是优化了一些主要关键帧的位姿,这里进行全局BA可以全局优化所有位姿和MapPointsmbRunningGBA = true;mbFinishedGBA = false;mbStopGBA = false;mpThreadGBA = new thread(&LoopClosing::RunGlobalBundleAdjustment,this,mpCurrentKF->mnId);// Loop closed. Release Local Mapping.mpLocalMapper->Release();    cout << "Loop Closed!" << endl;mLastLoopKFid = mpCurrentKF->mnId;
}

LoopClosing::SearchAndFuse()

// 通过将闭环时相连关键帧的MapPoints投影到当前关键帧组中的这些关键帧中
// 进行MapPoints检查与替换(将回环帧中的地图点代替当前关键帧组中关键帧中的地图点)
// 因为回环关键帧处的时间比较久远,而当前关键帧组中的关键帧的地图点会有累计的误差啊
void LoopClosing::SearchAndFuse(const KeyFrameAndPose &CorrectedPosesMap)
{// 定义ORB匹配器ORBmatcher matcher(0.8);// 遍历闭环相连的关键帧 for(KeyFrameAndPose::const_iterator mit=CorrectedPosesMap.begin(), mend=CorrectedPosesMap.end(); mit!=mend;mit++){KeyFrame* pKF = mit->first;g2o::Sim3 g2oScw = mit->second;cv::Mat cvScw = Converter::toCvMat(g2oScw);// 将闭环相连帧的MapPoints坐标变换到pKF帧坐标系,然后投影,检查冲突并融合vector<MapPoint*> vpReplacePoints(mvpLoopMapPoints.size(),static_cast<MapPoint*>(NULL));// vpReplacePoints中存储的将会是这个关键帧中的地图点(也就是需要替换掉的新的地图点),原地图点的id则对应这个变量的下标// 搜索区域系数为4matcher.Fuse(pKF,cvScw,mvpLoopMapPoints,4,vpReplacePoints);// Get Map Mutex// 之所以不在前面的 Fuse 函数中进行地图点融合更新的原因是需要对地图加锁,而这里的设计中matcher中并不保存地图的指针unique_lock<mutex> lock(mpMap->mMutexMapUpdate);const int nLP = mvpLoopMapPoints.size();// 遍历闭环帧组的所有的地图点for(int i=0; i<nLP;i++){MapPoint* pRep = vpReplacePoints[i];if(pRep){// 用mvpLoopMapPoints替换掉之前的pRep->Replace(mvpLoopMapPoints[i]);}}}
}

LoopClosing::RunGlobalBundleAdjustment()

// 全局BA线程,这个是这个线程的主函数; 输入的函数参数看上去是闭环关键帧,但是在调用的时候给的其实是当前关键帧的id
void LoopClosing::RunGlobalBundleAdjustment(unsigned long nLoopKF)
{cout << "Starting Global Bundle Adjustment" << endl;// 记录当前迭代id,用来检查全局BA过程是否是因为意外结束的int idx =  mnFullBAIdx;// 牛逼,直接调优化器中的函数了// 10是迭代次数// mbStopGBA直接传引用过去了,这样当有外部请求的时候这个优化函数能够及时相应并且结束掉//? 提问:进行完这个过程后我们能够获得哪些信息?// 目测是能够得到全部关键帧优化后的位姿,以及部分地图点优化之后的位姿Optimizer::GlobalBundleAdjustemnt(mpMap,        // 地图点对象10,           // 迭代次数&mbStopGBA,   // 外界控制 GBA 停止的标志nLoopKF,      // 形成了闭环的当前关键帧的idfalse);       // 不使用鲁棒核函数// Update all MapPoints and KeyFrames// Local Mapping was active during BA, that means that there might be new keyframes// not included in the Global BA and they are not consistent with the updated map.// We need to propagate the correction through the spanning tree{unique_lock<mutex> lock(mMutexGBA);// 如果全局BA过程是因为意外结束的,那么后面的内容就都不用管了if(idx!=mnFullBAIdx)return;// 如果没有中断当前次BA的请求// 这里和上面那句话的功能还有些不同,因为如果一次全局优化被中断,往往意味又要重新开启一个新的全局BA;为了中断当前正在执行的优化过程mbStopGBA将会被置位,同时会有一定的时间// 使得该线程进行响应;而在开启一个新的全局优化进程之前 mbStopGBA 将会被置为False// 因此,如果被强行中断的线程退出时已经有新的线程启动了,mbStopGBA=false,为了避免进行后面的程序,所以有了上面的程序;// 而如果被强行终端的线程退出时新的线程还没有启动,那么上面的条件就不起作用了(虽然概率很小,前面的程序中mbStopGBA置位后很快mnFullBAIdx就++了,保险起见),所以这里要再判断一次if(!mbStopGBA){cout << "Global Bundle Adjustment finished" << endl;cout << "Updating map ..." << endl;mpLocalMapper->RequestStop();// Wait until Local Mapping has effectively stoppedwhile(!mpLocalMapper->isStopped() && !mpLocalMapper->isFinished()){//usleep(1000);std::this_thread::sleep_for(std::chrono::milliseconds(1));}// Get Map Mutexunique_lock<mutex> lock(mpMap->mMutexMapUpdate);// Correct keyframes starting at map first keyframe// 看上去是一个向量,但是要知道这个变量中其实只保存了第一个关键帧list<KeyFrame*> lpKFtoCheck(mpMap->mvpKeyFrameOrigins.begin(),mpMap->mvpKeyFrameOrigins.end());// 遍历全局地图中的所有关键帧while(!lpKFtoCheck.empty()){KeyFrame* pKF = lpKFtoCheck.front();const set<KeyFrame*> sChilds = pKF->GetChilds();cv::Mat Twc = pKF->GetPoseInverse();// 遍历当前关键帧的子关键帧for(set<KeyFrame*>::const_iterator sit=sChilds.begin();sit!=sChilds.end();sit++){KeyFrame* pChild = *sit;// 避免重复设置if(pChild->mnBAGlobalForKF!=nLoopKF){// (对于坐标系中的点的话)从父关键帧到当前子关键帧的位姿变换cv::Mat Tchildc = pChild->GetPose()*Twc;// (对于坐标系中的点)再利用优化后的父关键帧的位姿,转换到世界坐标系下 --  //? 算是得到了这个子关键帧的优化后的位姿啦?// 这种最小生成树中除了根节点,其他的节点都会作为其他关键帧的子节点,这样做可以使得最终所有的关键帧都得到了优化pChild->mTcwGBA = Tchildc*pKF->mTcwGBA;//*Tcorc*pKF->mTcwGBA;pChild->mnBAGlobalForKF=nLoopKF;}lpKFtoCheck.push_back(pChild);}// 更新当前关键帧的位姿pKF->mTcwBefGBA = pKF->GetPose();pKF->SetPose(pKF->mTcwGBA);// 从列表中移除lpKFtoCheck.pop_front();}// Correct MapPointsconst vector<MapPoint*> vpMPs = mpMap->GetAllMapPoints();// 遍历每一个地图点for(size_t i=0; i<vpMPs.size(); i++){MapPoint* pMP = vpMPs[i];if(pMP->isBad())continue;// NOTICE 并不是所有的地图点都会直接参与到全局BA优化中,但是大部分的地图点需要根据全局BA优化后的结果来重新纠正自己的位姿// 如果这个地图点直接参与到了全局BA优化的过程,那么就直接重新设置器位姿即可if(pMP->mnBAGlobalForKF==nLoopKF){// If optimized by Global BA, just updatepMP->SetWorldPos(pMP->mPosGBA);}else // 如故这个地图点并没有直接参与到全局BA优化的过程中,那么就使用器参考关键帧的新位姿来优化自己的位姿{// Update according to the correction of its reference keyframeKeyFrame* pRefKF = pMP->GetReferenceKeyFrame();// 说明这个关键帧,在前面的过程中也没有因为“当前关键帧”得到全局BA优化 //? 可是,为什么会出现这种情况呢? 难道是因为这个地图点的参考关键帧设置成为了bad?if(pRefKF->mnBAGlobalForKF!=nLoopKF)continue;// Map to non-corrected cameracv::Mat Rcw = pRefKF->mTcwBefGBA.rowRange(0,3).colRange(0,3);cv::Mat tcw = pRefKF->mTcwBefGBA.rowRange(0,3).col(3);// 转换到其参考关键帧相机坐标系下的坐标cv::Mat Xc = Rcw*pMP->GetWorldPos()+tcw;// Backproject using corrected camera// 然后使用已经纠正过的参考关键帧的位姿,再将该地图点变换到世界坐标系下cv::Mat Twc = pRefKF->GetPoseInverse();cv::Mat Rwc = Twc.rowRange(0,3).colRange(0,3);cv::Mat twc = Twc.rowRange(0,3).col(3);pMP->SetWorldPos(Rwc*Xc+twc);}}// 释放,使得LocalMapping线程重新开始工作mpLocalMapper->Release();cout << "Map updated!" << endl;}mbFinishedGBA = true;mbRunningGBA = false;} // 更新(几乎)所有的关键帧和地图点
}

REFERENCES

ORB_SLAM2代码阅读(4)——LoopClosing线程 https://blog.csdn.net/u014709760/article/details/90813846

ORB_SLAM2探秘 第三章 LoopClosing线程相关推荐

  1. Java7并发编程指南——第三章:线程同步辅助类

    Java7并发编程指南--第三章:线程同步辅助类 @(并发和IO流) Java7并发编程指南第三章线程同步辅助类 思维导图 项目代码 思维导图 项目代码 GitHub:Java7Concurrency ...

  2. C# 线程手册 第三章 使用线程 实现一个数据库连接池(实战篇)

    在企业级软件开发过程中,为了改善应用程序的性能需要通常使用对象池来控制对象的实例化.例如,在我们每次需要连接一个数据库时都需要创建一个数据库连接,而数据库连接是非常昂贵的对象.所以,为了节省为每次数据 ...

  3. C# 线程手册 第三章 使用线程

    概要 在之前章节,我们已经讨论过线程在开发多用户应用程序时扮演的重要角色.我们已经使用线程来解决一些重要的问题,比如让多个用户或者客户端在同一时间访问同一个资源.然而,在学习过程中我们忽略了一个问题, ...

  4. Java基础 - 坦克大战(第三章,线程基础与线程使用)

    文章目录 本章内容 - 多线程处理 绘制敌方坦克 线程基础 线程相关概念 程序(program) 进程 什么是线程 其他相关概念 单线程 多线程 并发 并行 获取当前电脑处理器(cpu)个数 Java ...

  5. C# 线程手册 第三章 使用线程 Monitor.TryEnter()

    Monitor 类的TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法类似.然而,它不像Enter()方法那样会阻塞执行.如果线程成功进入关键区域那么TryEnter( ...

  6. ORB_SLAM2代码阅读(4)——LoopClosing线程

    ORB_SLAM2代码阅读(4)--LoopClosing线程 1.说明 2.简介 3.检测回环 4.计算Sim3 4.1 为什么在进行回环检测的时候需要计算相似变换矩阵,而不是等距变换? 4.2 累 ...

  7. 深入理解Java虚拟机(周志明第三版)- 第十三章:线程安全与锁优化

    系列文章目录 第一章: 走近Java 第二章: Java内存区域与内存溢出异常 第三章: Java垃圾收集器与内存分配策略 并发处理的广泛应用是Amdahl定律代替摩尔定律[1]成为计算机性能发展源动 ...

  8. 【重识云原生】第三章云存储3.2节——SPDK方案综述

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  9. 《Linux内核设计与实现》读书笔记 第三章 进程管理

    第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种.我们拥有操作系统就是为了运行用户程序,因此,进程管理就是所有操作系统的心脏所在. 3.1进程 概念: 进程:处于执行期的程序.但不仅局限于 ...

  10. Windows Pe 第三章 PE头文件(上)

    第三章  PE头文件 本章是全书重点,所以要好好理解,概念比较多,但是非常重要. PE头文件记录了PE文件中所有的数据的组织方式,它类似于一本书的目录,通过目录我们可以快速定位到某个具体的章节:通过P ...

最新文章

  1. as 不显示gradle视图_Python构建RESTful网络服务[Django篇:基于类视图的API]
  2. Java游戏服务器系列之Netty详解
  3. arduino上ESP8266用Adafruit_SSD1306库驱动OLED屏
  4. 索引使用的限制条件,sql优化有哪些,数据同步问题(缓存和数据库),缓存优化
  5. 吴恩达 coursera ML 第十六课总结+作业答案
  6. printf多行输入格式
  7. Ubuntu版本介绍
  8. mysql免安装版配置
  9. 为iOS5设计消息通知
  10. Winter is coming,明星公司也裁员了...
  11. 互联网的高薪是否可持续,20万年薪的公务员到底香不香
  12. logback配置控制打印台异常信息_老板下了死命令,要把日志系统切换到Logback
  13. 【C/C++】size_t 数据类型
  14. 一线互联网公司Java高级面试总结
  15. Funcode学习笔记:写一个维护性高、扩展性强的框架【By Myself】【C++】
  16. 谷歌浏览器:下载,插件安装
  17. tf1.x版RandLA-Net源码解读
  18. Effective Java
  19. Power BI 学习三:数据整理和关系管理
  20. 2022腾讯云双十一服务器价格出炉来看看便宜不

热门文章

  1. ajax中xmlhttp.readyState==4 xmlhttp.status==200 是什么意思
  2. NOIP 2014 无线网络发射器选址
  3. 把博客园的博客导出为MovableType的文本格式
  4. An internal error occurred during: Launching MVC on Tomcat 7.x.
  5. Wijmo 5 与Breeze 的组合,及与METRONIC 的集成
  6. sphinx系列之中文分词LibMMSeg安装(三)
  7. ElasticSearch学习(五):数据导入之Logstash
  8. Redis 禁止使用耗时命令和时间复杂度为O(n)的命令
  9. c语言程序改错:求两个整数的最小公倍数,【C语言】求两个整数的最大公约数、最小公倍数...
  10. linux内核打印%us,linux-kernel-使用us计时器跟踪Linux内核,按功能(仅最大)