ORB-SLAM2 ----Tracking::NeedNewKeyFrame()和Tracking::CreateNewKeyFrame()

文章目录

  • 一. 函数的意义
  • 二、Tracking::NeedNewKeyFrame()
    • 1. 函数讲解
    • 2. 函数代码
  • 三、Tracking::CreateNewKeyFrame()
    • 1. 函数讲解
    • 2. 函数代码
  • 四、总结

一. 函数的意义

前面几篇文章讲过跟踪线程的核心问题–找到新来帧的位姿,这两个函数起到的作用是连接跟踪线程和局部建图线程,这两个线程靠关键帧来连接。这两个函数一个还判断是否需要创建关键帧,一个是创建新关键帧的函数,在前面的学习中关键帧可能没有发挥到太大的作用,但是在接下来的两个线程中,关键帧是两大研究的对线之一(两大研究对象是,地图点和关键帧)。所以他的作用及其重要,学习这两个函数,我们的目标是知道,什么情况需要新的关键帧,关键帧应具有什么样的特点。

二、Tracking::NeedNewKeyFrame()

1. 函数讲解

该函数分为以下几个步骤。本函数为一个有返回值的函数,如果返回值为true则白表示,需要插入关键帧,如果返回值为false则表示不需要插入关键帧。判断是否需要关键帧,首先我们要明白为什么要插入关键帧,是因为跟踪得不太好,需要插入一个关键帧,让结果更加可靠。所以这里有一个必要的条件,也就是代码总出现得c2,和参考关键帧相比跟踪得点太少,或者当前帧没跟踪到得点太多(跟踪到得小于100,没跟踪到得大于70)。然后就是判断能不能插入关键帧,c1a说很长时间没插入可以插入,因为很长时间没插入,时间太就前面得关键帧可能就不靠谱了,c1b说满足插入关键帧的最小间隔并且localMapper处于空闲状态可以插入,是因为这个时候不会对局部建图产生影响,在这种情况下可以尽量得使得位姿估计准确。c1c时双目和RGBD情况下得判断条件,他和c2其实说的是一件事情,只不过RGBD需要更苛刻得条件才会去插入关键帧。我们要知道得是双目和RGBD因为有深度信息,所以关键帧数量没有单目那么高。

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

2. 函数代码

/**
 * @brief 判断当前帧是否需要插入关键帧
 * @return true         需要
 * @return false        不需要
 */ 
bool Tracking::NeedNewKeyFrame()
{
    // 仅定位模式不插入关键帧
    if(mbOnlyTracking)
        return false;

    // If Local Mapping is freezed by a Loop Closure do not insert keyframes
    // 如果局部地图线程被闭环检测使用,则不插入关键帧
    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
    // 如果距离上一次重定位比较近,并且关键帧数目超出最大限制,不插入关键帧
    if(mCurrentFrame.mnId<mnLastRelocFrameId+mMaxFrames && nKFs>mMaxFrames)
        return false;

    // Tracked MapPoints in the reference keyframe
    // 得到参考关键帧跟踪到的地图点数量
     
    // 设置地图点的最小观测次数
    int nMinObs = 3;

    // 如果关键帧数量小于等于2,则设定最小观测数量为2
    if(nKFs<=2)
        nMinObs=2;
    
    // 获取参考关键帧地图点中观测的数目>= nMinObs的地图点数目
    int nRefMatches = mpReferenceKF->TrackedMapPoints(nMinObs);

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

    // Check how many "close" points are being tracked and how many could be potentially created.
    // 对于双目或RGBD摄像头,统计成功跟踪的近点的数量,如果跟踪到的近点太少,没有跟踪到的近点较多,可以插入关键帧
    int nNonTrackedClose = 0;   //双目或RGB-D中没有跟踪到的近点
    int nTrackedClose= 0;       //双目或RGB-D中成功跟踪的近点(三维点)
    
    // 如果不是单目,则遍历当前帧的特征点,在排除深度不合格的点后,将其分为跟踪到的近点和没有跟踪到的近点
    if(mSensor!=System::MONOCULAR)
    {
        for(int i =0; i<mCurrentFrame.N; i++)
        {
            if(mCurrentFrame.mvDepth[i]>0 && mCurrentFrame.mvDepth[i]<mThDepth)
            {
                if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i])
                    nTrackedClose++;
                else
                    nNonTrackedClose++;
            }
        }
    }
    
    // 如果跟踪到的近点数小于100,且没跟踪到大于70,则返回true 
    bool bNeedToInsertClose = (nTrackedClose<100) && (nNonTrackedClose>70);

    // 决策是否需要插入关键帧
    // Thresholds

    // 设定比例阈值,当前帧和参考关键帧跟踪到点的比例,比例越大,越倾向于增加关键帧
    float thRefRatio = 0.75f;
    // 如果关键帧数量小于2,减小这个比例
    if(nKFs<2)
        thRefRatio = 0.4f;

    // 单目情况设置设置为0.9
    if(mSensor==System::MONOCULAR)
        thRefRatio = 0.9f;

    // Condition 1a: More than "MaxFrames" have passed from last keyframe insertion
    // 很长时间没有插入关键帧,可以插入
    const bool c1a = mCurrentFrame.mnId>=mnLastKeyFrameId+mMaxFrames;
    // Condition 1b: More than "MinFrames" have passed and Local Mapping is idle
    // 满足插入关键帧的最小间隔并且localMapper处于空闲状态,可以插入
    const bool c1b = (mCurrentFrame.mnId>=mnLastKeyFrameId+mMinFrames && bLocalMappingIdle);
    //Condition 1c: tracking is weak
    // 在双目,RGB-D的情况下当前帧跟踪到的点比参考关键帧的0.25倍还少,或者满足bNeedToInsertClose
    const bool c1c =  mSensor!=System::MONOCULAR && (mnMatchesInliers<nRefMatches*0.25 || bNeedToInsertClose) ;
    // Condition 2: Few tracked points compared to reference keyframe. Lots of visual odometry compared to map matches.
    // 和参考帧相比当前跟踪到的点太少或者满足bNeedToInsertClose;同时跟踪到的内点还不能太少
    const bool c2 = ((mnMatchesInliers<nRefMatches*thRefRatio|| bNeedToInsertClose) && mnMatchesInliers>15);

    // 满足c1a,c1b,c1c中的一个和c2
    if((c1a||c1b||c1c)&&c2)
    {
        // If the mapping accepts keyframes, insert keyframe.
        // Otherwise send a signal to interrupt BA
        // 局部建图线程空闲就可以插入
        if(bLocalMappingIdle)
        {
            return true;
        }
        else
        {
            // 发射一个中断BA的信号
            mpLocalMapper->InterruptBA();
            // 如果不是单目
            if(mSensor!=System::MONOCULAR)
            {
                // 队列里不能阻塞太多关键帧
                // tracking插入关键帧不是直接插入,而且先插入到mlNewKeyFrames中,
                // 然后localmapper再逐个pop出来插入到mspKeyFrames
                if(mpLocalMapper->KeyframesInQueue()<3)
                    return true;
                //队列中缓冲的关键帧数目太多,暂时不能插入
                else
                    return false;
            }
            //对于单目情况,就直接无法插入关键帧了
            //? 为什么这里对单目情况的处理不一样?
            //回答:可能是单目关键帧相对比较密集
            else
                return false;
        }
    }
    else
        return false;
}

三、Tracking::CreateNewKeyFrame()

1. 函数讲解

这个函数是上一个函数得延申,如果上一个函数需要关键帧,那么这个函数就将当前帧构建为关键帧,因为新增了关键帧,那后面得帧完全可以以当前关键帧为当前参考关键帧(因为相对位移比上一个关键帧少),所以需要将当前关键帧设置为参考关键帧。最后一步就是,双目和RGBD情况时,我们因为有特征点的深度信息,完全可以把准确的特征点投影成地图点,这样可以为后面的位姿优化提供更多的约束条件,使误差更小,增加实时建图的精度。

2. 函数代码

/**
 * @brief 创建新的关键帧
 * 对于非单目的情况,同时创建新的MapPoints
 * 
 * Step 1:将当前帧构造成关键帧
 * Step 2:将当前关键帧设置为当前帧的参考关键帧
 * Step 3:对于双目或rgbd摄像头,为当前帧生成新的MapPoints
 */ 
void Tracking::CreateNewKeyFrame()
{
    // 如果局部建图线程关闭了,就无法插入关键帧
    if(!mpLocalMapper->SetNotStop(true))
        return;

    // 将当前帧创建为关键帧
    KeyFrame* pKF = new KeyFrame(mCurrentFrame,mpMap,mpKeyFrameDB);

    // 将当前关键帧设置为当前帧的参考关键帧
    mpReferenceKF = pKF;
    mCurrentFrame.mpReferenceKF = pKF;

    // 双目和RGB的情况
    if(mSensor!=System::MONOCULAR)
    {
        // 更新当前帧世界坐标
        mCurrentFrame.UpdatePoseMatrices();

        // We sort points by the measured depth by the stereo/RGBD sensor.
        // We create all those MapPoints whose depth < mThDepth.
        // If there are less than 100 close points we create the 100 closest.
        // 得到当前帧有深度值的特征点
        vector<pair<float,int> > vDepthIdx;
        vDepthIdx.reserve(mCurrentFrame.N);
        for(int i=0; i<mCurrentFrame.N; i++)
        {
            float z = mCurrentFrame.mvDepth[i];
            if(z>0)
            {
                vDepthIdx.push_back(make_pair(z,i));
            }
        }

        if(!vDepthIdx.empty())
        {
            // 按照深度从小到大排序
            sort(vDepthIdx.begin(),vDepthIdx.end());

            int nPoints = 0;
            // 从中找出不是地图点的包装为地图点
            for(size_t j=0; j<vDepthIdx.size();j++)
            {
                int i = vDepthIdx[j].second;

                bool bCreateNew = false;

                // 如果这个点对应在上一帧中的地图点没有,或者创建后就没有被观测到,那么就包装为地图点
                MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];
                if(!pMP)
                    bCreateNew = true;
                else if(pMP->Observations()<1)
                {
                    bCreateNew = true;
                    mCurrentFrame.mvpMapPoints[i] = static_cast<MapPoint*>(NULL);
                }

                // 如果需要就新建地图点,这里的地图点不是临时的,是全局地图中新建地图点,用于跟踪
                if(bCreateNew)
                {
                    cv::Mat x3D = mCurrentFrame.UnprojectStereo(i);
                    MapPoint* pNewMP = new MapPoint(x3D,pKF,mpMap);
                    pNewMP->AddObservation(pKF,i);
                    pKF->AddMapPoint(pNewMP,i);
                    pNewMP->ComputeDistinctiveDescriptors();
                    pNewMP->UpdateNormalAndDepth();
                    mpMap->AddMapPoint(pNewMP);

                    mCurrentFrame.mvpMapPoints[i]=pNewMP;
                    nPoints++;
                }
                else
                {    
                    nPoints++;
                }
                
                // 停止新建地图点必须同时满足以下条件:
                // 1、当前的点的深度已经超过了设定的深度阈值(35倍基线)
                // 2、nPoints已经超过100个点,说明距离比较远了,可能不准确,停掉退出
                if(vDepthIdx[j].first>mThDepth && nPoints>100)
                    break;
            }
        }
    }

    // 插入关键帧
    mpLocalMapper->InsertKeyFrame(pKF);

    // 插入好了,允许局部建图停止
    mpLocalMapper->SetNotStop(false);

    // 当前帧成为新的关键帧,更新
    mnLastKeyFrameId = mCurrentFrame.mnId;
    mpLastKeyFrame = pKF;
}

四、总结

这两个函数起到的是承上启下的作用,只有沈城关键帧,才会进入局部建图环节,是连接追踪线程和局部见图线程的函数。这两个函数中,第一个函数是重点,要了解什么时候需要关键帧,知道其中的原理,这个函数也就完全掌握了。到此追踪线程的主要函数就全部讲完了,接下来的文章将进入第二个线程—局部见图线程。如果对追踪线程有疑问或者发现问题,欢迎大家交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924217.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pycharm2021.1汉化失败 “chinese (simplified) language pack“ was not installed

汉化报错&#xff1a;pycharm plugin “chinese (simplified) language pack” was not installed : Invalid filename returned by a server 翻译&#xff1a;pycharm 插件“中文&#xff08;简体&#xff09;语言包”未安装&#xff1a;服务器返回的文件名无效 解决&#…

Java基于 SpringBoot+Vue的口腔管理平台(附源码+lw+部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

图书系统小案例

目前就实现了分页查询&#xff0c;修改&#xff0c;删除功能 这个小案例练习到了很多技能&#xff0c;比如前后端交互、异步请求、三层架构思想、后端连接数据库、配置文件、基础业务crud等等 感兴趣的小伙伴可以去做一个试试 准备工作 1、使用maven构建一个web工程 打开i…

延时系统建模,整数延时与分数延时,连续传函与离散传函,Pade近似与Thiran近似,Matlab实现

连续传递函数 严格建模&#xff1a;指数形式 根据拉普拉斯变换的性质&#xff0c; [ f ( t ) ↔ F ( s ) ] ⇔ [ f ( t − t 0 ) ↔ e − s t 0 F ( s ) ] \left[ {f\left( t \right) \leftrightarrow F\left( s \right)} \right] \Leftrightarrow \left[ {f\left( {t - {t_0…

3.14MayBeSomeStack

栈指针是sp 静态数据在内存中位置不改变 码距就是相邻两个合法的数据之间的差距&#xff0c;如果为2的话&#xff0c;相邻两个合法的数据之间存在一个冗余的数据&#xff0c;这个数据肯定是出错的&#xff0c;但是无法判断是哪个合法的数产生的&#xff1b; 如果码距是3的话&…

NLP 2、机器学习简介

人生的苦难不过伏尔加河上的纤夫 —— 24.11.27 一、机器学习起源 机器学习的本质 —— 找规律 通过一定量的训练样本找到这些数据样本中所蕴含的规律 规律愈发复杂&#xff0c;机器学习就是在其中找到这些的规律&#xff0c;挖掘规律建立一个公式&#xff0c;导致对陌生的数…

springboot视频网站系统的设计与实现(代码+数据库+LW)

摘 要 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在视频信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的视频网站系统管理员功…

探索Python网页解析新纪元:requests-html库揭秘

文章目录 **探索Python网页解析新纪元&#xff1a;requests-html库揭秘**1. 背景介绍&#xff1a;为何选择requests-html&#xff1f;2. requests-html库是什么&#xff1f;3. 如何安装requests-html库&#xff1f;4. 五个简单的库函数使用方法4.1 发起HTTP请求4.2 解析HTML内容…

DataWhale—PumpkinBook(TASK05决策树)

课程开源地址及相关视频链接&#xff1a;&#xff08;当然这里也希望大家支持一下正版西瓜书和南瓜书图书&#xff0c;支持文睿、秦州等等致力于开源生态建设的大佬✿✿ヽ(▽)ノ✿&#xff09; Datawhale-学用 AI,从此开始 【吃瓜教程】《机器学习公式详解》&#xff08;南瓜…

爱尔兰杀菌剂数据分析_1

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

捉虫笔记(七)-再探谁把系统卡住了

捉虫笔记&#xff08;七&#xff09;-再探谁把系统卡住 1、内核调试 在实体物理机上&#xff0c;内核调试的第一个门槛就是如何建立调试链接。 这里我选择的建立网络连接进行内核调试。 至于如何建立网络连接后续文章再和大家分享。 2、如何分析 在上一篇文章中&#xff0c;我们…

linux(redhat8)如何安装mysql8.0之rpmtar双版本(最新版)(内网)(离线)

一.环境 系统版本&#xff1a;Red Hat 8.5.0-20 Java环境&#xff1a;build 1.8.0_181-b13 MYSQL&#xff1a;8.x版本 二、查看内核版本 #查看内核版本&#xff0c;根据内核版本下载对应的安装包 cat /proc/version 三、安装方式 一、rpm包方式 一、下载安装包 1. 登录网…

【WRF后处理】WRF模拟效果评价及可视化:MB、RMSE、IOA、R

【WRF后处理】模拟效果评价及可视化 准备工作模型评价指标Python实现代码Python处理代码:导入站点及WRF模拟结果可视化图形及评价指标参考在气象和环境建模中(如使用 WRF 模型进行模拟),模型性能评价指标是用于定量评估模拟值与观测值之间偏差和拟合程度的重要工具。 本博客…

深度学习基础2

目录 1.损失函数 1.1 线性回归损失函数 1.1.1 MAE损失 1.1.2 MSE损失 1.1.3 SmoothL1Loss 1.2 CrossEntropyLoss 1.3 BCELoss 1.4. 总结 2.BP算法 2.1 前向传播 2.2 反向传播 2.2.1 原理 2.2.2. 链式法则 2.4 重要性 2.5 案例 2.5.1 数据准备 2.5.2 神经元计算…

STM32的CAN波特率计算

公式&#xff1a; CAN波特率 APB总线频率 / &#xff08;BRP分频器 1&#xff09;/ (SWJ BS1 BS2) SWJ一般为1。 例如STM32F407的&#xff0c;CAN1和CAN2都在在APB1下&#xff0c;频率是42000000 如果想配置成1M波特率&#xff0c;则计算公式为&#xff1a;

⭐ Unity 资源管理解决方案:Addressable_ Demo演示

一、使用Addressable插件的好处&#xff1a; 1.自动管理依赖关系 2.方便资源卸载 3.自带整合好的资源管理界面 4.支持远程资源加载和热更新 二、使用步骤 安装组件 1.创建资源分组 2.将资源加入资源组 3.打包资源 4.加载资源 三种方式可以加载 using System.Collections…

uniapp实现APP版本升级

App.vue 直接上代码 <script>export default {methods: {//APP 版本升级Urlupload() {// #ifdef APP-PLUSplus.runtime.getProperty(plus.runtime.appid, (info) > {// 版本号变量持久化存储getApp().globalData.version info.version;this.ToLoadUpdate(info.versi…

spark 写入mysql 中文数据 显示?? 或者 乱码

目录 前言 Spark报错&#xff1a; 解决办法&#xff1a; 总结一下&#xff1a; 报错&#xff1a; 解决&#xff1a; 前言 用spark写入mysql中&#xff0c;查看中文数据 显示?? 或者 乱码 Spark报错&#xff1a; Sat Nov 23 19:15:59 CST 2024 WARN: Establishing SSL…

欧科云链研究院:比特币还能“燃”多久?

出品&#xff5c; OKG Research 作者&#xff5c;Hedy Bi 本周二&#xff0c;隔夜“特朗普交易” 的逆转趋势波及到比特币市场。比特币价格一度冲高至约99,000美元后迅速回落至93,000美元以下&#xff0c;最大跌幅超6%。这是由于有关以色列和黎巴嫩有望达成停火协议的传闻引发…

27加餐篇:gRPC框架的优势与不足之处

gRPC作为一个现代的、开源的远程过程调用(RPC)框架,在多个方面都展现了其优雅之处,同时也存在一些不足之处。这篇文章我们就相对全面的分析一下gRPC框架那些优雅的地方和不足的地方。 优雅的地方 gRPC作为一个RPC框架,在编码、传输协议已经支持多语言方面都比较高效,下…