讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件):
(01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/123092196
?
文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX官方认证
?

通过上一篇博客对闭环线程中的Optimizer::OptimizeEssentialGraph→本质图优化进行了详细讲解。今天主要讲解ORB-SLAM2中的最后一个BA优化(g2o),即函数Optimizer::GlobalBundleAdjustemnt→全局优化。该函数在两个地方有被调用:①src/Tracking.cc 文件中的Tracking::CreateInitialMapMonocular() 函数;②src/LoopClosing.cc 中的 LoopClosing::RunGlobalBundleAdjustment() 函数。

另外 LoopClosing::RunGlobalBundleAdjustment() 函数被 LoopClosing::CorrectLoop() 调用,且还是通过新启一个线程的方式,调用关系如下:

 

对于LoopClosing::RunGlobalBundleAdjustment函数而样,第一个执行的就是Optimizer::GlobalBundleAdjustemnt()函数,所以下面先对 GBA(GlobalBundleAdjustemnt)进行讲解,后续也把GlobalBundleAdjustemnt函数简称为GBA。
?

对于该函数,其实现于 src/Optimizer.cc 中,代码十分简单,如下所示:

 

其逻辑可以说是简单至极,就是获得地图中的所有关键帧,及所有地图点,然后传入到 BundleAdjustment() 函数中进行优化。那么很显然,该函数就是整个全局优化的核心了,那么下面就对齐进行详细讲解吧,代码位于 src/Optimizer 文件中。整体参数如下:

 

对于上述中的顶点与边,在前面的博客都进行过详细的讲解,所以这里就不再重复啰嗦。

1、源码逻辑

( 1 ) : \color{blue}{(1):} (1): 初始化g2o优化器,使用LM算法优化。根据 pbStopFlag 标志判断外部是否请求终止,如果是,那就结束。如果此时没有收到请请求,那么后续外部再请求结束BA,就结束不了了。

( 2 ) : \color{blue}{(2):} (2): 向优化器添加顶点,顶点主要包含→①所有关键帧Sim3位姿(g2o::VertexSE3Expmap);②地图中的所有地图点位姿(g2o::VertexSBAPointXYZ)。 需要注意 → \color{red}{需要注意}→ 需要注意在添加第0帧关键帧为顶点的时候,调用了vSE3->setFixed(ture)。也就是说有第0帧关键帧不优化(仅第0帧),其作为参考基准。
这里需要与前面的本质图优化Optimizer::OptimizeEssentialGraph作一个对比,在本质图优化中,其锁定的是与当前帧匹配上回环候选帧,并没有锁定第0帧,也就是说第0帧参与了优化。

( 3 ) : \color{blue}{(3):} (3): 把每个地图作为顶点添加到优化器之后,获得能够观测到该地图点的所有关键帧 observations,循环遍历所有观察到当前地图点的所有关键帧记为 pKF,跳过不合法的关键帧。取出该地图点对应该关键帧的2D特征点kpUn,作为观测值(认为其正确)。然后地图点与关键帧之间建立 g2o::EdgeSE3ProjectXYZ 边。

( 4 ) : \color{blue}{(4):} (4): 开始优化,进行迭代。迭代完成之后遍历所有关键帧,获取到优化后的位姿,把优化后的位姿写入到帧的一个专门的成员变量mTcwGBA中备用。 遍历所有地图点,去除其中没有参与优化过程的地图点(可能是随着位姿的不断优化,观测不到该地图点了)。

2、源码注释

BundleAdjustment 函数位于 src/Optimizer.cc 文件中,代码注释如下:

 

?

现在回过头来对 LoopClosing::RunGlobalBundleAdjustment(unsigned long nLoopKF) 函数进行讲解,其传入的参数 nLoopKF 看上去是闭环关键帧id,但是在调用的时候给的其实是当前关键帧的id。

在前面提到 LoopClosing::RunGlobalBundleAdjustment() 函数被 LoopClosing::CorrectLoop() 调用,且还是通过新启一个线程的方式。那么也就是说 RunGlobalBundleAdjustment() 可能被多个线程执行,在global BA过程中local mapping线程仍然在工作,这意味着在global BA时可能有新的关键帧产生,但是并未包括在GBA里,
所以和更新后的地图并不连续。需要通过spanning tree来传播。

1、代码逻辑

( 1 ) : \color{blue}{(1):} (1): 传入的参数nLoopKF 看上去是闭环关键帧id,但是在调用的时候给的其实是当前关键帧的id,首先调用 Optimizer::GlobalBundleAdjustemnt() 函数,执行全局BA,优化所有的关键帧位姿和地图中地图点。

( 2 ) : \color{blue}{(2):} (2): 在global BA过程中local mapping线程仍然在工作,这意味着在global BA时可能有新的关键帧产生,但是并未包括在GBA里,所以和更新后的地图并不连续。需要通过spanning tree来传播。

 

这些指针把的KF组织起来成为一个树形结构,称为 spanning tree。如果全局BA过程是因为意外结束的,那么直接退出GBA
?
( 3 ) : \color{blue}{(3):} (3): 如果当前GBA没有中断请求,则开始更新位姿和地图点。请求局部建图线程停止,等待直到local mapping 结束才会继续后续操作,对地图进行更行之后先上锁。创建list<KeyFrame*> lpKFtoCheck,刚开始只保存了初始化第0个关键帧

( 4 ) : \color{blue}{(4):} (4): 在GBA→Optimizer::GlobalBundleAdjustemnt中,GBA里锁住第一个关键帧位姿没有优化(前面已经讲解,且字体标红)。问:GBA里锁住第一个关键帧位姿没有优化,其对应的pKF->mTcwGBA是不变的吧?那后面调整位姿的意义何在?回答:注意在前面essential graph BA里只锁住了回环帧,没有锁定第0个初始化关键帧位姿。所以第0个初始化关键帧位姿已经更新了 在GBA里锁住第一个关键帧位姿没有优化,其对应的pKF->mTcwGBA应该是essential BA结果,在这里统一更新了。

( 5 ) : \color{blue}{(5):} (5): 通过lpKFtoCheck获得初始化的第0个关键帧的所有子关键帧,先获得父关键帧到当前子关键帧的位姿变换 Tchildc = pChild->GetPose() ? * ?Twc(未优化),再利用优化后的父关键帧的位姿,转换到世界坐标系下,相当于更新了子关键帧的位姿 pChild->mTcwGBA = Tchildc ? * ?pKF->mTcwGBA。

( 6 ) : \color{blue}{(6):} (6): 然后把所有子关键帧添加到 lpKFtoCheck 中,作为父关键帧,一直这样循环下去。这样最小生成树除了根节点(第0关键帧),其他的节点都会作为其他关键帧的子节点,这样做可以使得最终所有的关键帧都得到了优化。

( 7 ) : \color{blue}{(7):} (7): 遍历每一个地图点并用更新的关键帧位姿来更新地图点位置→①如果这个地图点直接参与到了全局BA优化的过程,那么就直接重新设置器位姿即可。②如这个地图点并没有直接参与到全局BA优化的过程中,那么就使用其参考关键帧的新位姿来优化自己的坐标。简而言之,把地图世界坐标系下的位姿转换到其参考关键帧相机坐标系下的坐标,然后使用已经纠正过的参考关键帧的位姿,再将该地图点变换到世界坐标系下,就完成了优化。
?

2、源码注释
 

?

到目前为止,可以说关于 ORB-SLAM2 的核心部分全部都讲解完成了,建档梳理一下,我们已经讲解了如下内容:

 

其上还有很多东西没有列举,现在回顾来看,ORB-SLAM2 涉及到的东西太多了,是一个大工程。但是可以说是对其每一个细节都进行了分析。理论知识已经讲解完成了,后续还回讲解一些工程中比较实用的东西。
?
?
本文内容来自计算机视觉life ORB-SLAM2 课程课件