您当前的位置: 首页 > 
  • 1浏览

    0关注

    417博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

(01)ORB-SLAM2源码无死角解析-(25) 关键帧KeyFrame→判断系统目前是否需要关键帧

江南才尽,年少无知! 发布时间:2022-05-09 15:06:42 ,浏览量:1

讲解关于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→官方认证  

一、前言

通过前面的博客,我们已经了解单目初始化的整个流程。但是这仅仅是追踪线程中的一部分,回到 src/Tracking.cc 文件 void Tracking::Track() 函数,很明显的看到,还有太多的东西需要讲解。在进行细致讲解之前,我们先来讲解一下 KeyFrame 相关的东西。在整个 ORB-SLAM2 系统中,关键帧 KeyFrame 起到了一个至关重要的作用,那么首先,我们来看看什么是关键帧 KeyFrame。

 

二、为什么需要关键帧

根据前面的知识讲解,已经能够很明白的了解到特征点,3D点,地图点之间的区别。建立在这些的基础上首先,我们需要明白的是,什么样的普通帧,可以认为是关键帧。通俗来说,关键帧就是几帧普通帧里面具有代表性的一帧。

( 1 ) : \color{blue}{(1)}: (1): 相近帧之间信息冗余度很高,关键帧是取局部相近帧中最有代表性的一帧,可以降低信息冗余度。举例来说,摄像头放在原处不动,普通帧还是要记录的,但关键帧不会增加。

( 2 ) : \color{blue}{(2)}: (2): 关键帧选择时还会对图片质量、特征点质量等进行考察,在Bundle Fusion、RKD SLAM等RGB-D SLAM相关方案中常常用普通帧的深度投影到关键帧上进行深度图优化,一定程度上关键帧是普通帧滤波和优化的结果,防止无用的或错误的信息进入优化过程而破坏定位建图的准确性。

( 3 ) : \color{blue}{(3)}: (3): 如果所有帧全部参与计算,不仅浪费了算力,对内存也是极大的考验,这一点在前端vo中表现不明显,但在后端优化里是一个大问题,所以关键帧主要作用是面向后端优化的算力与精度的折中,使得有限的计算资源能够用在刀刃上,保证系统的平稳运行。假如你放松ORB_SLAM2 关键帧选择条件,大量产生的关键帧不仅耗计算资源,还会导致 local mapping 计算不过来,出现误差累积。  

三、如何选择关键帧

选择关键帧主要从 关键帧自身和关键帧与其他关键帧的关系 \color{red}{关键帧自身和关键帧与其他关键帧的关系} 关键帧自身和关键帧与其他关键帧的关系 2方面来考虑。关键帧自身质量要好→例如不能是非常模糊的图像、特征点数量要充足、特征点分布要尽量均匀等等;关键帧与其他关键帧之间的关系→例如需要和局部地图中的其他关键帧有一定的共视关系但又不能重复度太高,以达到既存在约束,又尽量少的信息冗余的效果,选取的指标主要有如下:

( 1 ) 时间 : \color{blue}{(1)}时间: (1)时间: 距离上一关键帧的帧数是否足够多(时间)。比如我每隔固定帧数选择一个关键帧,这样编程简单但效果不好。比如运动很慢的时候,就会选择大量相似的关键帧,冗余,运动快的时候又丢失了很多重要的帧。

( 2 ) 空间 : \color{blue}{(2)}空间: (2)空间: 距离最近关键帧的距离是否足够远(空间)/运动比如相邻帧根据pose计算运动的相对大小,可以是位移也可以是旋转或者两个都考虑,运动足够大(超过一定阈值)就新建一个关键帧,这种方法比第一种好。但问题是如果对着同一个物体来回扫就会出现大量相似关键帧

( 3 ) 共视特征点数目 : \color{blue}{(3)}共视特征点数目: (3)共视特征点数目: 跟踪局部地图质量(共视特征点数目),记录当前视角下跟踪的特征点数或者比例,当相机离开当前场景时(双目或比例明显降低)才会新建关键帧,避免了第2种方法的问题。缺点是数据结构和逻辑比较复杂。

在关键帧的运用上,我认为orbslam2做的非常好,跟踪线程选择关键帧标准较宽松,局部建图线程再跟据共视冗余度进行剔除,尤其是在回环检测中使用了以关键帧为代表的帧“簇”的概念,回环筛选中有一步将关键帧前后10帧为一组,计算组内总分,以最高分的组的0.75为阈值,滤除一些组,再在剩下的组内各自找最高分的一帧作为备选帧,这个方法非常好地诠释了“关键帧代表局部”的这个理念。下面是整个 ORB-SLAM2 的流程图: 在这里插入图片描述 其上的三个红框的部分分别代表 追踪、局部建图、闭环 三个线程。简要的可以看出,在追踪线程中,其会决定是否新建关键帧。局部建图、闭环都是对关键帧进行处理。

 

四、源码分析与讲解

如何判断当前帧是否为普通帧? 或者说判断系统目前是否需要关键帧? 其核心函数为 Tracking::NeedNewKeyFrame()。其主要有如下几个步骤:

1:纯VO(视觉里程)模式下不插入关键帧,如果局部地图被闭环检测使用,则不插入关键帧
2:如果距离上一次重定位比较近,或者关键帧数目超出最大限制,不插入关键帧
3:得到参考关键帧跟踪到的地图点数量
4:查询局部地图管理器是否繁忙,也就是当前能否接受新的关键帧
5:对于双目或RGBD摄像头,统计可以添加的有效地图点总数 和 跟踪到的地图点数量
6:决策是否需要插入关键帧

代码注释如下:

/**
 * @brief 判断当前帧是否需要插入关键帧
 * 
 * Step 1:纯VO模式下不插入关键帧,如果局部地图被闭环检测使用,则不插入关键帧
 * Step 2:如果距离上一次重定位比较近,或者关键帧数目超出最大限制,不插入关键帧
 * Step 3:得到参考关键帧跟踪到的地图点数量
 * Step 4:查询局部地图管理器是否繁忙,也就是当前能否接受新的关键帧
 * Step 5:对于双目或RGBD摄像头,统计可以添加的有效地图点总数 和 跟踪到的地图点数量
 * Step 6:决策是否需要插入关键帧
 * @return true         需要
 * @return false        不需要
 */
bool Tracking::NeedNewKeyFrame()
{
    // Step 1:纯VO模式下不插入关键帧
    if(mbOnlyTracking)
        return false;

    // If Local Mapping is freezed by a Loop Closure do not insert keyframes
    // Step 2:如果局部地图线程被闭环检测使用,则不插入关键帧
    if(mpLocalMapper->isStopped() || mpLocalMapper->stopRequested())
        return false;
    // 获取当前地图中的关键帧数目
    const int nKFs = mpMap->KeyFramesInMap();

    // Do not insert keyframes if not enough frames have passed from last relocalisation
    // mCurrentFrame.mnId是当前帧的ID
    // mnLastRelocFrameId是最近一次重定位帧的ID
    // mMaxFrames等于图像输入的帧率
    //  Step 3:如果距离上一次重定位比较近,并且关键帧数目超出最大限制,不插入关键帧
    if( mCurrentFrame.mnId mMaxFrames)                                     
        return false;

    // Tracked MapPoints in the reference keyframe
    // Step 4:得到参考关键帧跟踪到的地图点数量
    // UpdateLocalKeyFrames 函数中会将与当前关键帧共视程度最高的关键帧设定为当前帧的参考关键帧 

    // 地图点的最小观测次数
    int nMinObs = 3;
    if(nKFs= nMinObs的地图点数目
    int nRefMatches = mpReferenceKF->TrackedMapPoints(nMinObs);

    // Local Mapping accept keyframes?
    // Step 5:查询局部地图线程是否繁忙,当前能否接受新的关键帧
    bool bLocalMappingIdle = mpLocalMapper->AcceptKeyFrames();

    // Check how many "close" points are being tracked and how many could be potentially created.
    // Step 6:对于双目或RGBD摄像头,统计成功跟踪的近点的数量,如果跟踪到的近点太少,没有跟踪到的近点较多,可以插入关键帧
     int nNonTrackedClose = 0;  //双目或RGB-D中没有跟踪到的近点
    int nTrackedClose= 0;       //双目或RGB-D中成功跟踪的近点(三维点)
    if(mSensor!=System::MONOCULAR)
    {
        for(int i =0; i0 && mCurrentFrame.mvDepth[i]=mnLastKeyFrameId+mMinFrames && bLocalMappingIdle);

    // Condition 1c: tracking is weak
    // Step 7.4:在双目,RGB-D的情况下当前帧跟踪到的点比参考关键帧的0.25倍还少,或者满足bNeedToInsertClose
    const bool c1c =  mSensor!=System::MONOCULAR &&             //只考虑在双目,RGB-D的情况
                    (mnMatchesInliersInterruptBA();
            if(mSensor!=System::MONOCULAR)
            {
                // 队列里不能阻塞太多关键帧
                // tracking插入关键帧不是直接插入,而且先插入到mlNewKeyFrames中,
                // 然后localmapper再逐个pop出来插入到mspKeyFrames
                if(mpLocalMapper->KeyframesInQueue()TrackedMapPoints(nMinObs); 该函数在上一篇博客中有讲解: 如果刚刚完成初始化,这参考帧为当前帧,否则在 UpdateLocalKeyFrames 函数中会将与当前关键帧共视程度最高的关键帧设定为当前帧的参考关键帧 。这里认为为与当前帧共视程度最高的关键帧为参考帧。

在当前帧中存在N个特征点,但是并不是每个特征点都能够三角化,生成一个地图点。所以这里对当前帧的地图点做了一个统计,如果当前帧的一个地图点,被 minObs 以上关键帧(相机) 观测到,认为该关键点被追踪到。该函数返回的局部变量 nRefMatches 则表示当前帧被追踪到的地图点总数。

mSensor!=System::MONOCULAR 如果不是单目摄像头,其会做一些额外的操作,比如判断当前帧关键点的深度值是否在有效范围内(比较近的距离)。如果关键点不在有效范围内,则执行 nNonTrackedClose++操作。总的来说 nNonTrackedClose记录双目或RGB-D中没有跟踪到的近点数目;nTrackedClose 记录双目或RGB-D中成功跟踪的近点(三维点)数目。

 

六、结语

通过该篇博客,知道 ORB-SLAM2 运行的时候,何时需要关键帧。那么不必多说,可想而知接下来就是创建关键帧,或者说添加关键帧,精彩请看下篇博客。

    本文内容来自计算机视觉life ORB-SLAM2 课程课件

关注
打赏
1592542134
查看更多评论
0.0369s