orb-slam2代码详解之system
目录
系统在Orb-SLAM2中的功能描述如下:
详细阐述了该系统的程序架构设计;
对System类的功能进行简要分析;
请详细描述System.h文件中所定义的System类;
请详细描述System.h文件中所定义的System类的相关属性与方法;
-
系统构建函数
-
接收输入参数
- 解析输入的文件路径信息
- 创建必要的线程与数据库连接
-
附录内容
-
其中涉及的关键技术:其中最为重要的组成部分是FileStorage类。
-
orb词汇表:其中核心功能模块包括OrbVocabulary模块。
-
帧数据库:在实现过程中需要用到KeyFrameDatabase这一技术基础。
-
对象构造函数:其核心逻辑由一个自定义的对象构造函数完成。
-
核心运行逻辑:整个系统的运行依赖于run成员函数的有效实现。
-
数据传输机制:不同线程之间的通信机制则由专门设计的数据传输接口负责。
mono_kitti.cc中main函数里有创建System对象的语句:
ORB_SLAM2::System SLAM(argv[1],argv[2],ORB_SLAM2::System::MONOCULAR,true);
AI生成项目cpp
system在orb-slam2中的作用
1)读取ORB字典,为后期的回环检测做准备;
2)创建关键帧数据库KeyFrameDatabase,用于存放关键帧相关数据;
3)启动Tracking子线程。实际上,在主程序流程内运行的其实是Tracking子线程;可以简单地理解为这个主程序流程就是这个Tracking子线程。
4)初始化并启动LocalMapping线程;
5)初始化并启动LoopClosing线程;
6)初始化并启动窗口显示线程mptViewer;
7)在各个线程之间分配资源,方便线程彼此之间的数据交互。
system程序基本结构

system类的基本介绍
system.h
在System.h文件中定义了七个关键类:包括Viewer、FrameDrawer、Map、Tracking、LocalMapping以及LoopClosing等声明,并附带System类的完整定义。这些组件共同构成了一个完整的系统架构。
class Viewer;
class FrameDrawer;
class Map;
class Tracking;
class LocalMapping;
class LoopClosing;
class System;
AI生成项目cpp
system类
该类中的public成员包括一个传感器枚举以及一系列相关功能:其主要功能包括初始化、读取数据以及断开连接等操作
// Input sensor 定义eSensor类型的sensor变量
enum eSensor{
MONOCULAR=0,
STEREO=1,
RGBD=2
};
AI生成项目cpp
0, 1, 2各自代表不同的传感器类型。
枚举数据类型是由程序员自定义的一种数据类型,在其定义中与每个值相关联的是一个特定的一组命名整数常量。
因此被称作枚举类型的原因在于,在数据类型的定义中包含了这些命名整数常量作为其合法值。
以下是枚举类型声明的示例:
enum Roster {Tom, Sharon, Bill, Teresa, John};
AI生成项目cpp
该语句将定义一个命名为 Roster 的数据类型。由于 "enum" 在 C++ 中是关键字,因此它必须以小写字母形式存在.与 Roster 类型相关联的命名整数常量被称为枚举常量,而 Roster 类型的变量只能与这些枚举常量中的一个相关联.默认情况下,编译器通常会将第一个枚举常量设为 0,随后依次递增.需要注意的是,在这种情况中, enum 语句并未定义任何变量,仅仅是为了说明数据类型的构建.
② System构造函数
System(const string &strVocFile, const string &strSettingsFile, const eSensor sensor, const bool bUseViewer = true);
AI生成项目cpp
在编程环境中如何实现基于视觉的实时定位与建图算法?这是一个关键步骤:首先需要指定词包路径来构建一个SLAM(Simultaneous Localization and Mapping)系统;然后提供对应的YAML配置文件;最后指定传感器类型,并启动viewer进程以观察结果。
③ Tracking函数
//双目视觉传入左眼视觉图像,右眼视觉图像,以及时间戳
cv::Mat TrackStereo(const cv::Mat &imLeft, const cv::Mat &imRight, const double ×tamp);
//RGB-D传入彩色图和深度图以及时间戳
cv::Mat TrackRGBD(const cv::Mat &im, const cv::Mat &depthmap, const double ×tamp);
cv::Mat TrackMonocular(const cv::Mat &im, const double ×tamp);
AI生成项目cpp
基于不同类型的输入(包括RGB图像和灰度图像),该函数能够处理来自多种传感器的数据源(最终读入的数据将统一转换为gray-scale图像),并计算出相机的姿态信息pose(即 camera pose)。
④ 定位模式函数
void ActivateLocalizationMode();
void DeactivateLocalizationMode();
AI生成项目cpp
调用前者终止mapping线程,开启定位模式,调用后者重启mapping线程。
⑤ 重启与终止函数
void Reset();
void Shutdown();
AI生成项目cpp
清除映射、停止全部线程,在相机轨迹被保存之前调用此函数。
⑥ SaveTrajectory 函数
void SaveTrajectoryTUM(const string &filename);
//二者相似
void SaveKeyFrameTrajectoryTUM(const string &filename);
void SaveTrajectoryKITTI(const string &filename);
AI生成项目cpp
将相机轨迹存储为对应的数据集格式,在调用此函数之前先关闭SLAM系统; mono_kittti中save函数使用的是SaveKeyFrameTrajectoryTUM这一功能;看起来似乎仅适用于TUM数据集;然而,并非只有单一类型传感器才可以适用
system构造函数
传入参数
/** * strVocFile 为词典文件
* strSettingsFile 为设置配置文件
* sensor sensor类型,单目、双目和RGBD
* bUseViewer 是否进行相机位姿和图片帧显示
*/
//首先将参数传递到成员变量中,msensor是private的成员变量和sensor数据类型相同,此时传入为mono
//mpViewer是System类的隐含成员变量,Viewer类指针,这里赋空。
System::System(const string &strVocFile, const string &strSettingsFile, const eSensor sensor,
const bool bUseViewer):mSensor(sensor), mpViewer(static_cast<Viewer*>(NULL)), mbReset(false),mbActivateLocalizationMode(false),
mbDeactivateLocalizationMode(false)
AI生成项目cpp

读取传进来的文件路径
// Output welcome message 显示程序信息,以及此时设定的传感器
cout << endl <<
"ORB-SLAM2 Copyright (C) 2014-2016 Raul Mur-Artal, University of Zaragoza." << endl <<
"This program comes with ABSOLUTELY NO WARRANTY;" << endl <<
"This is free software, and you are welcome to redistribute it" << endl <<
"under certain conditions. See LICENSE.txt." << endl << endl;
cout << "Input sensor was set to: ";
if(mSensor==MONOCULAR)
cout << "Monocular" << endl;
else if(mSensor==STEREO)
cout << "Stereo" << endl;
else if(mSensor==RGBD)
cout << "RGB-D" << endl;
AI生成项目cpp

//Check settings file 检查配置文件是否打开
//将YAML文件打开,进行读操作
cv::FileStorage fsSettings(strSettingsFile.c_str(), cv::FileStorage::READ);
if(!fsSettings.isOpened())
{
cerr << "Failed to open settings file at: " << strSettingsFile << endl;
exit(-1);
}
//Load ORB Vocabulary 加载ORB词包
//ORB中vocabulary的作用是特征空间的划分和Bag-of-Words Vector的计算。
cout << endl << "Loading ORB Vocabulary. This could take a while..." << endl;
//使用默认的构造函数,使用无参化构造方法构造一个对象
//使用mpVocabulary指针指向在堆区存储该对象的位置
//ORBVocabulary类是 typedef 成的,来源于 DBoW2 的TemplatedVocabulary类 也就是子类继承
//DBoW2能够快速稳定地计算两幅图像之间的相似程度。
mpVocabulary = new ORBVocabulary();
//使用指针调用类内函数加载词包,并返回布尔变量表示是否成功加载
bool bVocLoad = mpVocabulary->loadFromTextFile(strVocFile);
//判断是否成功
if(!bVocLoad)
{
cerr << "Wrong path to vocabulary. " << endl;
cerr << "Falied to open at: " << strVocFile << endl;
exit(-1);
}
cout << "Vocabulary loaded!" << endl << endl;
AI生成项目cpp

初始化各种线程和数据库
①用词袋初始化关键帧数据库( 用于 重定位 和 回环检测 )
②初始化一个Map类,该类用于存储指向所有 关键帧 和 地图点 的指针
③初始化画图工具,用于可视化。
④创建Tracking线程,并与主线程通过this指针进行引用(仅进行初始化而不立即启动;其启动安排在main函数中执行TrackMonocular())。
⑤初始化 Local Mapping线程 并启动(这里mSensor传入MONOCULAR)
⑥初始化 Loop Closing线程 并启动(这里mSensor传入的不是MONOCULAR)
初始化该Viewer进程并启动它,并且采用了this指针机制;同时为Tracking线程配置了相应的Viewer设置。
⑧三个线程每两个线程之间设置指针
//Create KeyFrame Database 创建关键帧数据库
//有两个重要功能,一是计算在回环检测时计算候选关键帧集合,二是在重定位时计算候选关键帧
mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);
//Create the Map 创建地图对象
//增删关键帧和地图点
mpMap = new Map();
//Create Drawers. These are used by the Viewer 创建两个显示窗口
mpFrameDrawer = new FrameDrawer(mpMap);
mpMapDrawer = new MapDrawer(mpMap, strSettingsFile);
//Initialize the Tracking thread
//(it will live in the main thread of execution, the one that called this constructor)
//这里初始化的Tracking线程是在main线程中的(其实也就是main线程),所以不需要调用new thread来创建
mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer,
mpMap, mpKeyFrameDatabase, strSettingsFile, mSensor);
//Initialize the Local Mapping thread and launch
//初始化LocalMapping对象,并开启LocalMapping线程
mpLocalMapper = new LocalMapping(mpMap, mSensor==MONOCULAR);
//子线程在创建时启动,使用功能std::thread类创建线程对象
//std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数。
//run 调用loopclosing里面函数
mptLocalMapping = new thread(&ORB_SLAM2::LocalMapping::Run,mpLocalMapper);
//Initialize the Loop Closing thread and launch
//初始化LooClosing对象,并开启LoopClosing线程
mpLoopCloser = new LoopClosing(mpMap, mpKeyFrameDatabase, mpVocabulary, mSensor!=MONOCULAR);
mptLoopClosing = new thread(&ORB_SLAM2::LoopClosing::Run, mpLoopCloser);
//Initialize the Viewer thread and launch
//是否显示图像帧和相机位姿的窗口
if(bUseViewer)
{
//初始化显示窗口对象,并启动线程用于显示图像和地图点
mpViewer = new Viewer(this, mpFrameDrawer,mpMapDrawer,mpTracker,strSettingsFile);
mptViewer = new thread(&Viewer::Run, mpViewer);
mpTracker->SetViewer(mpViewer);
}
//Set pointers between threads
//给各个线程分配资源
//以下变量设置是为了在各个线程中可以访问其他线程中的数据,方便线程之间的数据交互
mpTracker->SetLocalMapper(mpLocalMapper);
mpTracker->SetLoopClosing(mpLoopCloser);
mpLocalMapper->SetTracker(mpTracker);
mpLocalMapper->SetLoopCloser(mpLoopCloser);
mpLoopCloser->SetTracker(mpTracker);
mpLoopCloser->SetLocalMapper(mpLocalMapper);
AI生成项目cpp

附录
FileStorage类
FileStorage类负责处理不同种类的OpenCV数据结构的数据,并将其转换为XML或YAML格式文件。此外还可以将其他类型的数值数据转换成这两种格式文件的形式。
cv::FileStorage(const string& source, int flags, const string& encoding=string());
AI生成项目cpp
参数:
文件名 source 用于存储或读取数据(字符串),其扩展名 (.xml 或 .yml/.yaml) 根据扩展名确定文件格式类型。
flags – 操作模式,包括:
通过打开指定的Read位置执行读取运算。
通过打开指定的Write位置执行存储运算。
通过打开指定的Append位置执行追加运算。
Memory组件负责从输入源读取数据并存储至内部缓存中(该功能由释放方法触发)。
编码类型决定文件的具体编码格式。目前系统不支持UTF-16 XML编码格式,请采用标准的8位字符编码方案。
该文介绍了OpenCV库中与FileStorage类相关的数据存取操作过程及其实现原理。具体来说:首先创建并初始化一个FileStorage对象;然后调用read方法获取文件中的所有图像数据,并将其存储在一个列表中;接着使用write方法将这些图像数据保存到指定路径;之后通过remove方法删除指定路径下的所有文件;最后使用rename方法将其中一个文件重命名为目标名称进行保存。文中通过详细的代码示例展示了各个操作的具体实现方式,并对每一步骤进行了简要说明
orb vocabulary
为减少积累误差,现普遍采用回环检测技术,并借助后端优化纠正整个回路的姿态估计偏差.为实现回环检测,SLAM系统需回答:同一个场景是否曾出现过?尤其是在可能存在平移加旋转的情形下,判断当前帧与历史帧是否源自同一场景.该问题可归类于基于内容的图像检索领域.ORB-SLAM采用将图像分解为多个局部描述符并以固定长度编码成全局特征的方式处理,而Vocabulary Tree则作为一种高效的数据结构用于局部描述符的量化与索引.
原文链接 : 为什么要加载orb vocabulary
OrbVocabulary部分
DBow2是一个开源C++工具,在处理图像时能够生成词袋模型(bag-of-words)表示。该库基于特征提取算法(用户可以选择不同的算法)生成图像的特征描述子,并采用特定方法将这些描述子聚类成"词汇"单元,并以树状结构构建词汇树。随后会通过词汇树计算图像的特征向量表示,并计算图像间的相似性。
该FORB类继承自FClass这一虚类对象,并提供若干关于描述子操作的功能实现。具体而言:
(1)meanValue()函数:用于计算描述子集合中的中间值;
这是一个衡量特征相似性的重要指标;
(2)distance()函数:用于计算两个描述子之间的距离度量。
OrbVocabulary类
查看代码,发现 OrbVocabulary是这样定义的
typedef DBoW2::TemplatedVocabulary<DBoW2::FORB::TDescriptor, DBoW2::FORB> OrbVocabulary;
AI生成项目cpp
TemplatedVocabulary被定义为一个字典模板类,请参考TemplatedVocabulary.h文件以获取其具体实现。该模板类指定了字典类的一系列通用操作包括构造过程、数据存储与恢复机制以及如何将图像或特征转换为对应的单词表示形式。在OrbVocabulary模块中接收的类型包括DBoW2::FORB::TDescriptor以及DBoW2::FORB两类类型体。其中前者专门用于描述子类型的定义实际上是在OpenCV库框架下完成这一功能的具体实现;而后者部分内容在上一篇博客中已经进行了详细阐述其实际作用在于处理特定的数据格式并将其映射到相应的数据结构之中。
KeyFrameDatabase
该类内容较为简略,并包含两大核心功能:其一是,在回环检测过程中生成候选关键帧集合;其二是,在重定位阶段完成候选关键帧的计算。
变量:
std::vector<list<KeyFrame*> > mvInvertedFile
AI生成项目cpp
由一系列数据构成的一个向量中包含了一系列的单词。每个单词都对应一个链表结构,在其相关的关键帧被组织在各自的链表中。
关键函数:
vector<KeyFrame*> KeyFrameDatabase::DetectLoopCandidates(KeyFrame* pKF, float minScore)
AI生成项目cpp
原文链接:KeyFrameDatabase类
线程类构造函数
std::thread类中的成员函数用于接收外部调用的对象。其构造函数采用可变数量模板实现方式,在这种机制下能够灵活接收任意数量的实际传递值。具体而言,在这些传递值中第一个位置接受的是启动线程所需的入口函数体其余位置则依次接收后续多个值并将其传递给该线程启动所需的入口函数体
在C语言中传递函数时,并非以函数指针的形式进行操作(原生C语言中的确是以传统函数指针实现对外部函数的间接调用)。自C++11起首次引入了支持对象作为可调用实体的概念(callable objects),这一变化使得面向对象编程更加灵活与强大。总体而言,在C++11及以上的版本中,默认支持以下几类具体的 callable object形式:
- function pointer
- 实现了运算符重载功能的对象类即为仿函数
- lambda expression (anonymous function)
- std::function 用于绑定与线程相关联的任务.
run成员函数
运行loopclosing内部的函数 CheckNewKeyFrames()以判断是否需要插入关键帧,在存在新的关键帧时继续执行后续操作
DetectLoop(),布尔型,判断是否存在闭环,如果有,继续
l 每一个候选帧都将由与其自身相关联的关键帧共同构建一个"子组spCandidateGroup"
判断每个子候选组的关键帧是否包含于连续组中。其中若存在,则nCurrentConsistency增加1,并将该子候选组归并至当前连续组vCurrentConsistentGroups中。
当nCurrentConsistency不小于3时,则表示"子候选组"所对应的候选帧通过关卡,并成功加入"mvpEnoughConsistentCandidates"序列
ComputeSim3() 计算sim3
CorrectLoop() 闭环校正
ResetIfRequested() 检测是否结束

线程间数据的传递
实现对其他线程数据的更新途径:通过私有成员变量(使用指针操作),使得所有Tracking子线程的数据进行更新;其中仅用于其他两个相关联的线程进行数据更新
//Other Thread Pointers
///局部建图器句柄
LocalMapping* mpLocalMapper;
///回环检测器句柄
LoopClosing* mpLoopClosing;
AI生成项目cpp
获取函数:私有成员函数
//设置局部建图器
void Tracking::SetLocalMapper(LocalMapping *pLocalMapper)
{
mpLocalMapper=pLocalMapper;
}
//设置回环检测器
void Tracking::SetLoopClosing(LoopClosing *pLoopClosing)
{
mpLoopClosing=pLoopClosing;
}
AI生成项目cpp

通过Tracking机制获取关键帧数据,并基于两个成员变量动态更新LocalMapping和LoopClosing线程的信息状态。同时查看其他两个线程的执行情况后会发现它们也能提供必要的数据支持。这些数据变化最终会直接影响到Tracking过程中的数据更新机制
