之前研究过一些回环检测的内容,首先要看的自然是用词袋回环的鼻祖和正当继承人(没有冒犯VINS和LDSO的意思)ORB-SLAM。下面是我的代码注释。因为代码都是自己手打的,不是在源码上注释的,所以一些我觉得不是太重要的被略过了,可能也会有一些typo.

ORB的回环策略比较偏向seq-SLAM的思路,通过共视帧打包的关系,比较每个包的相似值,而非只是关注单帧和单帧的匹配,这个思路是比较合适的,但是VINS和LDSO两位后来者用实际行动证明了我不太看中你这种思路,两个都没有用。后续我会介绍一些VINS和LDSO里的回环方法。

System Constructor

// System constructor
System::System()
{ mpVocabulary = new ORBVocabulary(); // -- create keyframe database
mpVocabulary->LoadFromTextFile(strVocFile); // load file mpLoopCloser = new LoopClosing(...);
mptLoopClosing = new thread(&ORB_SLAM2:LoopClosing::Run, mploopCloser); // a thread // -- mpTracker, mpLocalMapper, mpLoopCloser have to know each other's pointer
...
}

ComputeBow

在系统中的多个地方会计算Frame或者Keyframe的Bow,具体如下:

void Frame::ComputeBow()
{
if(mBowVec.empty())
{ // -- mDescriptors: cv::Mat
vector<cv::Mat> vCurrentDesc = Converter::toDescriptorVector(mDescriptors);
mpORBVocabulary->transform(vCurrentDesc, mBowVec, mFeatVec, 4);
}
} // -- LocalMapping::ProcessNewKeyFrame() 调用
// -- Tracking::CreatInitialMapMonocular() 调用,开始两个关键帧
// -- Tracking::TrackReferenceKeyFrame()
// -- Tracking::Relocalization()
void KeyFrame::ComputeBow()
{
if(mBowVec.empty() || mFeatVec.empty())
{
同上;
}
}

LoopClosing

void LoopClosing::Run()
{
whlie(1)
{
if(CheckNewKeyFrames()) // -- lock and check LoopKeyFrameQueue
{
if(DetectLoop())
{
if(ComputeSim3())
{
CorrectLoop();
}
}
}
}
}
bool LoopClosing::DetectLoop()
{
{ // -- 得到当前帧
unique_lock<mutex> lock(...); // -- lock
mpCurrentKF = mlpLoopKeyFrameQueue.front();
mlpLoopKeyFrameQueue.pop_front();
mpCurrentKF->SetNotErase(); // -- 暂时不销毁
} if(mpCurrentKF->mnId<mLastLoopKFid + 10) // -- 如果上次发生回环在10kf以内,回false
{
mpKeyFrameDB->add(mpCurrentKF); //加入到关键帧数据集里面
mpCurrentKF->SetErase(); //销毁咯
return false;
} // -- 计算BoW相似分
// -- 当前帧和当前帧的共视帧
// -- 如果分很低更新minScore
const 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(socre < minScore)
{
minScore = score;
}
} // -- 用currentKF和minScore找回环候选帧
vector<KeyFrame*> vpCanndidateKFs = mpKeyFrameDB->DetectLoopCandidates(mpCurrentKF, minScore); // -- 如果没有候选帧,回家吃饭
if(vpCandidateKFs.empty())
{
mpKeyFrameDB->add(mpCurrentKF);
mvConsistentGroup.clear();
mpCurrentKF->SetErase();
return false;
} //TO BE CONTINUED
...
}

KeyframeDatabase

初始化

KeyFrameDatabae::KeyFrameDatabase (const ORBVocabulary &voc) : mpVoc(&voc)
{
mvInvertedFile.resize(voc.size());
}

mvInvertedFile是一个按单次ID顺序存储的vector,每一个空间存储一个KeyFrame*的list。

add函数存储关键帧指针到每一个ID的对应list里面。

void KeyFrameDatabase::add(KeyFrame *pKF)
{
unique_lock<mutex> lock(mMutex);
for(auto vit = pKF->mBowVec.begin(), vend; ;vit++ )
{
mvInvertedFile[vit->first].push_back(pKF); // -- std::vector<list<KeyFrame*>>
}
}
void KeyFrameDatabase::erase(KeyFrame* pKF)
{
unique_lock<mutex> lock(mMutex);
for(auto vit = pKF->mBoWVec.begin(), vend;; vit++)
{
list<KeyFrame*> &lKFs = mvInvertedFile[vit->first]; // -- 获取存有该ID单词的list
for(auto lit=lKFs.begin(),lend;;lit++)
{
找到pKF然后删除这个迭代器;
}
}
}

看如果用这个数据集来寻找回环候选帧

vector<KeyFrame*> KeyFrameDatabase::detectLoopCandidate(KeyFrame* pKF, float minScore)
{
set<KeyFrame*> spConnectedKeyframes = pKF->GetConnectedKeyFrames();
list<KeyFrame*> lKFsSharingWords;//找到有相同单次的帧加入list // -- 找到所有和当前帧有相同word的帧
{
unique_lock<mutex> lock<mMutex>;
for(auto vit=pkF->mBowVec, vend; vit != vend; vit++) //遍历当前帧的words
{
list<KeyFrame*> &lKFs = mvInvertedFile[vit->first]; // 找到对应的list
for(auto lit = lKFs.begin()..; ;lit++) //遍历list
{
KeyFrame* pKFi = *lit;
if(pKFi->mnLoopQuery!=pKF->mnId)//如果这个关键帧的**最近询问**不是当前帧-111
{
pKFi->mnLoopWords = 0;// 第一次找到单词
if(!spConnectedKeyFrames.count(pKFi))//查询不是当前帧的共视帧
{
pKFi->mnLoopQuery = pKF->mnId;//和-111遥相呼应,不重复操作
lKFSSharingWords.push_back(pKFi);//加入set
}
}
pKFi->mnLoopWords++;//记录一共共有的单词个数
}
}
} if(lKFsSharingWords.empty()) // -- 没有任何帧有相同的单词,GG
{
return vector<KeyFrame*>();
} list<pair<float, KeyFrame*>> lScoreAndMatch; int maxCommonWords = 0;
{
//遍历找到共享单词最多的更新给maxCommonWords;
}
int minCommonWords = maxCommonWords * .8f; // 至少是最多共视的80%
int nscore = 0; //轮询lKFsSharingWords
//计算相似得分,保留得分比minScore高的
for(auto lit = lKFsSharingWords.begin(), lend; ; lit++)
{
KeyFrame* pKFi = *lit;
if(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)); //记录得分和大于minScore的回环帧
}
}
} if(lScoreAndMatch.empty()) // -- 没有得分够的,GG
{
return vector<KeyFrame*>();
} list<pair<float, KeyFrame*>> lAccScoreAndMatch;
float bestAccScore = minScore; //翻云覆雨,用共视估计得分
for(auto it = lScoreAndMatch.begin(), itend; ; it++) //遍历lScoreAndMatch
{
KeyFrame* pKFi = it->second;
vector<KeyFrame*> vpNeighs = pKFi->GetBestCovisibilityKeyFrames(10); // -- 找10个共视最多的帧 这里Neigh应该是想说Neighbor
float bestScore = it->first;
float accScore = it->first; // -- accumulate累计的分数
KeyFrame* pBestKF = pKFi;
for(auto vit = vpNeights.begin(), vend; ;vit++)
{
KeyFrame* pKF2 = *vit;
if(pKF2->mnLoopQuery == pKF->mnId && pKF2->mnLoopWords > minCommonWords) //共视的帧也和当前帧有很强的BoW的反应
{
accScore += pKF2->mLoopScore;
if(pkF2->mLoopScore > bestScore)
{
pBestKF = pKF2; //更新最强回环帧
bestScore = pKF2->mLoopScore;
}
}
}
lAccScoreAndMatch.push_back(make_pair(accScore, pBestKF)); //把之前算的每一帧的,以最近10个共视帧的分时累计分数,并选出10帧中个分数最高的做pointer
if(accScore > bestAccScore) // -- 更新累计最高分
{
bestAccScore = accScore;
}
} float minScoreToRetain = 0.75f * bestAccSCore; set<KeyFrame*> spAlreadyAddedKF;
vector<KeyFrame*> vpLoopCandidates;
vpLoopCandidates.reserve(lAccScoreAndMatch.size()); for(auto it = lAccScoreAndMatch.begin...;;) //遍历lAccScoreAndMatch
{
if(it->first > minScoreToRetain)
{
KeyFrame* pKFi = it->second;
if(!spAlreadyAddedKF.count(pKFi)) // -- 滤除一样的帧
{
vpLoopCandiates.push_back(pKFi);
spAlreadyAddedKF.insert(pkFi);
}
}
} return vpLoopCandidates;
}

Code Reading: ORB-SLAM回环检测源码阅读+注释的更多相关文章

  1. ​综述 | SLAM回环检测方法

    本文作者任旭倩,公众号:计算机视觉life成员,由于格式原因,公式显示可能出问题,建议阅读原文链接:综述 | SLAM回环检测方法 在视觉SLAM问题中,位姿的估计往往是一个递推的过程,即由上一帧位姿 ...

  2. tinyhttp源码阅读(注释)

    这里就不细述了,代码很简单. 其实现的功能比较若,可以做一个参考. 因为其通过文件的权限位来判断是否是一个CGI脚本,所以在权限位不对的情况下会判断不正确.例如我将这个目录放置在NTFS分区,所有的文 ...

  3. 一个基于深度学习回环检测模块的简单双目 SLAM 系统

    转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12634631.html 写在前面 最近在搞本科毕设,关于基于深度学 ...

  4. 浅谈SLAM的回环检测技术

    什么是回环检测? 在讲解回环检测前,我们先来了解下回环的概念.在视觉SLAM问题中,位姿的估计往往是一个递推的过程,即由上一帧位姿解算当前帧位姿,因此其中的误差便这样一帧一帧的传递下去,也就是我们所说 ...

  5. ORB-SLAM(六)回环检测

    上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...

  6. DLoopDetector回环检测算法

    词袋模型是一种文本表征方法,它应用到计算机视觉领域就称之为BoF(bag of features),通过BoF可以把一张图片表示成一个向量.DBoW2是一个视觉词袋库,它提供了生成和使用词典的接口,但 ...

  7. segMatch:基于3D点云分割的回环检测

    该论文的地址是:https://arxiv.org/pdf/1609.07720.pdf segmatch是一个提供车辆的回环检测的技术,使用提取和匹配分割的三维激光点云技术.分割的例子可以在下面的图 ...

  8. 【C++】链表回环检测

    //链表回环检测问题 #include<iostream> #include<cstdlib> using namespace std; ; struct node { int ...

  9. VINS 回环检测与全局优化

    回环检测 VINS回环检测与全局优化都在pose_graph.cpp内处理.首先在pose_graph_node加载vocabulary文件给BriefDatabase用,如果要加载地图,会loadP ...

随机推荐

  1. Jenkins 配置git

    点击"新建任务"创建一个自用风格的项目 点击"源码管理",选择 git 系统管理 --> Global Tool Configuration<为访问 ...

  2. Spark大型电商项目实战-及其改良(3) 分析sparkSQL语句的性能影响

    之前的运行数据被清除了,只能再运行一次,对比一下sparkSQL语句的影响 纯SQL的时间 对应时间表 th:first-child,.table-bordered tbody:first-child ...

  3. ASCII对应码表-键值(完整版)

    ASCII对应码表-键值(完整版) Bin (二进制) Oct (八进制) Dec (十进制) Hex (十六进制) 缩写/字符 解释 0000 0000 00 0 0x00 NUL(null) 空字 ...

  4. Bugku-CTF之cookies欺骗

    Day22 cookies欺骗 http://123.206.87.240:8002/web11/ 答案格式:KEY{xxxxxxxx} 本题要点:cookie欺骗.base64编码传参  

  5. Django视图层

    本文目录 1 视图函数 2 HttpRequest对象 3 HttpResponse对象 4 JsonResponse 5 CBV和FBV 6 简单文件上传 回到目录 1 视图函数 一个视图函数,简称 ...

  6. uirecorder 启动webdriver服务报错

    在安装好uirecorder后,执行起来是各种错误. 不是少这个就是缺那个,也是因为自己对自动化测试知识太匮乏. 导致刚开始走自动化测试绕了很多弯路,报个错都不知所措.后来才知道要多看ERROR后面的 ...

  7. python 之生成器的介绍

    # 用生成器(generators)方便地写惰性运算 def double_numbers(iterable): for i in iterable: yield i + i # 生成器只有在需要时才 ...

  8. Unity日常记录-本地保存未来时间实现倒计时

    本地保存未来时间实现倒计时 TimeTool工具类:获取当前时间.未来时间.两时间差 using System; using UnityEngine; public class TimeTool { ...

  9. Unity外包 UE4外包 项目案例平台开通通知

    长年承接Unity外包 UE4外包,大家好,本公司团队已将案例上传至专门的案例官网. 欢迎联系我们索取,谢谢! 有项目外包请联系QQ:372900288 索取案例.

  10. Web浏览器与Web服务器之间的通信过程

     HTTP通信机制是在一次完整的HTTP通信过程中,Web浏览器与Web服务器之间将完成下列7个步骤:1:建立TCP连接 在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连 ...