• (01)ORB-SLAM2源码无死角解析-(51) 局部建图线程→LocalBundleAdjustment():局部优化


    讲解关于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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

     

    三、补充

    执行 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() 是否完成的标志。
         */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

     

    四、结语

    通过一系列博客的讲解,已经对 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

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

  • 相关阅读:
    YoLo V3 SPP u模型的讲解与总结
    流量2----2
    Windows 下自动预约申购 i茅台
    html静态网站基于品优购电商购物网站网页设计与实现共计3个页面 html+css+javascript网页设计实例 企业网站制作
    使用C#编写.NET分析器(三)
    电商卖家保障数据隐私和安全用什么安全的浏览器?
    CRS 管理与维护
    集合深度学习06—iterator 迭代器源码解析
    ESP32 AT指令连接AWS亚马逊云
    功能自动化测试的策略有哪些?
  • 原文地址:https://blog.csdn.net/weixin_43013761/article/details/126399259