ORB-SLAM2代码阅读
-
- Examples目录中分别存储了基于单目视图、双目视图以及RGBD视图的实例程序
- include目录下存放的是头文件
- ORB-SLAM2程序可以作为库直接调用
- src目录中存放的是与include目录对应的源代码
- Thirdparty目录中存储了依赖使用的第三方库资源
- Vocabulary目录中包含了用于回环检测阶段的视觉词典集合
- build.sh构建脚本在本地环境配置无误后即可执行
System.cc
函数体system()用于为SLAM系统设置参数,包括ORB字典路径、配置路径以及传感器类型。
首先判断传感器类型是单目、双目还是RGB-D。
2. 然后通过cv::FileStorage fsSetting()传入配置文件路径的读取配置文件,并由isOpened()判断文件是否存在。
3. 若存在,则通过ORBVocabulary()加载ORB字典到mpVocabulary变量,然后通过loadFromTextFile()判断加载是否成功。
4. 接着创建地图Map(),创建视图FrameDrawer()用来显示创建的地图,创建画图器MapDrawer()。
5. 初始化三个线程Tracking()、LocalMapping()、LoopClosing()。并通过thread()开启local mapping和loop closing线程。同时初始化显示线程Viewer()并启动该线程。
6. 最后通过setLocalMapper()等方法设置线程之间的指针。
首先确认输入传感器类型是否对应相应的Track方法。随后进行模型验证,在当前状态下如果是mbActivateLocalizationMode,则会暂停1秒直至完成局部建模;否则若处于mbDeactivateLocalizationMode,则会重启本地建模进程。随后分别通过GrabImageStereo(imRectLeft,imRectRight)、GrabImageRGBD(im,depthmap)、GrabImageMonocular(im)启动对应的跟踪子线程以维持系统正常运行。
如果当前帧追踪失败则丢弃,继续追踪下一帧——追踪成功则将当前参考帧设为关键帧——追踪成功但是关键帧不好,则获取当前关键帧相对于上一帧的位姿,并将上一帧设为关键帧,依次不断的判断关键帧的质量,直到选取合适的关键帧——将关键帧的位姿乘第一帧相对于世界坐标的位姿得到关键帧相对于世界坐标的位姿——在将关键帧相对于世界坐标的位姿乘当前帧相对于关键帧的位姿得到当前帧相对于世界坐标的位姿——取当前帧相对于世界坐标位姿的R和t——转换为四元数——存入文件中
SaveKeyFrameTrajectoryTUM()方法求数据集中每一关键帧的位姿。
rgb_tum.cc
LoadImages()加载图像——判断rgb图是否存在——判断rgb图与depth图数量是否对应相同。
2. ORB_SLAM2::System SLAM()初始化,创建SLAM系统,并初始化各个线程。
3. 遍历每一对RGB图和depth图【读取RGB图和depth图,读取时间戳(vTimestamps存储了时间戳,实际上就是存储了数据文件的每一幅图像的采集时间)——判断文件是否读取成功——SLAM.TrackRGB()将图片转给system处理,在这里从主线程进入tracking线程——计算track一帧的时间——计算两幅图像的采集时间之差,即采集一帧图像的时间——比较追踪时间和采集时间,通过休眠保持同步运行】
4. 关闭系统对象——计算追踪总时间以及时间中位数和平均数——SaveTrajectoryTUM()保存相机轨迹和SaveKeyFrameTrajectoryTUM()关键帧轨迹
该函数负责加载图像,并从associate.txt文件中读取图像信息。每条记录包含四个字段:RGB通道采集时间、RGB格式文件名、深度通道采集时间和深度格式文件名。其中每个文件名都以其对应的采集时间为前缀命名。整个过程将逐行处理这些记录:将第一个字段转换为双精度浮点数并存储于vTimestamps向量中;将第二个字段存储为RGB图像文件名序列;第三个字段则未被使用;最后将第四个字段用于存储深度图像文件名序列。
Tracking.cc
<> 和 <> 跟踪模块的详细分析

Frame.cc:ExtracORB()提取当前图像的ORB特征——消除扭曲——为网格分配关键点特征匹配
Frame.cc:ExtracORB()提取当前图像的ORB特征——消除扭曲——为网格分配关键点特征匹配
Tracking::Track() :
判断tracking状态:如果是NOT_INITIALIZED则针对于单目和非单目分别执行MonocularInitialization()、StereoInitialization()初始化,并更新地图视图。
2. 对于初始化成功的,接下来进行跟踪ORB-SLAM中关于跟踪状态有两种选择 (1)只进行跟踪不建图 (2)同时跟踪和建图,这里以(2)为例。初始化之后ORB-SLAM有三种跟踪模型可供选择。
- TrackWithMotionModel(); 运动模型:根据运动模型估计当前帧位姿——根据匀速运动模型对上一帧的地图点进行跟踪——优化位姿。
- TrackReferenceKeyFrame(); 关键帧模型:BoW搜索当前帧与参考帧的匹配点——将上一帧的位姿作为当前帧的初始值——通过优化3D-2D的重投影误差来获得位姿。
- Relocalization();重定位模型:计算当前帧的BoW——检测满足重定位条件的候选帧——通过BoW搜索当前帧与候选帧的匹配点——大于15个点就进行PnP位姿估计——优化。
- 首先假设相机恒速(即Rt和上一帧相同),然后计算匹配点数(如果匹配足够多则认为跟踪成功),如果匹配点数目较少,说明恒速模型失效,则选择参考帧模型(即特征匹配,PnP求解),如果参考帧模型同样不能进行跟踪,说明两帧键没有相关性,这时需要进行重定位,即和已经产生的关键帧中进行匹配(看看是否到了之前已经到过的地方)确定相机位姿,如果重定位仍然不能成功,则说明跟踪彻底丢失,要么等待相机回转,要不进行重置;
3. 假如通过一种模型完成了初始相机的位姿估计,则进一步跟踪局部地图TrackLocalMap(),即和当前帧相关联的地图点做联合优化,获得一个较为准确的相机位姿;
4. 创建关键帧/剔除外点/当前帧置为上一帧等
进行StereoInitialization初始化:若当前帧的关键点数量超过500个,则执行以下操作——首先将其标记为初始关键_frames,并加入全局地图;随后遍历所有关键_points并检查其正向深度信息;接着将这些_keypoints对应的地图坐标记录于地图中;然后与初始_keypoints建立关联关系;并将此_frame设为主参基准框架;最后将其记录于本地化主框架中的关键序列列表里。完成上述步骤后即可获取相机的姿态信息。
该函数用于更新局部地图,并包含关键帧与地图点的更新。首先检查局部地图中的点是否满足跟踪条件,并与当前帧中的相关点进行配对。随后对机器人位姿进行优化。然后根据配对结果及优化情况更新各 maps 点的状态。接着统计内部配对的数量。最后将处理结果返回
NeedNewKeyFrame():如果尚未启用局部建图,则无需在当前键入状态中插入新键;若已开启局部地图,并且当前键入状态与上一次重置之间的距离不足预设阈值,则无需在当前键入状态中插入新键;反之,则需检查所述局部构建过程是否满足以下五个必要条件:起始点一致性、拓扑结构完整性、几何一致性、时间戳同步性及数据冗余度等五个方面的要求:
超过MaxFrame帧必须通过全局重定位。(确保良好的定位)
2. 局部建图闲置,超过MinFrame帧必须通过关键帧插入。(确保及时处理)
3. 至少追踪当前帧的点是参考帧点的一定比例。(确保良好的追踪)
4. 当前帧追踪比参考帧90%的点少。(确保最小视觉变化)
5. 追踪到的close点的满足一定的条件
最后返回Boolean值
CreateNewFrame()
LocalMapping.cc
<>
该线程的主要职责是执行以下操作:
- 等待跟踪过程判断是否应该插入一个新的关键帧;
- 将该关键帧放入到地图中;
- 对于该局部地图(即在跟踪过程中动态维护的一个局部地图)执行BA优化以提升定位精度;
- 具体来说:
a. 完成3D点的三角剖分处理以确定空间位置;
b. 实现不同视角下的三维点匹配与融合以提高准确性;
c. 在适当时机将新捕获的关键帧加入到全局地图中以便增强整体定位效果;
d. 通过迭代优化算法对BA结果进行校准以确保计算一致性;
e. 最终完成所有步骤后记录下当前运行状态以便后续监控和分析。

SetAcceptKeyFrames(false)通知Tracking线程,LocalMapping处于忙碌状态,Tracking的关键帧发送不要太快
2. CheckNewKeyFrames()检查是否生成关键帧,及关键帧列表不为空——ProcessNewFrame()处理关键帧:计算BoW并插入到地图——MapPointCulling()上一步骤产生的不合格地图点,剔除地图点——CreateNewPoints()三角化创建新的地图点——CheckNewKeyFrames()判断关键帧队列处理完毕,则SearchInNeighbors()检查并融合当前关键帧与两级相邻帧间重复的地图点——已经处理完关键帧队列中的关键帧并且闭环检测没有要求停止LocalMapping,则进行局部BA优化——KeyFrameCulling()剔除冗余关键帧——InsertKeyFrame()插入关键帧到闭环检测队列
CheckNewKeyFrames():返回关键帧列表是否为空
ProcessNewFrame():生成当前关键帧的BoW特征向量,并建立与新地图点之间的对应关系。重新配置当前关键帧与其他关键帧之间的连接状态,并整合到全局的地图结构中。
首先从缓冲队列中取出一帧关键帧,设为当前关键帧,然后将栈顶的关键帧出栈
2. 计算该关键帧特征点的Bow映射关系
3. 跟踪局部地图过程中新匹配上的MapPoints和当前关键帧绑定
4. 遍历地图点,将当前关键帧生成的地图点添加到mlpRecentAddedMapPoints中,等待后续检查。而不是当前关键帧生成的地图点,更新该点的属性,将其设置为当前关键帧观察到的地图点,计算最佳的描述子,并获得该点的平均观测方向和观测距离范围。
5. 更新关键帧间的连接关系,Covisibility图和Essential图
6. 将该关键帧插入到地图中。
MapPointCulling():过滤ProcessNewKeyFrame与CreateNewMapPoints函数所生成的不良质量的MapPoints
对于上一阶段生成的mlpRecentAddedMapPoints数据集,在当前关键帧范围内提取相关地图点信息
直接剔除坏点
2. 跟踪到该点的帧数比预计可以观测到该点的帧数的比例小于0.25,则剔除该点
3. 从该点建立开始,到现在已经过了2个关键帧以上,但是观测到该点的关键帧数小于cnThObs(单目是2,非单目是3),则剔除该点
4. 从建立该点开始,已经过了3个关键帧而没有被剔除,则认为是质量高的点,因此没有SetBadFlag(),仅从队列中删除,放弃继续对该MapPoint的检测
SearchInNeighbors():通过与当前帧具有最高共视相似度的nn个相邻关键帧进行特征匹配,在获得对应特征点后完成三维重建(即三角化),从而得到对应的三维空间中的具体坐标位置信息。随后评估该匹配结果的质量指标包括深度正向性(即正向景深)、视差值以及重投影误差等,并验证其几何一致性后最终将这些计算出的三维坐标信息记录至全局地图中对应的位置上。
SearchInNeighbors():用于搜索并整合与其邻近帧(包括两级相邻)共享的MapPoints,并重新更新其地图点信息以及与周边帧的连接关系。
首先获得与当前关键帧具有最高共视程度的nn个关键帧,即是当前关键帧的一级相连帧,然后遍历一级相连帧,对每一个关键帧再获得与之具有最高共视程度的5个关键帧,即相当于当前关键帧的二级相连帧。
2. 将当前帧的MapPoints分别与一级二级相邻帧(的MapPoints)进行融合。
3. 将一级二级相邻帧的MapPoints分别与当前帧(的MapPoints)进行融合。
4. 更新当前帧MapPoints的描述子,深度,观测主方向等属性。
5. 更新当前帧的MapPoints后,更新covisibility图中当前关键帧与其它帧的连接关系。
KeyFrameCulling():
根据Covisibility Graph提取当前帧的共视关键帧。
2. 遍历该共视关键帧的MapPoints:判断该MapPoint是否同时被三个关键帧观测到,并且满足尺度约束条件
3. 统计以上满足条件的MapPoints
4. 判断以上统计的地图点数量是否大于有效地图点的90%。(有效地图点:不是坏点,且对于双目和深度相机,深度值满足一定条件)
5. 剔除上一条件不满足的共视关键帧。
LoopClosing.cc
<>
闭环检测线程采用LoopClosing::Run()函数实现。通过CheckNewKeyFrames()函数判断是否存在新的关键帧,并确保关键帧列表非空。随后由DetectLoop()算法完成回环检测工作。ComputeSim3()用于计算相似变换矩阵,在单目相机中表现为[sR|t]的形式,在双目或深度相机系统中则取s=1。CorrectLoop()算法负责对回环进行融合并完成姿态优化工作。
DetectLoop():

首先从闭环关键帧缓冲队列中取出一帧关键帧,设为当前关键帧,然后将栈顶的关键帧出栈
2. 判断距离上一次闭环检测是否超过10帧,不超过则不进行闭环检测,超过检测
3. 计算当前帧与相连关键帧的BoW最低得分minScore
4. 检测达到闭环候选帧DetectLoopCandidates()
1. 获得与当前帧有共视关系的但并不相连的关键帧
2. 获得当前关键帧与上述关键帧的最高单词数maxCommonWords
3. 阈值minCommonWords=0.8*maxCommonWords
4. 删选出大于阈值minCommonWords且评分得分大于minScore的关键帧
5. 将上述筛选后的相连的关键帧分为一组,不相连的剔除(单独得分很高的无关匹配关键帧)
6. 计算每一组最高得分bestAccScore
7. 阈值minScoreToRetain=0.75*bestAccScore
8. 大于阈值的为最后的候选关键帧vpLoopCandidates
5. 三个for循环检测候选关键帧的连续性
1. 首先遍历候选关键帧——vpCandidateKFs
2. 然后遍历当前帧及其相连帧(连续组)——mvCconsistentGroups
3. 最后遍历候选关键帧及其相连帧(候选组)——spCandidateeGroup
最后筛选出的闭环关键帧应该满足:候选关键帧能在三个连续组中同时被选作候选关键帧。
ComputeSim3()旨在通过计算当前帧和闭环关键帧之间的Sim3值,并从而实现更多匹配点的获取。同时在这一过程中,系统会进一步优化当前帧自身的位姿以及与其相连的所有其他帧的位置。
SearchByBow() 对每一个闭环帧,通过BoW的matcher方法进行第一次匹配,匹配闭环帧和当前关键帧之间的匹配关系,如果对应关系少于20个,则丢弃,否则构造一个Sim3求解器并保存起来。这一步主要是对效果较好的闭环帧构建Sim3求解器
2. 对上一步得到的每一个满足条件的闭环帧,通过RANSAC迭代,求解Sim3。
3. SearchBySim3()利用Sim3再去进行匹配点的查找,本次查找的匹配点数量,会在原来的基础上有所增加。
1. 当前帧的地图点投影到闭环帧上,得到更多的匹配点
2. 闭环帧的地图点投影到当前帧上,得到更多的匹配点
4. OptimizeSim3()在得到了第二次匹配的结果以后,要通过这些匹配点,再去优化Sim3的值,从而再精细化Rt和s
5. SearchByProjection()将闭环帧以及与其链接的关键帧所看到的所有的MapPoint都恢复出来。通过这个方法,可以尽可能得到我们当前关键帧所能看到的所有的地图点,为下一步做投影匹配,得到更多的匹配点做准备。
6. 使用投影得到更多的匹配点,如果匹配点数量充足,则接受该闭环。
CorrectLoop():

