之前研究过一些回环检测的内容,首先要看的自然是用词袋回环的鼻祖和正当继承人(没有冒犯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. java.sql.SQLException: ORA-28000: the account is locked

    解决方式:参考博客https://blog.csdn.net/java280580332/article/details/70756533

  2. [flutter+dart] windows7下开发环境的安装与配置

    前言 博主是做嵌入式的,参加工作时间也不久,而且是非科班出身,之前从未接触过移动开发.最近了解到了flutter框架和dart语言,想作为第二语言学习一下,因此会从最基础的环节开始,以此博客作为记录, ...

  3. C# 创建数据库和表

    using (SqlCommand command2 = new SqlCommand("CREATE TABLE bases(id int IDENTITY(1, 1) PRIMARY K ...

  4. ROS之坑

    我使用ROS建图的时候,用的是Kinect的深度信息转换成laser scan,Rviz仿真环境调用出laser scan信息的时候显示如下错误: Transform [sender=unknown_ ...

  5. Docker+Teamcity+Maven+SVN搭建持续集成环境

    这是使用Jenkins作为CI/CD工具,Teamcity的作用是相同的.

  6. loj 6008 餐巾计划 - 费用流

    题目传送门 传送门 题目大意 (经典题还不知道题意?) 容易想到需要把未使用的餐巾和已经使用的餐巾分开. 设$X_i$表示第$i$天已经的使用餐巾的点,设$Y_i$表示第$i$天还未使用的餐巾的点 我 ...

  7. win10配置java开发环境

    安装java(JDK) Oracle官网下载地址 选择JavaSE > Downloads 选择最新版本 > Download Accept License Agreement 选择要下载 ...

  8. 单元测试系列之十:Sonar 常用代码规则整理(二)

    摘要:帮助公司部署了一套sonar平台,经过一段时间运行,发现有一些问题出现频率很高,因此有必要将这些问题进行整理总结和分析,避免再次出现类似问题. 作者原创技术文章,转载请注明出处 ======== ...

  9. java解压多层目录中多个压缩文件和处理压缩文件中有内层目录的情况

    代码: package com.xiaobai; import java.io.File; import java.io.FileOutputStream; import java.io.IOExce ...

  10. 纸小墨ink简洁主题story爱上你的故事

    主题介绍 为纸小墨写的一款主题,该主题移植自Yumoe github地址:ink-theme-story Demo ink-theme-story 主题的一些食用说明 菜单 标题旁边有一个 · 字符, ...