作者丨卢涛@知乎

来源丨https://zhuanlan.zhihu.com/p/354616831

编辑丨3D视觉工坊

非完整版注释:https://github.com/smilefacehh/ORB-SLAM3-Note

orb-slam3与2的区别不大,本系列文章代码取自orb-slam3,但概念流程都一样,所以后面不加区分。这篇文章讲一下词袋模型BoW,它主要用于两帧2d-2d匹配加速,以及在历史关键帧中搜索最相近的帧(闭环检测)。本文内容包括kd树创建词典、单词的权重TF-IDF、词向量相似度计算、基于词典计算新帧的词向量和正逆向索引、正向索引和逆向索引的应用。如果有理解上的错误,请您指正。

kd树创建词典

BoW,Bag of Words,词袋。姑且认为word等同于一个特征点,它是若干特征点的聚类中心,当然还是一个特征点,只不过若干个接近的特征点,都映射到同一个特征点,给它起个新的名字叫做word。那么一帧图像,若干个特征点,可以映射得到若干个word,word集合就是BoW。

那么,如何将特征点映射得到word呢。上面说过word是局部范围内特征点的聚类中心,那么需要进行聚类操作。比如有N个特征点,希望聚类成M个word,M < N。通过K-means聚类,对N个特征点进行聚类,这些特征点来自大量图像提取的fast特征,数量庞大,一次聚类成M个中心,会很慢。那我们少次多量,一次聚类k个,k < M。得到k个簇之后,再对每个簇继续划分k个。执行d次,就得到了最终需要的word数量。

上面的过程是在创建词典,实际是一个kd树的过程。kd树一共d层,每层k个节点。叶子节点就是word,非叶子节点就是少量多次聚类操作的聚类中心,就是最具代表性的那个特征点。

词典创建完了,怎么用呢。新帧的特征点通过kd树查找得到对应的word,所有word构成BoW。除了计算BoW,还需要维护和更新两个信息,正向索引(Direct Index)、逆向索引(Inverse Index)。

kd数构建词典

结合上图对这两个概念进行说明。orb-slam3中维护了一个关键帧数据库,每次新增一个关键帧,都会通过kd树计算BoW,同时更新正向索引和逆向索引。每个单词拥有一个逆向索引表,记录包含该单词的帧,和权重。那么假设我要在关键帧数据库中,找到与当前帧最相似的一帧,只需要找与当前帧共享单词的这些帧(逆向索引表记录下来了),统计他们与当前帧共享单词的总数,取总数最大的那一帧即可。

正向索引则是针对每一帧而言,每帧图像有一个逆向索引表,记录在kd树某一层,命中的节点集合,以及节点中的特征点。如上图,在l=1层,图像1包含id为65、10、32的特征点,分别划分到了Node3、Node4节点中,记录这么一个关系。它主要用于加速两帧特征点匹配,显然两帧对应的匹配点会落入相同的节点中,这样的话,只需要对两帧相同节点中的特征点进行匹配即可。

单词的权重TF-IDF

首先说明一下,IDF是在构建词典的时候计算好,TF是在对新帧计算词向量的时候计算的,TF*IDF就是最终单词的权重,也就是单词的值。

IDF(Inverse Document Frequency),某个单词在词典中出现的频率越低,则辨识度越高,相应权重IDF会大一些。

template<class TDescriptor, class F>
void TemplatedVocabulary<TDescriptor,F>::setNodeWeights(const vector<vector<TDescriptor> > &training_features)
{const unsigned int NWords = m_words.size();const unsigned int NDocs = training_features.size();else if(m_weighting == IDF || m_weighting == TF_IDF){// 遍历所有帧for(mit = training_features.begin(); mit != training_features.end(); ++mit){// 遍历一帧featurefor(fit = mit->begin(); fit < mit->end(); ++fit){WordId word_id;transform(*fit, word_id);// 统计单词出现次数if(!counted[word_id]){Ni[word_id]++;counted[word_id] = true;}}}// 计算IDF = log(N/Ni)for(unsigned int i = 0; i < NWords; i++){if(Ni[i] > 0){m_words[i]->weight = log((double)NDocs / (double)Ni[i]);}}}
}

TF(Term Frequency),指在单帧中某个单词的频次,频次高,权重大。(emm..)

对于新帧计算BoW,它的权重就是TF*IDF。DBoW2里面,TF设置为1了。

词向量相似度计算

词向量就是单词的集合,可以表示成one-hot向量的形式。但是因为给定词典,单词的id都是固定的,所以只存命中的单词id、权重即可。

class BowVector:public std::map<WordId, WordValue>

计算两帧图像的相似度,等价于计算两个词向量的相似度。DBoW2库里面定义了6种计算词向量相似度的方法,具体实现可以看看代码,不是很难。

double L1Scoring::score(const BowVector &v1, const BowVector &v2) const;
double L2Scoring::score(const BowVector &v1, const BowVector &v2) const;
double ChiSquareScoring::score(const BowVector &v1, const BowVector &v2) const;
double KLScoring::score(const BowVector &v1, const BowVector &v2) const;
double BhattacharyyaScoring::score(const BowVector &v1, const BowVector &v2) const;
double DotProductScoring::score(const BowVector &v1, const BowVector &v2) const;

基于词典计算新帧的词向量、正逆索引

正向索引加速两帧2d-2d匹配,逆向索引加速查找匹配帧,通常应用于闭环检测。

通过已经构建好的ORB词典,对一帧描述子,计算词向量和正向索引。

DBoW2::BowVector mBowVec;
DBoW2::FeatureVector mFeatVec;class BowVector:public std::map<WordId, WordValue>
class FeatureVector:public std::map<NodeId, std::vector<unsigned int> >// 根据已有词典,计算一帧的词向量、正向索引
void KeyFrame::ComputeBoW()
{if(mBowVec.empty() || mFeatVec.empty()){vector<cv::Mat> vCurrentDesc = Converter::toDescriptorVector(mDescriptors);mpORBvocabulary->transform(vCurrentDesc,mBowVec,mFeatVec,4);}
}

逆向索引。KeyFrameDatabase维护了一个逆向索引map,记录每个单词中落入的关键帧。

// 新增一帧关键帧,更新逆向索引
void KeyFrameDatabase::add(KeyFrame *pKF)
{unique_lock<mutex> lock(mMutex);for(DBoW2::BowVector::const_iterator vit= pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit!=vend; vit++)mvInvertedFile[vit->first].push_back(pKF);
}

正向索引、逆向索引的应用

利用正向索引加速两帧匹配,假设在kd树的第4层(叶子节点是0层),事先统计好了特征点落入的节点,帧1 = {node1, node3, ..., node12},帧2 = {node2, node3, ..., node11}。那么不需要逐一比较两帧的特征点,只需要先找到相同的节点,在节点里面再去逐一比较特征点。

优化效率:假设100个特征点,平均分配到20个节点中,节点的重合率是80%,那么优化后比较次数是(100/20)^2*20*0.8 = 400,优化前是100^2 = 10000。如果分配到50个节点中,优化后是(100/50)^2*50*0.8 = 160。优化还是比较明显的。

int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches)
{...const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;...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();// 遍历两帧对应的node集合while(KFit != KFend && Fit != Fend){// 相同的node里面,注意比较特征点if(KFit->first == Fit->first){const vector<unsigned int> vIndicesKF = KFit->second;const vector<unsigned int> vIndicesF = Fit->second;...}else if(KFit->first < Fit->first){KFit = vFeatVecKF.lower_bound(Fit->first);}else{Fit = F.mFeatVec.lower_bound(KFit->first);}}...
}

利用逆向索引确定候选匹配闭环帧。遍历当前帧的单词集合,对于每个单词,它里面落入了许多历史关键帧,对这些帧计数+1,表示与当前帧共享一个单词,统计完当前帧的所有单词之后,取共享数量最多的那一帧,就是与当前帧最接近的一帧了。

vector<KeyFrame*> KeyFrameDatabase::DetectLoopCandidates(KeyFrame* pKF, float minScore)
{set<KeyFrame*> spConnectedKeyFrames = pKF->GetConnectedKeyFrames();list<KeyFrame*> lKFsSharingWords;// 遍历当前帧的单词集合for(DBoW2::BowVector::const_iterator vit=pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit != vend; vit++){// 落入该单词中的历史关键帧(通过逆向索引得到node里面的帧)list<KeyFrame*> &lKFs = mvInvertedFile[vit->first];// 遍历落入该单词中的历史关键帧for(list<KeyFrame*>::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++){KeyFrame* pKFi=*lit;...lKFsSharingWords.push_back(pKFi);pKFi->mnLoopWords++;}}// 统计历史关键帧中与当前帧共享单词数量最多的一帧int maxCommonWords=0;for(list<KeyFrame*>::iterator lit=lKFsSharingWords.begin(), lend= lKFsSharingWords.end(); lit!=lend; lit++){if((*lit)->mnLoopWords>maxCommonWords)maxCommonWords=(*lit)->mnLoopWords;}...
}

参考

1.《视觉Slam十四讲》,高翔

2."Bags of Binary Words for Fast Place Recognition in Image Sequences"

http://doriangalvez.com/papers/GalvezTRO12.pdf

3.小葡萄:[ORB-SLAM2] 回环&DBoW视觉词袋

https://zhuanlan.zhihu.com/p/61850005

本文仅做学术分享,如有侵权,请联系删文。

下载1

在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。

下载2

在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。

下载3

在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近3000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

圈里有高质量教程资料、可答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

ORB-SLAM3中的词袋模型BoW相关推荐

  1. 词向量之词袋模型(BOW)详解

    目录 前言 词袋模型 词袋模型的作用 词袋模型的实现 前言   自然语言处理面临的文本数据往往是非结构化杂乱无章的文本数据,而机器学习算法处理的数据往往是固定长度的输入和输出.因而机器学习并不能直接处 ...

  2. 词袋模型BoW和词集模型SoW比较

    Bag-of-Words词袋模型,经常用在自然语言处理和信息检索当中.在词袋模型中,一篇文本(文章)被表示成"装着词的袋子",也就是说忽略文章的词序和语法,句法;将文章看做词的组合 ...

  3. SLAM笔记(七)回环检测中的词袋BOW

    1.词频 (摘自阮一峰博客,参见附录参考) 如果某个词很重要,它应该在这篇文章中多次出现.于是,我们进行"词频"(Term Frequency,缩写为TF)统计.考虑到文章有长短之 ...

  4. 如何设计好词袋模型BoW模型的类类型

    回顾过去自己写过的一些词袋模型,比如BoW图像检索Python实战.图像检索(CBIR)三剑客之BoF.VLAD.FV以及Bag of Words cpp实现,这些写出来的要么只是助于自己理解词袋模型 ...

  5. 词袋模型BoW图像检索Python实战

    前几天把HABI哈希图像检索工具包更新到V2.0版本后,小白菜又重新回头来用Python搞BoW词袋模型,一方面主要是练练Python,另一方面也是为了CBIR群开讲的关于图像检索群活动第二期而准备的 ...

  6. 词袋 图像检索 matlab,词袋模型BoW图像检索Python实战

    前几天把HABI哈希图像检索工具包更新到V2.0版本后,小白菜又重新回头来用Python搞BoW词袋模型,一方面主要是练练Python,另一方面也是为了CBIR群开讲的关于图像检索群活动第二期而准备的 ...

  7. 文本表示(Text Representation)之词集模型(SOW)词袋模型(BOW)TF-IDF模型

    转载请注明来源 http://blog.csdn.net/Recall_Tomorrow/article/details/79488639 欢迎大家查看这些模型简单实现的代码--     \ \ \ ...

  8. 视觉词袋模型BOW学习笔记及matlab编程实现

    sift和surf特征提取代码 转载 http://blog.csdn.net/dulingtingzi/article/details/51223732 首先插入两个非常好的介绍BOW的ppt: 1 ...

  9. 机器学习基础(二)——词集模型(SOW)和词袋模型(BOW)

    (1)词集模型:Set Of Words,单词构成的集合,集合自然每个元素都只有一个,也即词集中的每个单词都只有一个 (2)词袋模型:Bag Of Words,如果一个单词在文档中出现不止一次,并统计 ...

最新文章

  1. seal report mysql_Seal Report开放数据库报表工具(.Net)
  2. 开启windows ping端口功能
  3. 【Android 应用开发】Activity 任务亲和性 taskAffinity 设置 ( taskAffinity 属性 )
  4. pr扫光转场插件_2020年最新pr转场特效:300套模板+200集视频教程+插件,送你参考...
  5. gnokii 中文安装使用文档
  6. 面对这么多困境,梅耶尔还能怎么应对?
  7. Apache伪静态学习
  8. 蓝桥杯2015初赛-奖券数目-枚举
  9. MySQL 慌了!这个分库分表方法论,要火了?
  10. PB市净率,PE市盈率
  11. synchronized 详解,偏向锁,轻量锁,重量锁
  12. 2018暑假第二周总结(7.16-7.22)
  13. 如何使用Python3连接MySQL
  14. 递归算法经典实例python-python实现汉诺塔递归算法经典案例
  15. 一个简单的python爬虫,以豆瓣妹子“http://www.dbmeizi.com/category/2?p= ”为例
  16. vnr光学识别怎么打开_物流仓库安防监控系统安装的作用和功能
  17. “ 一条路走到黑,不撞南墙不回头”——C++【DFS(深度优先搜索算法)】入门讲解
  18. 浏览器地址栏无法直接使用Google搜索问题
  19. ubuntu 为可执行程序创建启动图标
  20. js 跨域下载图片解决方法

热门文章

  1. 风格化半调效果如何制作?教程来了
  2. 史上最被低估的神级学科,看完忍不住感慨“它”也太重要了!
  3. h5页面如何预览excel文件_kintone excel预览插件
  4. 重新安装opencv,踩坑。
  5. 搜狗输入法怎么打印间隔号
  6. sel2000服务器自动关闭,管家婆辉煌:轻松处理SQL 2000数据库自动停止
  7. Super expression must either be null or a function错误
  8. node-Buffer(缓冲区)文件写入
  9. 微型计算机原理计算机钢琴,用汇编语言编写计算机钢琴程序.PDF
  10. Unbuntu18.04系统定制化制作与再生龙镜像生成