ORB-SLAM2 ---- 词袋模型BOW

文章目录

  • 一、回环检测的重要性
  • 二、回环检测的方法
  • 三、词袋模型
  • 四、词典
  • 五、实例展示
    • 1. 计算评分
    • 2. 找出有相同单词的关键帧
    • 3. 用词袋进行快速匹配
  • 六、总结

一、回环检测的重要性

在前面的学习我们知道,噪声的影响是不可消除的,而上一帧的误差不可避免的会累积到下一帧,使得整个SLAM出现累计误差,长时间的累积会让后续的位姿和地图点位置产生很大的影响,即使我们有很好的后端模型来减少误差,但好模型架不住烂数据。在只有相邻数据时我们能做的事情并不多,但是如果能得到间隔更久远的约束,就可以将误差拉回来。这就是回环检测的意义,就可以将x1-x100之间的位姿进行联系,然后构建回环,将误差平摊到大量的关键帧之中。这个SLAM过程中,回环检测非常重要,以至于有些时候我们把仅有前端和后端的系统称为视觉里程计,而把带有回环检测和全局后端的系统称为SLAM 。

二、回环检测的方法

我们知道回环检测最重要的是检测到回环,我们怎么来检测这个回环?从原理上来说,就是要在之前的关键帧中找出与当前关键帧很相似的一幅图像。在前面提取特征点的时候用过灰度法,但是当时就说过灰度是不稳定的,灰度受到光线的影响严重,我们不能保证发生回环的几个关键帧的光线条件一致。用如下的公式来表示两个图像的差异,如果A图像的光线发生偏差,会导致结果很大,可能会将两个相似度很高的图像误认为是完全不匹配的图像。

这个时候就产生了一个问题,怎么样才能正确的判断两个图像是否相似?词袋模型就孕育而出了,词袋模型能很好的解决这个问题。

三、词袋模型

词袋,也就是Bag-of-Words(BoW),目的是用“图像上有哪几种特征”来描述一幅图像。例如,我们说某张照片中有一个人、一辆车;而另一张中有两个人、一只狗。根据这样的描述,就可以度量这两幅图像的相似性。再具体一些,我们要做以下三步:

  1. 确定“人”“车”“狗”等概念–对应于BoW中的“单词”(Word),许多单词放在一
    起,组成了“字典”(Dictionary )。
  2. 确定一幅图像中出现了哪些在字典中定义的概念-我们用单词出现的情况(或直方
    图)描述整幅图像。这就把一幅图像转换成了一个向量的描述。
  3. 比较上一步中的描述的相似程度。
    以上面举的例子来说,首先我们通过某种方式得到了一本“字典”。字典上记录了许多单词,每个单词都有一定意义,例如“人”“车”“狗”都是记录在字典中的单词,我们不妨记为 W1,w2,w3。然后,对于任意图像A,根据它们含有的单词,可记为:
    请添加图片描述

我们有两种方法描述一幅图片,第一种是用单词数量,第二种只用单词出没出现在该函数中,如[1,0,1],单词出现记为1,没出现记为0。无论是哪种方式都可以很好的描述一个图像的特征。我们可以用如下公式来表示两个图像的匹配度评分:
请添加图片描述
可以看出如果两个向量完全一致的时候,函数值为1(两幅图像相似度高),两个向量完全相反的时候函数值为0(两幅图像相似度低)。

四、词典

我们在局部建图和回环检测线程中,都会用到词袋,那词袋到底是什么?答案是一幅图像中包含的单词(Word)的集合。而词典是包含所有单词信息的一个库,再出现词袋的地方,都会提到快速匹配,但是一个一个单词进行比较匹配真的算得上快速匹配吗?如果单词是按照顺序一个一个排列得话当然不是,甚至在单词数量很大的时候,会出现匹配时间很长的情况。这里用到的是数据结构中的方法,能进行快速的索引。字典的生成类似于一个聚类的问题,我们将根节点分为k个树杈,每个树杈按照特点继续细分,这样每次进行匹配的时候,就只需要知道这个单词在那个叶节点(分到最后的一层,称之为叶)上,然后依次向上寻找匹配,这样效率将大大的提升,真正意义上的实现了快速匹配。
请添加图片描述

五、实例展示

1. 计算评分

下面这个代码片段是回环检测线程中检测回环函数中的片段,可以看出这里就是用了计算词袋相似度评分的方法,来判断候选帧和当前帧之间的相似情况。

// Step 3:遍历当前回环关键帧所有连接(>15个共视地图点)关键帧,计算当前关键帧与每个共视关键的bow相似度得分,并得到最低得分minScoreconst vector<KeyFrame*> vpConnectedKeyFrames = mpCurrentKF->GetVectorCovisibleKeyFrames();const DBoW2::BowVector &CurrentBowVec = mpCurrentKF->mBowVec;float minScore = 1;for(size_t i=0; i<vpConnectedKeyFrames.size(); i++){KeyFrame* pKF = vpConnectedKeyFrames[i];if(pKF->isBad())continue;const DBoW2::BowVector &BowVec = pKF->mBowVec;// 计算两个关键帧的相似度得分;得分越低,相似度越低float score = mpORBVocabulary->score(CurrentBowVec, BowVec);// 更新最低得分if(score<minScore)minScore = score;}

2. 找出有相同单词的关键帧

下面这个代码片段是DetectLoopCandidates()函数中的片段,可以看出它充分的使用了单词(word),找出和当前帧有相同单词的帧,这里就用到了词典的快速索引方法。

for(DBoW2::BowVector::const_iterator vit=pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit != vend; vit++){// 提取所有包含该word的KeyFramelist<KeyFrame*> &lKFs =   mvInvertedFile[vit->first];// 然后对这些关键帧展开遍历for(list<KeyFrame*>::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++){KeyFrame* pKFi=*lit;if(pKFi->mnLoopQuery!=pKF->mnId)    {// 还没有标记为pKF的闭环候选帧pKFi->mnLoopWords=0;// 和当前关键帧共视的话不作为闭环候选帧if(!spConnectedKeyFrames.count(pKFi)){// 没有共视就标记作为闭环候选关键帧,放到lKFsSharingWords里pKFi->mnLoopQuery=pKF->mnId;lKFsSharingWords.push_back(pKFi);}}pKFi->mnLoopWords++;// 记录pKFi与pKF具有相同word的个数}

3. 用词袋进行快速匹配

下面这个函数片段是跟踪线程中参考关键帧跟踪中的词袋快速匹配函数的片段,这里就利用词袋进行快速匹配,只有处于同一个note中的特征点才会进行匹配,这样就能大大的节省匹配时间。

const vector<cv::KeyPoint> &vKeysUn1 = pKF1->mvKeysUn;const DBoW2::FeatureVector &vFeatVec1 = pKF1->mFeatVec;const vector<MapPoint*> vpMapPoints1 = pKF1->GetMapPointMatches();const cv::Mat &Descriptors1 = pKF1->mDescriptors;const vector<cv::KeyPoint> &vKeysUn2 = pKF2->mvKeysUn;const DBoW2::FeatureVector &vFeatVec2 = pKF2->mFeatVec;const vector<MapPoint*> vpMapPoints2 = pKF2->GetMapPointMatches();const cv::Mat &Descriptors2 = pKF2->mDescriptors;// 保存匹配结果vpMatches12 = vector<MapPoint*>(vpMapPoints1.size(),static_cast<MapPoint*>(NULL));vector<bool> vbMatched2(vpMapPoints2.size(),false);// Step 2 构建旋转直方图,HISTO_LENGTH = 30vector<int> rotHist[HISTO_LENGTH];for(int i=0;i<HISTO_LENGTH;i++)rotHist[i].reserve(500);//! 原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码   const float factor = HISTO_LENGTH/360.0f;int nmatches = 0;DBoW2::FeatureVector::const_iterator f1it = vFeatVec1.begin();DBoW2::FeatureVector::const_iterator f2it = vFeatVec2.begin();DBoW2::FeatureVector::const_iterator f1end = vFeatVec1.end();DBoW2::FeatureVector::const_iterator f2end = vFeatVec2.end();while(f1it != f1end && f2it != f2end){// Step 3 开始遍历,分别取出属于同一node的特征点(只有属于同一node,才有可能是匹配点)if(f1it->first == f2it->first){// 遍历KF中属于该node的特征点for(size_t i1=0, iend1=f1it->second.size(); i1<iend1; i1++){const size_t idx1 = f1it->second[i1];MapPoint* pMP1 = vpMapPoints1[idx1];if(!pMP1)continue;if(pMP1->isBad())continue;const cv::Mat &d1 = Descriptors1.row(idx1);int bestDist1=256;int bestIdx2 =-1 ;int bestDist2=256;// Step 4 遍历KF2中属于该node的特征点,找到了最优及次优匹配点for(size_t i2=0, iend2=f2it->second.size(); i2<iend2; i2++){const size_t idx2 = f2it->second[i2];MapPoint* pMP2 = vpMapPoints2[idx2];// 如果已经有匹配的点,或者遍历到的特征点对应的地图点无效if(vbMatched2[idx2] || !pMP2)continue;if(pMP2->isBad())continue;const cv::Mat &d2 = Descriptors2.row(idx2);int dist = DescriptorDistance(d1,d2);if(dist<bestDist1){bestDist2=bestDist1;bestDist1=dist;bestIdx2=idx2;}else if(dist<bestDist2){bestDist2=dist;}}// Step 5 对匹配结果进行检查,满足阈值、最优/次优比例,记录旋转直方图信息if(bestDist1<TH_LOW){if(static_cast<float>(bestDist1)<mfNNratio*static_cast<float>(bestDist2)){vpMatches12[idx1]=vpMapPoints2[bestIdx2];vbMatched2[bestIdx2]=true;if(mbCheckOrientation){float rot = vKeysUn1[idx1].angle-vKeysUn2[bestIdx2].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(idx1);}nmatches++;}}}

六、总结

词袋模型是ORB-SLAM2中很重要的一个模型,他的作用也有很多,在上述一一列举过,最重要的作用是,在回环检测中匹配相似的图像,曾经该方法是最优的方法之一,现在各种机器学习的发展,例如深度学习,能更高效的完成相似图像的识别,这也是最近深度学习大量运用在SLAM中的原因。

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

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

相关文章

Android APP自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 Android导入已有外部数据库 2015.06.26在QQ空间记录&#xff1a;在Android中不能直接打开res aw目录中的数据…

php项目的sdk封装成composer包的创建与发版

将一个 PHP 项目的 SDK 封装成 Composer 包并发布的过程大致可以分为以下几个步骤。这个过程涉及到创建一个符合 Composer 规范的包&#xff0c;配置相关信息&#xff0c;并将其发布到 Packagist 或其他 Composer 仓库。以下是详细的步骤&#xff1a; ### 1. 准备 PHP SDK 项目…

STM32F103单片机使用STM32CubeMX新建IAR工程步骤

打开STM32CubeMX软件&#xff0c;选择File 选择新建工程 在打开的窗口输入单片机型号 在右下角选择单片机型号&#xff0c;然后点右上角 start project&#xff0c;开始新建工程。 接下来设置调试接口&#xff0c;在左边System Core中选择 SYS&#xff0c;然后在右右边debu…

轻量化特征融合 | YOLOv11 引入一种基于增强层间特征相关性的轻量级特征融合网络 | 北理工新作

本改进已同步到Magic框架 摘要—无人机图像中的小目标检测由于分辨率低和背景融合等因素具有挑战性,导致特征信息有限。多尺度特征融合可以通过捕获不同尺度的信息来增强检测,但传统策略效果不佳。简单的连接或加法操作无法充分利用多尺度融合的优势,导致特征之间的相关性不…

Tomcat项目本地部署

今天分享一下如何在本地&#xff0c;不依赖于idea部署聚合项目&#xff0c;以我做过的哈米音乐项目为例&#xff0c;项目结构如下&#xff1a; ham-core模块为公共模块&#xff0c;我们只需将另外三个模块&#xff1a;前台、后台、文件服务器打包&#xff0c;将打好的jar、war包…

进入保护模式

Intel CPU启动的时候是16位(实模式), 但是我们要工作在32位模式下 实模式下没有任何保护措施, 别人可能通过给数据段寄存器赋值上代码段地址, 然后来改变代码段的内容, 保护模式访问内容会检查权限之类的, 也会检查程序访问的内存范围是不是超了, 我们这个操作系统不会利用保…

MVC基础——市场管理系统(一)

文章目录 项目地址一、创建项目结构1.1 创建程序以及Controller1.2 创建View1.3 创建Models层,并且在Edit页面显示1.4 创建Layou模板页面1.5 创建静态文件css中间件二、Categories的CRUD2.1 使用静态仓库存储数据2.2 将Categorie的列表显示在页面中(List)2.3 创建_ViewImport.…

C#开发-集合使用和技巧(十)Union用法-并集

在 C# 中&#xff0c;IEnumerable 的 Union 方法用于返回两个序列的并集。Union 方法会去除重复的元素&#xff0c;确保结果集中每个元素都是唯一的。以下是 Union 方法的基本用法&#xff1a; 基本语法 public static IEnumerable<TSource> Union<TSource>(this…

高效查找的秘密武器二:布隆过滤器

最近学了这个布隆过滤器&#xff0c;所以小编来分享下这个神奇的数据结构 引入&#xff1a; 在我们日常生活中&#xff0c;当然这里特指是编程中时&#xff0c;经常遇到要判断一个元素是否在集合中&#xff0c;比如判断一个单词/词语&#xff0c;是否在已知的字典中&#xff1…

C++入门终

目录 一、引用 二、内联函数 三、auto关键字 四、指针空值nullptr 一、引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间 类型&引用变量名(对象名)…

C++实现排序算法:冒泡排序

目录 前言 冒泡排序性质 C代码实现冒泡排序 冒泡图解 第一趟排序 第二趟排序 第三趟排序 排序结果 结语 前言 冒泡排序的基本思想是通过从前往后&#xff08;从后往前&#xff09;两两比较&#xff0c;若为逆序&#xff08;即arr[i] < arr[i 1]&#xff09;则交换…

selenium+python实现12306自动化抢火车票(二)

往期回顾&#xff1a; seleniumpython实现12306自动化抢火车票&#xff08;一&#xff09; 1、根据乘车人姓名匹配&#xff0c;支持1人或多人选择 定位出所有乘车人的元素集&#xff0c;根据姓名集合去元素集里循环迭代匹配&#xff0c;匹配上了操作选中 ele_alldriver.find_e…

基于openzeppelin插件的智能合约升级

一、作用以及优点 部署可升级合约&#xff0c;插件自动部署proxy和proxyAdmin合约&#xff0c;帮助管理合约升级和交互&#xff1b;升级已部署合约&#xff0c;通过插件快速升级合约&#xff0c;脚本开发方便快捷&#xff1b;管理代理管理员的权限&#xff0c;只有proxyAdmin的…

游戏引擎学习第36天

仓库 :https://gitee.com/mrxiao_com/2d_game 回顾之前的内容 在这个程序中&#xff0c;目标是通过手动编写代码来从头开始制作一个完整的游戏。整个过程不使用任何库或现成的游戏引擎&#xff0c;这样做的目的是为了能够全面了解游戏执行的每一个细节。开发过程中&#xff0…

MySQL-设置utf8mb4字符集以支持全面的字符显示

本文主要介绍如何通过统一使用utf8mb4字符集来实现在MySQL实例中存储emoji表情的最佳实践。 我们将从客户端、会话连接和MySQL实例等多个方面介绍如何配置和修改字符集以支持utf8mb4。 客户端和会话连接的字符集配置 为了确保能够正确存储和显示emoji表情&#xff0c;我们首…

【Linux从青铜到王者】数据链路层(mac,arp)以及ip分片

局域网通信 通过之前的学习&#xff0c;我们了解了应用层&#xff0c;传输层&#xff0c;网络层的协议和作用&#xff0c;这里先做个总结 应用层——http&#xff0c;https协议&#xff0c;也可以自己定义一套&#xff0c;作用是进行数据的处理传输层——tcp&#xff0c;udp协…

基于STM32的风速风向传感器设计

目录 引言系统设计 硬件设计软件设计系统功能模块 风速采集模块风向采集模块数据处理与显示模块控制算法 风速数据处理算法风向数据处理算法代码实现 风速数据采集与处理风向数据采集与处理数据显示与通信系统调试与优化结论与展望 1. 引言 随着气象监测需求的增加&#xff0…

13.在 Vue 3 中使用OpenLayers加载鹰眼控件示例教程

在 WebGIS 开发中&#xff0c;鹰眼控件 是一个常用的功能&#xff0c;它可以为用户提供当前地图位置的概览&#xff0c;帮助更好地定位和导航。在本文中&#xff0c;我们将基于 Vue 3 的 Composition API 和 OpenLayers&#xff0c;创建一个简单的鹰眼控件示例。 效果预览 在最…

安装certbot(ubuntu系统)

安装nginx 更新软件包列表 sudo apt update 更新软件包列表 sudo apt install nginx 更新软件包列表 sudo systemctl status nginx 注意&#xff1a;强烈推荐使用&#xff0c;系统直接安装nginx&#xff0c;&#xff08;不推荐使用docker安装nginx&#xff09;为后续更简单…

【C语言】C语言的变量和声明系统性讲解

声明和定义的概念 在C语言中&#xff0c;**声明&#xff08;Declaration&#xff09;和定义&#xff08;Definition&#xff09;**是两个重要的基础概念&#xff0c;它们都涉及到变量、函数、结构体等的使用&#xff0c;但功能和作用存在明显区别&#xff1a; 声明&#xff1a…