ORB-SLAM2:LocalMapping线程学习随笔【李哈哈:看看总有收获篇】

前言

在Tracking线程中,生成了关键帧并送到了局部建图线程,这说明关键帧和地图点插入到Map中是在局部建图线程进行的
局部建图线程主要工作:

  1. 插入关键帧
  2. 当前帧地图点剔除
  3. 创建新的地图点
  4. 局部BA
  5. 局部关键帧剔除

Tracking线程地图点处理方法

这段一定要看
首先利用恒速运动模型得到当前帧粗略的位姿,以及匹配到的部分地图点,然后依据这些地图点,构建局部地图,局部地图构建后,在其中查找当前帧视野内的地图点,与该帧特征点进行投影匹配,再次获得了一些地图点 ,之后再进行位姿优化
实际上局部建图线程就是在玩地图点,所以一定搞清楚哦

插入关键帧:ProcessNewKeyFrame()

如标题,这里主要是该函数内部的一系列操作
该函数完成了处理列表中的关键帧,包括计算特征点的BoW、更新观测、描述子、共视图,插入到地图等操作
Step 1: 从缓冲队列中取出一帧关键帧
该关键帧队列是Tracking线程向LocalMapping中插入的关键帧组成
取出的关键帧存入mpCurrentKeyFrame之中
Step 2: 计算该关键帧特征点的Bow信息
调用函数ComputeBoW()来计算BoW:

mpCurrentKeyFrame->ComputeBoW();

Step 3: 当前处理关键帧中有效的地图点,更新normal,描述子等信息
这里遍历了当前帧所有的地图点,来完成上述操作
注意:这里区分了地图点是否来自该帧
如果该帧观测到了这个地图点,则:

// 将上述地图点放入mlpRecentAddedMapPoints,等待后续MapPointCulling函数的检验
mlpRecentAddedMapPoints.push_back(pMP);

这里的这个变量是后边剔除地图点处理的变量
对于没有观测到的地图点,就增添观测信息,但是这之后呢???????????????????????????❓

Step 4: 更新关键帧间的连接关系(共视图)

// Step 4:更新关键帧间的连接关系(共视图)
mpCurrentKeyFrame->UpdateConnections();

该函数在KeyFrame.cc内
在该步完成了更新共视图(Covisibility)和本质图(Essential)
关于这里的共视图和局部地图有什么区别,放在下边
值得一提的是:在没有执行这个函数前,关键帧只和MapPoints之间有连接关系,这个函数可以更新关键帧之间的连接关系
进入该函数:
①统计每一个地图点都有多少关键帧与当前关键帧存在共视关系,统计结果放在KFcounter:

KFcounter[mit->first]++;

KFcounter的定义如下:

map<KeyFrame *, int> KFcounter; // 关键帧-权重,权重为其它关键帧与当前关键帧共视3d点的个数

第1个参数表示某个关键帧,第2个参数表示该关键帧看到了多少当前帧的地图点,也就是共视程度
②找到对应权重最大的关键帧(共视程度最高的关键帧),LLH:并保存了共视关系满足阈值的关键帧?
该部分遍历了刚刚的KFcounter,找到共视程度满足阈值的关键帧,并记录了最大共视关系的关键帧
将共视关系保存进vPairs:

// 对应权重需要大于阈值,对这些关键帧建立连接
vPairs.push_back(make_pair(mit->second, mit->first));

这里的mit就是刚刚KFcounters
vPairs定义为:

// vPairs记录与其它关键帧共视帧数大于th的关键帧
// pair<int,KeyFrame*>将关键帧的权重写在前面,关键帧写在后面方便后面排序
vector<pair<int, KeyFrame *>> vPairs;

③如果没有连接到关键(超过阈值的权重),则对权重最大的关键帧建立连接
这里就是一个弥补措施吧
④对共视程度比较高的关键帧对更新连接关系及权重(从大到小)
这个真没看懂要干嘛
不过保存了几个参数,可能用于“全局”调用?

// 更新当前帧与其它关键帧的连接权重
mConnectedKeyFrameWeights = KFcounter;
mvpOrderedConnectedKeyFrames = vector<KeyFrame *>(lKFs.begin(), lKFs.end());
mvOrderedWeights = vector<int>(lWs.begin(), lWs.end());

⑤更新生成树的连接
这里好像有个关键帧树,与当前关键帧共视程度最高的过去的关键帧就是其父关键帧
Step 5: 将该关键帧插入到地图中

mpMap->AddKeyFrame(mpCurrentKeyFrame); 
///这里是这个名为mpMap的map类对象,调用了加入关键帧的函数,把mpCurrentKeyFrame插入到其中

至此,用于插入关键帧的函数ProcessNewKeyFrame()执行完毕

局部地图与共视图什么区别联系?

局部地图是一个粗略的地图,他依据的地图点是粗略匹配来的,最终目的是优化位姿,找到更多的匹配点
共视图就是在这个新的更加准确的位姿上,进行更加有效的操作的,共视图是个“图”,其边具有权重,也就是共视程度
共视图同样遍历了地图点,找到了有共视关系的关键帧,和局部地图中构建局部关键帧很像
小tips:笔者看到这本来被绕晕了,后来恍然大悟嘻嘻

剔除当前帧地图点:MapPointCulling()

为了保存地图点,必须在创建该地图点的需要满足下面三个条件的约束,才能真正被保存,这样才能保证可跟踪且不容易在三角化时出现较大误差,在函数 LocalMapping::MapPointCulling() 中实现
其实我有个疑问:此时当前帧的地图点,是哪些地图点,或者说什么时候更新地图点?
几个条件如下:

  1. 已经是坏点的直接从检查链表中删除
  2. 要求跟踪到该地图点的关键帧数量比预计的比例大于25%,即mnFound/mnVisible > 0.25
  3. 如果一个地图点被构建,必须被超过三个关键帧观测到(单目为2个)
  4. 从建立该点开始,已经过了 3 个关键帧而没有被剔除,则认为是质量高的点,因此没有 SetBadFlag(),仅从队列中删除,放弃继续对该 MapPoint 的检测

自言自语:这个地图点的剔除就很玄幻,主要是不清楚地图点的来源
10 minutes later:好像新的地图点来自下边:

创建新的地图点:CreateNewMapPoints()

根据源码中的注释,这里完成了用当前关键帧与相邻关键帧通过三角化产生新的地图点,使得跟踪更稳这一操作
一看到三角化,我啪的一下就站起来了,很快啊!
犹记得单目初始化就是用三角化找到了匹配的3D地图点,从而有了“新的”地图点,那这一步是不是就是产生了新的地图点呢?
Step 1: 在当前关键帧的共视关键帧中找到共视程度最高的nn帧相邻关键帧
这里的nn是自己设定的需要的共视关键帧数目

const vector<KeyFrame *> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);

之后遍历所有的共视关键帧
Step 2: 判断相机运动的基线是不是足够长
这个不是很清楚要干嘛,好像是三角化前的一个基础工作,检测能否正常三角化
Step 3: 根据两个关键帧的位姿计算它们之间的基础矩阵
计算基础矩阵,用于后边的三角化
❓基础矩阵是三角化的前提吗?这里的原理具体怎么玩的
Step 4: 通过BoW对两关键帧的未匹配的特征点快速匹配,用极线约束抑制离群点,生成新的匹配点对
使用如下函数完成该操作:

/// 用当前关键帧与相邻关键帧通过三角化产生新的地图点,使得跟踪更稳
matcher.SearchForTriangulation(mpCurrentKeyFrame, pKF2, F12, vMatchedIndices, false);

Step:5 对每对匹配点 2d-2d 通过三角化生成 3D 点,和 Triangulate 函数差不多
注意,这里是一堆的操作,我现在完全还不能参透
不过最终结果是将这些新产生的地图点加入了mlpRecentAddedMapPoints等待处理:

// Step 6.10:将新产生的点放入检测队列
// 这些MapPoints都会经过MapPointCulling函数的检验
mlpRecentAddedMapPoints.push_back(pMP);

这里值得一提这个mlpRecentAddedMapPoints

关于mlpRecentAddedMapPoints()

其定义为:

/// 存储当前关键帧生成的地图点,也是等待检查的地图点列表
std::list<MapPoint*> mlpRecentAddedMapPoints;

有两个地方添加了该列表,一个是插入关键帧的时候(更新共视图之前),一个是生成新的地图点这里
添加之后,在地图点剔除中,将遍历所有的这些点,有点套娃耶
不过其实剔除地图点的时候,最后一次剔除是与众不同的!
最后一次剔除是因为已经过了3帧没有被剔除,所以那些是好的地图点
这一次清除,相当于宣告了这是一个地图点,我暂时是这么理解的
所以生成的地图点究竟是不是地图点,就在于这一不起眼的小操作!(好隐蔽)
↑应该不是我自嗨吧[捂脸]😕

相邻关键帧融合优化:SearchInNeighbors()

首先说明:相邻指的是有共视关系的关键帧
Step 1: 获得当前关键帧在共视图中权重排名前nn的邻接关键帧
这里的邻接关键帧指的是邻居和邻居的邻居关键帧:

// 和当前关键帧相邻的关键帧,也就是一级相邻关键帧
const vector<KeyFrame *> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);

这里先获得一级相邻关键帧,再对其遍历,获得二级相邻关键帧
这些邻接关键帧都存入vpNeighKFs之中
Step 2: 将当前帧的地图点分别与一级二级相邻关键帧地图点进行融合 – 正向
Step 3: 将一级二级相邻关键帧地图点分别与当前关键帧地图点进行融合 – 反向
第2 3步的融合操作分别为:

matcher.Fuse(pKFi, vpMapPointMatches);
…
matcher.Fuse(mpCurrentKeyFrame, vpFuseCandidates);

Fuse函数前者是关键帧,后者是地图点集合
有一说一,不知道为啥要正向反向各来一次地图点融合
Step 4: 更新当前帧地图点的描述子、深度、观测主方向等属性
Step 5: 更新当前帧的MapPoints后更新与其它帧的连接关系
这里再次调用UpdateConnections函数更新共视图:

// 更新covisibility图
mpCurrentKeyFrame->UpdateConnections();

局部BA:LocalBundleAdjustment()

在已经处理完队列中的最后一个关键帧之后,并且闭环检测线程没有请求停止局部建图线程
则开始对当前帧进行局部 BA 优化,在 Optimizer::LocalBundleAdjustment函数中进行。

剔除冗余关键帧:KeyFrameCulling()

在局部建图最后,还要判断是否要剔除冗余关键帧
调用KeyFrameCulling(),步骤如下:
Step 1: 根据共视图提取当前关键帧的所有共视关键帧
Step 2: 提取每个共视关键帧的地图点
Step 3: 遍历该共视关键帧的所有地图点,判断是否90%以上的地图点能被其它至少3个关键帧(同样或者更低层级)观测到
Step 4: 该关键帧90%以上的有效地图点被判断为冗余的,则删除该关键帧:

if (nRedundantObservations > 0.9 * nMPs)
pKF->SetBadFlag();//删除关键帧的函数

问题

  1. 计算关键帧特征点的BoW向量是在干嘛?

参考达达的博客:ORB-SLAM2之LocalMapping线程——讲解篇
词袋模型(BoW)有如下几个几个关键概念:
单词(Word)
对于每个特征点,我们都有一个特定的描述子来描述它,假定这个描述子是一个P维向量,那么这个描述子在P维向量空间中就有一个位置,每个单词可以理解为是一个集合,包含这个向量空间中一定范围内的特征点。
词袋(Bag of Words)
一帧图片可以理解为一个词袋(Bag of Words),也就是一个向量,存储了这一帧图片中有哪些单词(word)。
字典(Dictionary)
单词是在字典(Dictionary)中定义的,在ORB-SLAM2中,字典是最开始初始化的时候从外部导入的(mono_kitti中的System函数),相当于一个库。


了解了这些概念,不难看出,词袋模型可以加快描述子的匹配,词袋模型通过层聚类,实现了树的结构
我们在检索匹配的描述子的时候,就可以先检索单词树是否相同,这样起到了加速匹配的作用。

  1. 关于地图点的一个大问题:

在这篇文章中作者自问自答了一个问题:
ORB_SLAM2代码阅读(3)——LocalMapping线程
我引用过来哈:

在处理关键帧过程中,有一个问题需要搞清楚(我自己开始理解的不清楚,导致这个问题想了很久)。这个问题是:在获取当前关键帧的地图点之后,为什么还要判断地图点是否在当前关键帧中?

要弄清这个问题还要从tracking线程中的局部地图跟踪过程说起。局部地图跟踪过程中首先将能够观测到当前帧地图点的关键帧及这些关键帧的共视关键帧和父子关键帧作为局部地图跟踪过程中的关键帧,然后将这些关键帧的所有地图点作为局部地图跟踪过程中的地图点。有了关键帧和地图点之后,需要将这些地图点与当前帧的地图点进行投影匹配,然后进行当前帧的位姿优化。这是局部地图跟踪的大致过程。由于局部地图(此处的局部地图是tracking线程中的局部地图)中的地图点与当前帧的地图点进行了投影匹配。所有可以认为当前帧的匹配地图点分为两部分:当前帧自己生成的地图点,非当前帧(局部地图中其他关键帧)生成的地图点。而非当前帧生成的地图点未与当前帧进行关联,所以在局部建图线程的关键帧处理部分需要为这些非当前帧(局部地图中其他关键帧)生成的地图点更新属性。
————————————————
版权声明:本文为CSDN博主「Andy是个男子名」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014709760/article/details/91378039

这里的核心论点是因为投影匹配,产生了两种地图点:当前帧自己生成的地图点,非当前帧(局部地图中其他关键帧)生成的地图点
所以应该好好看看投影匹配,我哭了亲人们,我太菜了呜呜
好吧我回头会好好看的,爬
10 minutes later:不过刚刚我又跑回去看了以下,发现好像刚刚的理解有点问题
在Tracking里进行了两次匹配
一次是帧间位姿估计的时候,从参考关键帧获得了一些地图点,这些应该是作者所谓的当前帧自己的地图点
一次是利用局部地图,在当前帧上遍历局部地图点,投影到当前帧,寻找匹配的地图点,这就是作者所谓的非当前帧生成的地图点

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值