(01)ORB-SLAM2源码无死角解析-(52) 局部建图线程→剔除关键帧KeyFrameCulling()、整体线程梳理

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

一、前言

上一篇博客对 SearchInNeighbors() 函数进行了讲解,该函数的主要功能为检查并融合当前关键帧与相邻关键帧帧(两级相邻)中重复的地图点。接下来呢,原本是应该讲解一个重要的函数,那就是 Optimizer::LocalBundleAdjustment() 函数,其功能为局部优化。

但是其涉及的东西还是挺多的,所以就放在后面进行讲解了,另外,在前面的博客中,讲解了参考关键帧追踪TrackReferenceKeyFrame()、恒速模型跟踪当前普通帧TrackWithMotionModel()、重定位跟踪 Relocalization()、重定位跟踪 Relocalization()。这些函数都调用了 Optimizer::PoseOptimization(&mCurrentFrame),可想而知其是多么的重要。

Optimizer::PoseOptimization()与Optimizer::LocalBundleAdjustment() 他们有很多相似的地方,为了给大家一个系统的认知,打算在后面的文章中进行统一的讲解。这里我们进行简单的理解即可:

O p t i m i z e r : : P o s e O p t i m i z a t i o n ( ) : \color{blue}Optimizer::PoseOptimization(): Optimizer::PoseOptimization() 当前帧仅位姿优化
O p t i m i z e r : : L o c a l B u n d l e A d j u s t m e n t ( ) : \color{blue} Optimizer::LocalBundleAdjustment() : Optimizer::LocalBundleAdjustment()对局部关键帧的进行位姿与地图点优化。

那么我们回到 src/LocalMapping.cc 文件中的 void LocalMapping::Run() 函数,暂且跳过 Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap); 函数,直接对 KeyFrameCulling(); 进行讲解了。
 

二、KeyFrameCulling()

该函数位于 src/LocalMapping.cc 文件中,其主要功能为: 检测当前关键帧在共视图中的关键帧,根据地图点在共视图中的冗余程度剔除该共视关键帧,90%以上的地图点能被其他关键帧(至少3个)观测到。 注意: \color{red} 注意: 注意:剔除的不是当前关键帧,而是共视关键帧。

( 1 ) : \color{blue}{(1):} (1): 根据共视图提取当前关键帧mpCurrentKeyFrame的所有共视关键帧,存储于变量 vpLocalKeyFrames 之中。

( 2 ) : \color{blue}{(2):} (2): 对所有的共视关键帧进行遍历, 先判断共视关键帧是否为第一帧关键帧,如果为第一帧关键帧则跳过(第一帧关键帧不会被删除),然后提取共视关键帧的所有地图点存放于 vpMapPoints 之中。

( 3 ) : \color{blue}{(3):} (3): 对共视关键帧的所有地图 vpMapPoints 进行遍历,如果地图点 pMP 是坏的则跳过。然后统计地图点能够被多少关键帧观测到,如果超过 thObs=3,则 nRedundantObservations 进行 +1操作。

( 4 ) : \color{blue}{(4):} (4): 遍历完共视关键帧的所有地图之后会跳出小循环,然后判断 nRedundantObservations 的数目是否超过该共视关键帧地图数的90%。如果超过了,则通过 pKF->SetBadFlag() 告知系统该共视关键帧需要删除,这里需要注意,其并没有马上删除该关键帧→这个关键帧有可能正在回环检测或者计算sim3操作,这时候虽然这个关键帧冗余,但是却不能删除,仅设置mbNotErase为true,这时候调用setbadflag函数时,不会将这个关键帧删除,只会把mbTobeErase变成true,代表这个关键帧可以删除但不到时候,先记下来以后处理。在闭环线程里调用 SetErase()会根据mbToBeErased 来删除之前可以删除还没删除的帧。

代码注释如下:

/**
 * @brief 检测当前关键帧在共视图中的关键帧,根据地图点在共视图中的冗余程度剔除该共视关键帧
 * 冗余关键帧的判定:90%以上的地图点能被其他关键帧(至少3个)观测到
 */
void LocalMapping::KeyFrameCulling()
{
    // Check redundant keyframes (only local keyframes)
    // A keyframe is considered redundant if the 90% of the MapPoints it sees, are seen
    // in at least other 3 keyframes (in the same or finer scale)
    // We only consider close stereo points

    // 该函数里变量层层深入,这里列一下:
    // mpCurrentKeyFrame:当前关键帧,本程序就是判断它是否需要删除
    // pKF: mpCurrentKeyFrame的某一个共视关键帧
    // vpMapPoints:pKF对应的所有地图点
    // pMP:vpMapPoints中的某个地图点
    // observations:所有能观测到pMP的关键帧
    // pKFi:observations中的某个关键帧
    // scaleLeveli:pKFi的金字塔尺度
    // scaleLevel:pKF的金字塔尺度

    // Step 1:根据共视图提取当前关键帧的所有共视关键帧
    vector<KeyFrame*> vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();

    // 对所有的共视关键帧进行遍历
    for(vector<KeyFrame*>::iterator vit=vpLocalKeyFrames.begin(), vend=vpLocalKeyFrames.end(); vit!=vend; vit++)
    {
        KeyFrame* pKF = *vit;
        // 第1个关键帧不能删除,跳过
        if(pKF->mnId==0)
            continue;
        // Step 2:提取每个共视关键帧的地图点
        const vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();

        // 记录某个点被观测次数,后面并未使用
        int nObs = 3;                     
        // 观测次数阈值,默认为3
        const int thObs=nObs;               
        // 记录冗余观测点的数目
        int nRedundantObservations=0;     
                                                                                      
        int nMPs=0;            

        // Step 3:遍历该共视关键帧的所有地图点,其中能被其它至少3个关键帧观测到的地图点为冗余地图点
        for(size_t i=0, iend=vpMapPoints.size(); i<iend; i++)
        {
            MapPoint* pMP = vpMapPoints[i];
            if(pMP)
            {
                if(!pMP->isBad())
                {
                    if(!mbMonocular)
                    {
                        // 对于双目或RGB-D,仅考虑近处(不超过基线的40倍 )的地图点
                        if(pKF->mvDepth[i]>pKF->mThDepth || pKF->mvDepth[i]<0)
                            continue;
                    }

                    nMPs++;
                    // pMP->Observations() 是观测到该地图点的相机总数目(单目1,双目2)
                    if(pMP->Observations()>thObs)
                    {
                        const int &scaleLevel = pKF->mvKeysUn[i].octave;
                        // Observation存储的是可以看到该地图点的所有关键帧的集合
                        const map<KeyFrame*, size_t> observations = pMP->GetObservations();

                        int nObs=0;
                        // 遍历观测到该地图点的关键帧
                        for(map<KeyFrame*, size_t>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
                        {
                            KeyFrame* pKFi = mit->first;
                            if(pKFi==pKF)
                                continue;
                            const int &scaleLeveli = pKFi->mvKeysUn[mit->second].octave;

                            // 尺度约束:为什么pKF 尺度+1 要大于等于 pKFi 尺度?
                            // 回答:因为同样或更低金字塔层级的地图点更准确
                            if(scaleLeveli<=scaleLevel+1)
                            {
                                nObs++;
                                // 已经找到3个满足条件的关键帧,就停止不找了
                                if(nObs>=thObs)
                                    break;
                            }
                        }
                        // 地图点至少被3个关键帧观测到,就记录为冗余点,更新冗余点计数数目
                        if(nObs>=thObs)
                        {
                            nRedundantObservations++;
                        }
                    }
                }
            }
        }

        // Step 4:如果该关键帧90%以上的有效地图点被判断为冗余的,则认为该关键帧是冗余的,需要删除该关键帧
        if(nRedundantObservations>0.9*nMPs)
            pKF->SetBadFlag();
    }
}

 

三、补充

执行 KeyFrameCulling() 函数之后,LocalMapping::Run() 调用了 mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame),将当前帧加入到闭环检测队列中。然后做了查看是否有复位线程的请求,如果当前线程已经结束了就跳出主循环等操作。在 LocalMapping 的初始化函数中可以看到很多标志位,主要用于线程之间的交互的,如下:

// 构造函数
LocalMapping::LocalMapping(Map *pMap, const float bMonocular):
    mbMonocular(bMonocular), mbResetRequested(false), mbFinishRequested(false), mbFinished(true), mpMap(pMap),
    mbAbortBA(false), mbStopped(false), mbStopRequested(false), mbNotStop(false), mbAcceptKeyFrames(true)
{
    /*
     * mbStopRequested:    外部线程调用,为true,表示外部线程请求停止 local mapping
     * mbStopped:          为true表示可以并终止localmapping 线程
     * mbNotStop:          true,表示不要停止 localmapping 线程,因为要插入关键帧了。需要和 mbStopped 结合使用
     * mbAcceptKeyFrames:  true,允许接受关键帧。tracking 和local mapping 之间的关键帧调度
     * mbAbortBA:          是否流产BA优化的标志位
     * mbFinishRequested:  请求终止当前线程的标志。注意只是请求,不一定终止。终止要看 mbFinished
     * mbResetRequested:   请求当前线程复位的标志。true,表示一直请求复位,但复位还未完成;表示复位完成为false
     * mbFinished:         判断最终LocalMapping::Run() 是否完成的标志。
     */
}

 

四、结语

通过一系列博客的讲解,已经对 void LocalMapping::Run() 函数做了很细致的分析,那么这里再贴一下整体的代码,这样局部建图线程我们就全部讲解完成了(除了Optimizer::LocalBundleAdjustment() ),代码注释如下:

// 线程主函数
void LocalMapping::Run()
{

    // 标记状态,表示当前run函数正在运行,尚未结束
    mbFinished = false;
    // 主循环
    while(1)
    {
        // Tracking will see that Local Mapping is busy
        // Step 1 告诉Tracking,LocalMapping正处于繁忙状态,请不要给我发送关键帧打扰我
        // LocalMapping线程处理的关键帧都是Tracking线程发来的
        SetAcceptKeyFrames(false);

        // Check if there are keyframes in the queue
        // 等待处理的关键帧列表不为空
        if(CheckNewKeyFrames())
        {
            // BoW conversion and insertion in Map
            // Step 2 处理列表中的关键帧,包括计算BoW、更新观测、描述子、共视图,插入到地图等
            ProcessNewKeyFrame();

            // Check recent MapPoints
            // Step 3 根据地图点的观测情况剔除质量不好的地图点
            MapPointCulling();

            // Triangulate new MapPoints
            // Step 4 当前关键帧与相邻关键帧通过三角化产生新的地图点,使得跟踪更稳
            CreateNewMapPoints();

            // 已经处理完队列中的最后的一个关键帧
            if(!CheckNewKeyFrames())
            {
                // Find more matches in neighbor keyframes and fuse point duplications
                //  Step 5 检查并融合当前关键帧与相邻关键帧帧(两级相邻)中重复的地图点
                SearchInNeighbors();
            }

            // 终止BA的标志
            mbAbortBA = false;

            // 已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
            if(!CheckNewKeyFrames() && !stopRequested())
            {
                // Local BA
                // Step 6 当局部地图中的关键帧大于2个的时候进行局部地图的BA
                if(mpMap->KeyFramesInMap()>2)
                    // 注意这里的第二个参数是按地址传递的,当这里的 mbAbortBA 状态发生变化时,能够及时执行/停止BA
                    Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);

                // Check redundant local Keyframes
                // Step 7 检测并剔除当前帧相邻的关键帧中冗余的关键帧
                // 冗余的判定:该关键帧的90%的地图点可以被其它关键帧观测到
                KeyFrameCulling();
            }

            // Step 8 将当前帧加入到闭环检测队列中
            // 注意这里的关键帧被设置成为了bad的情况,这个需要注意
            mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
        }
        else if(Stop())     // 当要终止当前线程的时候
        {
            // Safe area to stop
            while(isStopped() && !CheckFinish())
            {
                // 如果还没有结束利索,那么等
                // usleep(3000);
                std::this_thread::sleep_for(std::chrono::milliseconds(3));
            }
            // 然后确定终止了就跳出这个线程的主循环
            if(CheckFinish())
                break;
        }

        // 查看是否有复位线程的请求
        ResetIfRequested();

        // Tracking will see that Local Mapping is not busy
        SetAcceptKeyFrames(true);

        // 如果当前线程已经结束了就跳出主循环
        if(CheckFinish())
            break;

        //usleep(3000);
        std::this_thread::sleep_for(std::chrono::milliseconds(3));

    }

    // 设置线程已经终止
    SetFinish();
}

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值