ORB_SLAM3 TrackReferenceKeyFrame

news2024/11/16 9:20:48

TrackReferenceKeyFrame

  • 使用条件:
    1. 运动模型为空并且imu未初始化,说明是刚初始化完第一帧跟踪,或者已经跟丢了
    2. 当前帧重定位帧间隔很近,用重定位帧来恢复位姿
    3. 恒速云端模型跟踪失败

1.计算当前帧的描述子的Bow向量

mCurrentFrame.ComputeBoW();

DBoW2由一棵词汇树(Vocabulary Tree)、逆向索引表(Inverse Indexes)以及一个正向索引表(Direct Indexes)三个部分构成。对于词汇树,它是由一堆图像离线训练而来的,首先对训练的图像计算ORB特征点,然后把这些特征点放在一起,通过K-means对它们聚类, 将之分为k类。然后对每簇,再次通过K-means进行聚类。如此重复L次,就得到了深度为L的树,除了叶子之外,每个节点都有k个子节点。

ComputeBoW主要是通过transform函数将当前帧的描述子mDescriptors转换为mBowVecmFeatVec,其中:

  • mBowVec:[单词的Id,权重]
    • std::map<WordId, WordValue>
    • 单词的Id:为词汇树中距离最近的叶子节点的id
    • 权重:对于同一个单词,权重累加
  • mFeatVec:[Node的Id,对应的图像featureIds]
    • std::map<NodeId, std::vector<unsigned int> >
    • Node的Id:距离叶子节点深度为level up对应的node的Id
    • 对应的图像featureId:该节点下所有叶子节点对应的featureid
  • level up确定搜素范围:
    • 如果 level up越大,那么featureVecsize越大,搜索的范围越广,速度越慢;
    • 如果level up越小,那么featureVecsize越小,搜索的范围越小,速度越快。

在这里插入图片描述

template<class TDescriptor, class F>
void TemplatedVocabulary<TDescriptor,F>::transform(const TDescriptor &feature, 
  WordId &word_id, WordValue &weight, NodeId *nid, int levelsup) const
{ 
  // propagate the feature down the tree
  vector<NodeId> nodes;
  typename vector<NodeId>::const_iterator nit;

  // level at which the node must be stored in nid, if given
  const int nid_level = m_L - levelsup;
  if(nid_level <= 0 && nid != NULL) *nid = 0; // root

  NodeId final_id = 0; // root
  int current_level = 0;

  do
  {
    ++current_level;
    nodes = m_nodes[final_id].children;
    final_id = nodes[0];
 
    double best_d = F::distance(feature, m_nodes[final_id].descriptor);

    for(nit = nodes.begin() + 1; nit != nodes.end(); ++nit)
    {
      NodeId id = *nit;
      double d = F::distance(feature, m_nodes[id].descriptor);
      if(d < best_d)
      {
        best_d = d;
        final_id = id;
      }
    }
    
    if(nid != NULL && current_level == nid_level)
      *nid = final_id;
    
  } while( !m_nodes[final_id].isLeaf() );

  // turn node id into word id
  word_id = m_nodes[final_id].word_id;
  weight = m_nodes[final_id].weight;
}

2.通过SearchByBoW加速当前帧与参考帧之间的特征匹配

  1. 构造旋转直方图
        // 特征点角度旋转差统计用的直方图
        vector<int> rotHist[HISTO_LENGTH];
        for(int i=0;i<HISTO_LENGTH;i++)
            rotHist[i].reserve(500);

        // 将0~360的数转换到0~HISTO_LENGTH的系数
        //! 原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码  
        // const float factor = HISTO_LENGTH/360.0f;
        const float factor = 1.0f/HISTO_LENGTH;
  1. 对于pKFFFeatureVector ,对属于同一节点的ORB特征进行匹配

为什么用FeatureVector能加快搜索?从词汇树中可以看出,Node相对于Word为更加抽象的簇,一个Node下包含了许多的相似的Word。如果想比较两个东西,那么先用抽象特征进行粗筛,然后再逐步到具体的特征。两帧图像特征匹配类似,先对比FeatureVector中的Node,如果Node为同一节点,再用节点下features进行匹配,这样避免了所有特征点之间的两两匹配

        // 取出关键帧的词袋特征向量
        const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;
        // We perform the matching over ORB that belong to the same vocabulary node (at a certain level)
        // 将属于同一节点的ORB特征进行匹配
        DBoW2::FeatureVector::const_iterator KFit = vFeatVecKF.begin();
        DBoW2::FeatureVector::const_iterator Fit = F.mFeatVec.begin();
        DBoW2::FeatureVector::const_iterator KFend = vFeatVecKF.end();
        DBoW2::FeatureVector::const_iterator Fend = F.mFeatVec.end();

        while(KFit != KFend && Fit != Fend)
        {
            // Step 1:分别取出属于同一node的ORB特征点
            if(KFit->first == Fit->first) 
            {
            	...
                KFit++;
                Fit++;
            }
            else if(KFit->first < Fit->first)
            {
                // 对齐
                KFit = vFeatVecKF.lower_bound(Fit->first);
            }
            else
            {
                // 对齐
                Fit = F.mFeatVec.lower_bound(KFit->first);
            }
        }
  1. 对同一node,用KF地图点对应的ORB特征点F中的ORB特征点两两匹配,其条件:
    • 不能重复匹配vpMapPointMatches:如果vpMapPointMatches[realIdxF]NULL,说明已有匹配了,则不能再匹配
    • 最佳匹配距离小于阈值TH_LOW
    • 最佳匹配距离与次佳匹配距离的比值小于阈值mfNNratio
                // second 是该node内存储的feature index
                const vector<unsigned int> vIndicesKF = KFit->second;
                const vector<unsigned int> vIndicesF = Fit->second;

                // Step 2:遍历KF中属于该node的特征点
                for(size_t iKF=0; iKF<vIndicesKF.size(); iKF++)
                {
                    // 关键帧该节点中特征点的索引
                    const unsigned int realIdxKF = vIndicesKF[iKF];

                    // 取出KF中该特征对应的地图点
                    MapPoint* pMP = vpMapPointsKF[realIdxKF];

                    if(!pMP)
                        continue;

                    if(pMP->isBad())
                        continue;
                    // 取出关键帧KF中该特征对应的描述子
                    const cv::Mat &dKF= pKF->mDescriptors.row(realIdxKF); 

                    int bestDist1=256; // 最好的距离(最小距离)
                    int bestIdxF =-1 ;
                    int bestDist2=256; // 次好距离(倒数第二小距离)

                    int bestDist1R=256;
                    int bestIdxFR =-1 ;
                    int bestDist2R=256;
                    // Step 3:遍历F中属于该node的特征点,寻找最佳匹配点
                    for(size_t iF=0; iF<vIndicesF.size(); iF++)
                    {
                        if(F.Nleft == -1){
                            // 这里的realIdxF是指普通帧该节点中特征点的索引
                            const unsigned int realIdxF = vIndicesF[iF];

                            // 如果地图点存在,说明这个点已经被匹配过了,不再匹配,加快速度
                            if(vpMapPointMatches[realIdxF])
                                continue;
                            // 取出普通帧F中该特征对应的描述子
                            const cv::Mat &dF = F.mDescriptors.row(realIdxF);
                            // 计算描述子的距离
                            const int dist =  DescriptorDistance(dKF,dF);

                            // 遍历,记录最佳距离、最佳距离对应的索引、次佳距离等
                            // 如果 dist < bestDist1 < bestDist2,更新bestDist1 bestDist2
                            if(dist<bestDist1)
                            {
                                bestDist2=bestDist1;
                                bestDist1=dist;
                                bestIdxF=realIdxF;
                            }
                            // 如果bestDist1 < dist < bestDist2,更新bestDist2
                            else if(dist<bestDist2)
                            {
                                bestDist2=dist;
                            }
                        }
                        else{
                            const unsigned int realIdxF = vIndicesF[iF];

                            if(vpMapPointMatches[realIdxF])
                                continue;

                            const cv::Mat &dF = F.mDescriptors.row(realIdxF);

                            const int dist =  DescriptorDistance(dKF,dF);

                            if(realIdxF < F.Nleft && dist<bestDist1){
                                bestDist2=bestDist1;
                                bestDist1=dist;
                                bestIdxF=realIdxF;
                            }
                            else if(realIdxF < F.Nleft && dist<bestDist2){
                                bestDist2=dist;
                            }

                            if(realIdxF >= F.Nleft && dist<bestDist1R){
                                bestDist2R=bestDist1R;
                                bestDist1R=dist;
                                bestIdxFR=realIdxF;
                            }
                            else if(realIdxF >= F.Nleft && dist<bestDist2R){
                                bestDist2R=dist;
                            }
                        }

                    }
                    // Step 4:根据阈值 和 角度投票剔除误匹配
                    // Step 4.1:第一关筛选:匹配距离必须小于设定阈值
                    if(bestDist1<=TH_LOW)
                    {
                        // Step 4.2:第二关筛选:最佳匹配比次佳匹配明显要好,那么最佳匹配才真正靠谱
                        if(static_cast<float>(bestDist1)<mfNNratio*static_cast<float>(bestDist2))
                        {
                            // Step 4.3:记录成功匹配特征点的对应的地图点(来自关键帧)
                            vpMapPointMatches[bestIdxF]=pMP;

                            // 这里的realIdxKF是当前遍历到的关键帧的特征点id
                            const cv::KeyPoint &kp =
                                    (!pKF->mpCamera2) ? pKF->mvKeysUn[realIdxKF] :
                                    (realIdxKF >= pKF -> NLeft) ? pKF -> mvKeysRight[realIdxKF - pKF -> NLeft]
                                                                : pKF -> mvKeys[realIdxKF];
                            // Step 4.4:计算匹配点旋转角度差所在的直方图
                            if(mbCheckOrientation)
                            {
                                cv::KeyPoint &Fkp =
                                        (!pKF->mpCamera2 || F.Nleft == -1) ? F.mvKeys[bestIdxF] :
                                        (bestIdxF >= F.Nleft) ? F.mvKeysRight[bestIdxF - F.Nleft]
                                                            : F.mvKeys[bestIdxF];
                                // 所有的特征点的角度变化应该是一致的,通过直方图统计得到最准确的角度变化值
                                float rot = kp.angle-Fkp.angle;
                                if(rot<0.0)
                                    rot+=360.0f;
                                int bin = round(rot*factor);// 将rot分配到bin组, 四舍五入, 其实就是离散到对应的直方图组中
                                if(bin==HISTO_LENGTH)
                                    bin=0;
                                assert(bin>=0 && bin<HISTO_LENGTH);
                                rotHist[bin].push_back(bestIdxF);
                            }
                            nmatches++;
                        }

                        if(bestDist1R<=TH_LOW)
                        {
                            if(static_cast<float>(bestDist1R)<mfNNratio*static_cast<float>(bestDist2R) || true)
                            {
                                vpMapPointMatches[bestIdxFR]=pMP;

                                const cv::KeyPoint &kp =
                                        (!pKF->mpCamera2) ? pKF->mvKeysUn[realIdxKF] :
                                        (realIdxKF >= pKF -> NLeft) ? pKF -> mvKeysRight[realIdxKF - pKF -> NLeft]
                                                                    : pKF -> mvKeys[realIdxKF];

                                if(mbCheckOrientation)
                                {
                                    cv::KeyPoint &Fkp =
                                            (!F.mpCamera2) ? F.mvKeys[bestIdxFR] :
                                            (bestIdxFR >= F.Nleft) ? F.mvKeysRight[bestIdxFR - F.Nleft]
                                                                : F.mvKeys[bestIdxFR];

                                    float rot = kp.angle-Fkp.angle;
                                    if(rot<0.0)
                                        rot+=360.0f;
                                    int bin = round(rot*factor);
                                    if(bin==HISTO_LENGTH)
                                        bin=0;
                                    assert(bin>=0 && bin<HISTO_LENGTH);
                                    rotHist[bin].push_back(bestIdxFR);
                                }
                                nmatches++;
                            }
                        }
                    }

                }
  1. 旋转一致检测,剔除不一致的匹配
    在这里插入图片描述
        // Step 5 根据方向剔除误匹配的点
        if(mbCheckOrientation)
        {
            // index
            int ind1=-1;
            int ind2=-1;
            int ind3=-1;

            // 筛选出在旋转角度差落在在直方图区间内数量最多的前三个bin的索引
            ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3);

            for(int i=0; i<HISTO_LENGTH; i++)
            {
                // 如果特征点的旋转角度变化量属于这三个组,则保留
                if(i==ind1 || i==ind2 || i==ind3)
                    continue;

                // 剔除掉不在前三的匹配对,因为他们不符合“主流旋转方向”  
                for(size_t j=0, jend=rotHist[i].size(); j<jend; j++)
                {
                    vpMapPointMatches[rotHist[i][j]]=static_cast<MapPoint*>(NULL);
                    nmatches--;
                }
            }
        }

3.将上一帧的位姿作为当前帧的位姿的初值

    mCurrentFrame.mvpMapPoints = vpMapPointMatches;
    mCurrentFrame.SetPose(mLastFrame.GetPose());  

4.通过最小化重投影误差优化当前帧位姿

Optimizer::PoseOptimization(&mCurrentFrame);

参考ORB_SLAM3 TrackWithMotionModel中的PoseOptimization

5.剔除优化后匹配中的外点

    int nmatchesMap = 0;
    for(int i =0; i<mCurrentFrame.N; i++)
    {
        if(mCurrentFrame.mvpMapPoints[i])
        {
            // 如果对应到的某个特征点是外点
            if(mCurrentFrame.mvbOutlier[i])
            {
                MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];
                mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);
                mCurrentFrame.mvbOutlier[i]=false;
                if(i < mCurrentFrame.Nleft){
                    pMP->mbTrackInView = false;
                }
                else{
                    pMP->mbTrackInViewR = false;
                }
                pMP->mbTrackInView = false;
                pMP->mnLastFrameSeen = mCurrentFrame.mnId;
                nmatches--;
            }
            else if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)
                // 匹配的内点计数++
                nmatchesMap++;
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用ArcGIS Pro制作间断标注等高线

如果直接对ArcGIS Pro生成的等高线进行标注&#xff0c;默认情况下是标注在等高线上&#xff0c;这样效果不是很明显也不是很美观&#xff0c;我们可以对默认的等高线标注进行处理&#xff0c;使其标注范围内的等高线不显示&#xff0c;这里为大家介绍一下这种间断标注等高线的…

SpringBoot的static静态资源访问、参数配置、代码自定义访问规则

目录 1. 静态资源1.1 默认静态资源1.2 Controller高优先级1.3 修改静态资源的URL根路径1.4 修改静态资源的目录1.5 访问webjars依赖包的静态资源1.6 静态资源的关闭1.7 静态资源在浏览器的缓存1.8 静态资源实战1.9 通过代码自定义静态资源访问规则 1. 静态资源 查看源码如下&a…

RLHF 技术:如何能更有效?又有何局限性?

编者按&#xff1a;自ChatGPT推出后&#xff0c;基于人类反馈的强化学习(RLHF)技术便成为大模型构建和应用人员关注的热点。但该方法一些情况下效果却差强人意&#xff0c;有些基础模型经RLHF调优后反而表现更差。RLHF技术的适用性和具体操作细节似乎成谜。 这篇文章探讨了基于…

分享 13 个有用的 JavaScript 片段,提升你的工作效率

JavaScript 是您可以学习的最流行的语言之一。当我开始学习 JavaScript 时&#xff0c;我总是在 StackOverflow、medium 和其他博客上寻找代码片段。在这篇文章中&#xff0c;我将分享我发现它们有用的 15 个 JavaScript 代码片段。 1. 不循环地重复字符串 此 JS 片段将展示如何…

<C++> STL_string

目录 1.string类 2.string类的接口 2.1 成员函数 2.1.1 string构造函数 2.1.2 string赋值运算 2.1.3 string析构函数 2.2 string对象访问以及迭代器 2.2.1 string的遍历方式 2.2.2 迭代器的使用 2.2.3 const_迭代器的使用 2.2.4 at 2.2.5 back和front 2.3 string容…

手机防窥膜对眼睛危害非常大,快速避坑,避免智商税!

背景 如果你的手机贴了防窥膜&#xff0c;在室外阳光下你想看清楚机屏幕上的文字&#xff0c;是不是有个动作就是调亮屏幕&#xff01;因为防窥膜透光率比较低&#xff0c;那你就得提高手机亮度。 国产的防窥膜透光率只有30%左右韩国进口防窥膜的透光率在50%左右 透光率越低意味…

Semantic Kernel 入门系列:Kernel 内核和Skills 技能

理解了LLM的作用之后&#xff0c;如何才能构造出与LLM相结合的应用程序呢&#xff1f; 首先我们需要把LLM AI的能力和原生代码的能力区分开来&#xff0c;在Semantic Kernel&#xff08;以下简称SK&#xff09;&#xff0c;LLM的能力称为 semantic function &#xff0c;代码的…

innovus设置size only的方法

dbSet [dbGetInstByName $inst].dontTouch sizeOk 我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口

【BMC】OpenBMC使用基础(WSL2版本)

代码准备 OpenBMC是一个开源的项目&#xff0c;用于开发BMC固件。官网是https://www.openbmc.org/&#xff0c;不过里面似乎没有什么内容&#xff0c;所以还需要依赖其它的网站&#xff0c;https://github.com/openbmc&#xff0c;在这里可以下载到需要的代码和文档。其主体部…

工作中遇到的关于配置问题

工作中遇到的问题 想记录一下 一个程序员小白每天遇到的问题 1.创建了一个Maven的web工程&#xff0c;但是启动一直是404&#xff0c;原服务器未能找到目标资源 解决办法&#xff1a; 选择deployment&#xff0c;点击加号选择war格式就OK啦 目录里面无法创建类&#xff0…

如何在面试IT公司时展现出色的表现

在面试IT技术岗位的过程中&#xff0c;展现出色的表现是至关重要的。下面我将分享一些我个人的经验和观察&#xff0c;希望对大家有所帮助。 首先&#xff0c;提前准备是非常重要的。在面试前&#xff0c;你应该充分了解目标公司的业务和技术需求。这样你就能更好地回答面试官…

一些高频的C++ cache line面试

C那些事之False Sharing与Cache line 最近看到一段代码&#xff0c;手动做的对齐&#xff0c;于是研究一下不对齐又会带来什么影响&#xff1f; template <typename T> class AtomicWithPadding {private:static constexpr int kCacheLineSize 64;uint8_t padding_befor…

HTML+CSS+JavaScript:轮播图的自动播放、手动播放、鼠标悬停暂停播放

一、需求 昨天我们做了轮播图的自动播放&#xff0c;即每隔一秒自动切换一次 今天我们增加两个需求&#xff1a; 1、鼠标点击向右按钮&#xff0c;轮播图往后切换一次&#xff1b;鼠标点击向左按钮&#xff0c;轮播图往前切换一次 2、鼠标悬停在轮播图区域中时&#xff0c;…

闲鱼链接生成 仿闲鱼链接搭建

教程&#xff1a;修改数据库账号密码直接使用。 源码带有教程! 下载程序&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

layui框架学习(36:数据表格_复杂表头)

table数据表格模块中的col属性支持配置复杂表头&#xff0c;其为二维数组&#xff0c;用于描述复杂表头中每个表头单元格的位置和尺寸信息&#xff08;colspan和rowspan描述表头单元格所占行数和列数&#xff09;。   从参考文献2-3给出的示例来看&#xff0c;描述复杂表头的…

[数学公式] 1秒移动x米是多少码 x码一秒钟移动几米

1秒移动x米是多少码&#xff1a;3.6x码 x码一秒钟移动几米&#xff1a;

6、用restful风格写controller方法接口,单元测试依赖

编写单元测试&#xff0c;用restful风格写controller方法 单元测试依赖 实际项目开发中&#xff0c;单元测试与业务代码通常都会要求同步进行 TDD测试驱动开发&#xff1a;先编写单元测试&#xff0c;然后努力去开发业务代码去满足所有的单元测试用例。 添加SpringBoot的测试…

LeetCode_双指针_中等_143.重排链表

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → L~n - 1~ → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → L~n - 1~ → L2 → L~n - 2~ → … 不…

python网站创建006:常见CSS样式

1. 给标签添加样式有三种方式(在标签上添加、在head中添加、通过独立文件添加)。 其中(在head中添加、通过独立文件添加)是有选择器存在的 直接在标签上添加 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><tit…

如何部署Redis哨兵

目录 一、Redis数据库 二、Redis哨兵模式 三、部署Redis哨兵 第一步 关闭防火墙和安全机制 第二步 修改Redis配置文件 第三步 开启Master主节点 第四步 查看哨兵信息 一、Redis数据库 ●主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主…