Code Reading: ORB-SLAM回环检测源码阅读+注释
之前研究过一些回环检测的内容,首先要看的自然是用词袋回环的鼻祖和正当继承人(没有冒犯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回环检测源码阅读+注释的更多相关文章
- 综述 | SLAM回环检测方法
本文作者任旭倩,公众号:计算机视觉life成员,由于格式原因,公式显示可能出问题,建议阅读原文链接:综述 | SLAM回环检测方法 在视觉SLAM问题中,位姿的估计往往是一个递推的过程,即由上一帧位姿 ...
- tinyhttp源码阅读(注释)
这里就不细述了,代码很简单. 其实现的功能比较若,可以做一个参考. 因为其通过文件的权限位来判断是否是一个CGI脚本,所以在权限位不对的情况下会判断不正确.例如我将这个目录放置在NTFS分区,所有的文 ...
- 一个基于深度学习回环检测模块的简单双目 SLAM 系统
转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12634631.html 写在前面 最近在搞本科毕设,关于基于深度学 ...
- 浅谈SLAM的回环检测技术
什么是回环检测? 在讲解回环检测前,我们先来了解下回环的概念.在视觉SLAM问题中,位姿的估计往往是一个递推的过程,即由上一帧位姿解算当前帧位姿,因此其中的误差便这样一帧一帧的传递下去,也就是我们所说 ...
- ORB-SLAM(六)回环检测
上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...
- DLoopDetector回环检测算法
词袋模型是一种文本表征方法,它应用到计算机视觉领域就称之为BoF(bag of features),通过BoF可以把一张图片表示成一个向量.DBoW2是一个视觉词袋库,它提供了生成和使用词典的接口,但 ...
- segMatch:基于3D点云分割的回环检测
该论文的地址是:https://arxiv.org/pdf/1609.07720.pdf segmatch是一个提供车辆的回环检测的技术,使用提取和匹配分割的三维激光点云技术.分割的例子可以在下面的图 ...
- 【C++】链表回环检测
//链表回环检测问题 #include<iostream> #include<cstdlib> using namespace std; ; struct node { int ...
- VINS 回环检测与全局优化
回环检测 VINS回环检测与全局优化都在pose_graph.cpp内处理.首先在pose_graph_node加载vocabulary文件给BriefDatabase用,如果要加载地图,会loadP ...
随机推荐
- JavaScript--鼠标滚动改变图片大小
鼠标滚动改变图片的大小: 原理:当鼠标滚动时改变了zoom的值: <!DOCTYPE HTML> <html> <head> <title>通过鼠标滚轮 ...
- linux磁盘命令-lsblk显现磁盘阵列分组
linux磁盘命令-lsblk显现磁盘阵列分组 lsblk(list block devices)能列出系统上所有的磁盘. lsblk [-dfimpt] [device] 选项与参数: -d :仅列 ...
- 20190410Linux中磁盘管理及LVM(week2day1)
Linux磁盘管理及LVM讲解(week2_day2) 硬盘接口 从整体的角度上,硬盘接口分为IDE.SATA.SCSI和SAS四种,IDE接口硬盘多用于家用产品中,也部分应用于服务器,SCSI接 ...
- CentOS7操作Redis4.0
单机安装 1. 从官网下载 redis-4.0.10.tar.gz 到本地,然后上传到VMware虚拟机上,存放地址随意. 2. 解压: tar -zxvf redis-4.0.10.tar.gz 3 ...
- 0x16 Tire
参考链接:https://www.cnblogs.com/TheRoadToTheGold/p/6290732.html 题目链接:https://www.acwing.com/problem/con ...
- Cent OS6下SS+BBR+改SSH端口
SS+BBR+改SSH端口 (一)搭建SS wget --no-check-certificate -O shadowsocks-libev.sh https://raw.githubusercont ...
- Elasticsearch NEST 控制字段名称命名格式
在使用NEST操作elasticsearch时,字段名会根据model中字段,默认为首字母小写. 如果需要调整NEST的默认明个规则,可以在 ConnectionSettings中进行自定义. var ...
- C# 简单粗暴写日志
public static void WriteLog(string text) { string path = AppDomain.CurrentDomain.BaseDirectory; path ...
- unittest用例执行的顺序
unittest在执行用例(test_xxx)时,并不是按从上到下的顺序执行,有特定的顺序. 示例: import unittest class TestBdd(unittest.TestCase): ...
- HDU 1257 最少拦截系统(思路题)
Problem Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高 ...