zoukankan      html  css  js  c++  java
  • ORB-SLAM2 论文&代码学习 —— Tracking 线程

    转载请注明出处,谢谢
    原创作者:Mingrui
    原创链接:https://www.cnblogs.com/MingruiYu/p/12352960.html


    本文要点:

    • ORB-SLAM2 Tracking 线程 论文内容介绍
    • ORB-SLAM2 Tracking 线程 代码结构介绍

    写在前面

    上一篇文章中我们已经对 ORB-SLAM2 系统有了一个概览性的了解。通过我绘制的详细的思维导图形式的程序导图,我们也可以很清晰地看出各个线程之间的关系,以及它们是如何和论文中的 System Overview 图对应上的。

    依旧祭出该图,方便查看:

    也再次献上我绘制的程序导图全图:ORB-SLAM2 程序导图

    从这篇文章开始,我们将会进入 ORB-SLAM2 的每个部分,学习 ORB-SLAM2 每个部分的具体结构和逻辑。Tracking 线程是 ORB-SLAM2 系统的主线程,每一帧图像送入后也会先经过 Tracking 线程的处理。所以这篇文章,我们先来看看 Tracking 线程的具体工作。

    老规矩,还是分两部分:以 ORB-SLAM 论文为参考 和 以 ORB-SLAM2 代码(程序导图)为参考。

    以 ORB-SLAM 论文为参考

    首先,来看看论文中对 Tracking 线程的介绍。

    Tracking 线程的主要工作如下:

    • 对于新读取的帧,提取 ORB 特征
    • (系统初始化)
    • 当前帧位姿初值估计(根据上一帧 + motion-only BA,或进行重定位)
    • 局部地图跟踪
      • 对上一步得到的位姿初值进行进一步 BA 优化
      • 局部地图:指 Covisibility Graph 中附近的 KFs 及其 MapPoints 所组成的局部的地图
    • 决定是否将当前帧作为关键帧插入 LocalMapping 线程

    下面我们具体来看这些内容。

    注: Tracking 线程中很重要的一个工作是进行单目初始化,这一部分我会单独写一片文章来进行介绍,所以本文会暂时跳过单目初始化的具体内容。

    ORB 特征提取

    ORB-SLAM2 系统采用 ORB 特征作为贯穿整个系统使用的特征提取和描述方式。其优势在于,提取速度快(大幅快于 SIFT 和 SURF,但其实 ORB 特征的提取还是整个系统中最耗时的部分)。关于 ORB 特征的详细内容可见论文:ORB: An efficient alternative to SIFT or SURF PDF

    ORB 特征具有旋转不变性,但没有尺度不变性。为了减小尺度变化对于 ORB 特征的影响,ORB-SLAM 采用尺度金字塔的方式,将图像放大或缩小形成不同尺度(共8个,每个尺度之间的缩放比例为1.2),之后再在每个尺度的图像上都提取一遍 ORB 特征(提出 ORB 特征会带有一个标记,标记其是从哪个尺度提取出来的),将每个尺度提取出的 ORB 特征汇总在一起,就成为了该图像提取的 ORB 特征。

    为了尽可能使得 提取的 ORB 特征在图像上分布均匀(ORB 特征提取本身存在一个问题,其在图像上分布不均,经常有的局部一大堆特征点,有的局部没有特征点),ORB-SLAM 将每个尺度的图像,划分成一个个小格格(切蛋糕了),在每个小格格上提取至少5个特征点。如果提取不出5个特征点,就将提取特征的阈值放低一些。

    提取的 ORB 特征在 ORB-SLAM 系统中相当重要,会贯穿整个系统,用于所有的特征匹配。

    当前帧位姿初值估计

    Tracking 线程的目的之一是求出当前帧的位姿,其巧妙地将这个求解过程分为两步,从粗到细。相对较粗的步骤 —— 当前帧位姿初值估计,在估计好一个初值后,会进入相对较细的步骤 —— 局部地图跟踪,然后得到一个最终的位姿(当然在 LocalMapping 线程中还要继续优化)。

    首先来看这个相对较粗的步骤 —— 相机位姿初值估计。其有三种可能的估计方式,论文里提到了两种:根据上一帧和运动模型进行估计(上一帧跟踪成功) 和 通过全局重定位估计(上一帧跟踪丢失)。还有一种是根据 Reference KF 进行估计(虽然上一帧跟踪成功,但因为种种原因,无法使用上一帧和运动模型进行估计),我们会在代码部分对其进行介绍。另外,在这一部分中,除了会估计当前帧的位姿外,还会将当前帧的 FeaturePoints 和 MapPoints 做一个初步的匹配。

    根据上一帧和运动模型进行估计

    如果上一帧跟踪成功,就可以继续正常的跟踪。ORB-SLAM 系统假设了一个匀速运动模型,意思就是假设当前帧与上一帧之间的相对位姿变化量 = 上一帧和上上帧之间的相对位姿变化量。通过这个可以先估计出当前帧的一个位姿初值,根据这个位姿初值,将上一帧的 MapPoints 和当前帧的 FeaturePoints 进行匹配,之后根据匹配进行优化。(如果没有找到足够多的匹配,就要使用上面提到的 根据 Reference KF 进行估计的方法了)

    重定位

    如果上一帧跟踪失败了,没有上一帧的位姿,肯定是无法通过上面的方法继续跟踪的,所以要进行重定位。重定位的含义就是从 KF Database 中寻找有没有哪个 KF 与当前帧很相似,有的话可能当前帧就在那个 KF 附近,从而定位了当前帧。

    寻找可能的 KF 并计算当前帧位姿的方法如下:根据当前帧的 FeaturePoints 计算当前帧的 BoW。通过当前帧的 BoW 与 KF Database 中的 KFs 的 BoW 进行匹配,初步筛选初一批 Candidate KFs,并将当前帧的 FeaturePoints 与 Candidate KFs 含有的 MapPoints 进行匹配。之后,RANSAC 迭代计算当前帧的位姿(通过 PnP 算法求解)。注意,上述计算的目的之一是进一步筛选 Candidate KFs,所以根据每一个 Candidate KFs 都要计算出一个当前帧的位姿,直到找到一个合适的 Candidate KF,根据它计算出的当前帧位姿很合适(有足够多的 inliers)。再对其进行进一步优化,再进行更多的 FeaturePoints 和 MapPoints 的匹配。如果再优化后这个位姿计算还很合适(有足够多的 inliers),那就确定当前帧的位姿了,之后就可以继续正常跟踪了。

    局部地图跟踪

    当在上一步中获得了当前帧的位姿初值并且当前帧的 FeaturePoints 和 MapPoints 有了初步的匹配后,就会进入这个更精细的求解当前帧位姿的步骤 —— 局部地图跟踪。

    局部地图里包括:

    • 和当前帧有共同观察到的 MapPoints 的 KFs
      *上述 KFs 的 Covisible KFs
    • 它们含有的某些 MapPoints
      • 该 MapPoint 可以投影到当前帧的画幅内
      • 该 MapPoint 的平均可视方向与当前帧的方向的夹角不大于60度
      • 该 MapPoint 距离当前帧光心的距离在一定范围内(范围太大的话,ORB 特征的尺度不变性很难保证,这样匹配出错的概率很大)

    在计算得到局部地图的同时,还需要尽可能进一步地将局部地图的 MapPoints 与 当前帧还未匹配的 FeaturePoints 相匹配。最终,对该局部地图进行 BA 优化。

    决定是否将当前帧作为 KeyFrame

    如果当前帧比较重要,则会将其作为 KF 插入 Map 并送入 LocalMapping 线程。ORB-SLAM 的一个特点就是,其插入 KF 的条件很宽松,这样会插入很多 KFs,而 ORB-SLAM 会在 LocalMapping 线程中对它们中冗余的进行剔除。这样的目的是不放过可能有用的帧,增强系统的鲁棒性,以应对纯旋转等很难处理的相机运动。

    虽然这个条件很宽松,但还是有条件的:

    • 距离上一次重定位已经过去了超过20帧
    • LocalMapping 线程正闲置,但如果已经有连续20帧内没有插入过 KF 了,那么 LocalMapping 线程不管忙不忙,都要插入 KF 了 (忙就给老子停下hhh)
    • 当前帧至少追踪了 50 个点(当前帧质量不能太差)
    • 当前帧追踪的点中不能有90%以上和其 Reference KF 相同(当前帧不能太冗余)

    以 ORB-SLAM2 代码(程序导图)为参考

    看完了论文,可能有点不好理解,毕竟用文字进行描述的难度是很高的,特别是 ORB-SLAM2 系统内部各部分之间的逻辑又是较为复杂的。Talk is cheap, give me code. 下面我们从 ORB-SLAM2 的代码出发,结合我绘制的 ORB-SLAM2 程序导图,进一步加深对 Tracking 线程的理解。

    如上图所示,在 System.cc 的主循环中,启动了对于 Tracking 线程的调用。每次读入一帧图像,为其创建 Frame 对象,在创建的同时就提起了 ORB 特征。这里有一个细节,在单目初始化时,对于该帧提取的 ORB 特征数是平时的两倍。

    如上图所示,进入 Tracking 线程中,可以看到一个很重要的状态变量为 mState,根据它来区分当前系统的状态。当系统还未初始化时,会运行 MonocularInitialization() 来进行初始化。关于这一部分会在后面的博文中专门介绍。

    初始化之后,就会进入常规 Tracking 过程,其中首先进行当前帧位姿,之后进行局部地图跟踪,再之后决定是否生成 KF,并插入 KF。下面我们一个部分一个部分来看。

    因为这些地方实在不好截图,请大家点击这张导图的链接 (在文首)来查看清晰大图吧。

    注:

    ORB-SLAM2 系统有两种模式(可以由使用者手动切换),其以 mbOnlyTracking 变量进行区分:

    • SLAM 模型:所有线程都正常工作
    • Localization 模式:只有 Tracking 线程工作,其它线程均不工作

    同时,Localization 模式中也有两种情况(系统自动判定,根据当前跟踪情况自动切换),其以 mbVO 变量进行区分:

    • VO 情况:Visual Odometry,上一帧追踪到的点大部分是 VO 点(未能与 MapPoints 匹配(此处存疑)),此时不会进入局部地图跟踪,直到重定位成功,具体后面细说。
    • 正常情况:常规,所有部分正常运行。

    当前帧位姿初值估计

    如果是 SLAM 模式,则首先根据 mState 判断系统之前的跟踪状态。如果之前跟踪丢失,则要不断进行重定位 Tracking::Relocalization(),直到当前帧与 KF Database 中的某个 KF 匹配上了。如果之前跟踪正常,则继续跟踪,一般来说使用 Tracking::TrackWithMotionMode() 进行估计,但如果运动模型还未建立,或者刚刚进行了重定位,则使用 Tracking::TrackReferenceKeyFrame() 进行估计。TrackReferenceKeyFrame() 指当前帧和其 Reference KF 进行匹配来估计位姿,其匹配的搜索量会大很多,所以当 Tracking::TrackWithMotionMode() 不行的时候才会用它。

    具体的位姿估计方式都是 匹配 + 优化。只是匹配的方式会有所不同:

    • Tracking::TrackReferenceKeyFrame() 是根据 BoW 来在当前帧所有提取出的 FeaturePoints 和 Reference Frame 的 MapPoints 中进行匹配(当然使用 BoW 有可以减少计算量的方法);
    • Tracking::TrackWithMotionMode() 中是有了位姿初值,所以可以根据该初值进行投影,将上一帧的 MapPoints 先投影至当前帧的一个大概区域,从而缩小了搜索的区域大小,减小了搜索量。

    如果是 Localization 模式,那么如果之前系统跟踪丢失,同样不断进行重定位 Tracking::Relocalization()。如果之前系统跟踪正常,与 SLAM 模式不同的地方在于,其会判断当前处于 VO 情况还是正常情况:

    • 正常情况:与 SLAM 模式基本一致,根据运动模式是否已经建立而采用 Tracking::TrackWithMotionMode() 或 Tracking::Relocalization()。
    • VO 情况:与 SLAM 模式不一样了,此时进行 Tracking::TrackWithMotionMode() 和 Tracking::Relocalization(),优先使用 Relocalization() 的结果(此时重定位的结果更可靠一些),如果重定位失败,则采用 Tracking::TrackWithMotionMode() 继续跟踪甚至直接丢失,如果重定位成功,则可以推出 VO 模型回到正常模式。

    局部地图跟踪

    只有 SLAM 模式下,且上一步当前帧位姿初值估计成功(有位姿初值了)的情况下才会进行局部地图跟踪。

    在局部地图跟踪优化后,会判断优化的效果如何,如果效果可以的话,才会判断本次跟踪成功(当前帧位姿初值估计 + 局部地图跟踪 都成功才算成功),否则本次跟踪丢失。

    决定是否生成关键帧,并插入关键帧

    如果当前帧丢失的话,那肯定是不会将其作为 KF 插入的。但刚初始化完没几帧就丢失了,说明初始化的质量不行,系统 Reset,重新初始化。(从中可以看出,ORB-SLAM2 对于初始化的质量标准很高,所以也经常出现在实际中其迟迟不肯启动的状况)。

    如果当前帧跟踪成功,更新运动模型,且根据论文中的标准决定当前帧是否作为 KF 插入 Map,并送入 LocalMapping 线程。

    ORB-SLAM2 系列博文

    ORB-SLAM2 系列博文

  • 相关阅读:
    Spring Cloud Hystrix Dashboard的使用 5.1.3
    Spring Cloud Hystrix 服务容错保护 5.1
    Spring Cloud Ribbon 客户端负载均衡 4.3
    Spring Cloud 如何实现服务间的调用 4.2.3
    hadoop3.1集成yarn ha
    hadoop3.1 hdfs的api使用
    hadoop3.1 ha高可用部署
    hadoop3.1 分布式集群部署
    hadoop3.1伪分布式部署
    KVM(八)使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机
  • 原文地址:https://www.cnblogs.com/MingruiYu/p/12352960.html
Copyright © 2011-2022 走看看