讲解关于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→官方认证

一、前言

通过上一篇博客,已经对 BoW 进行了理论讲解。其中有一个重要的图示:

假设上述为构建好的 BoW词袋,那么如何把一个特征点的 BRIEF 转行成 BoW向量呢? src/Frame.cc 文件,找到函数 void Frame::ComputeBoW()。可以看到如下代码:

     // 将特征点的描述子转换成词袋向量mBowVec以及特征向量mFeatVecmpORBvocabulary->transform(vCurrentDesc,  //当前的描述子vectormBowVec,          //输出,词袋向量,记录的是单词的id及其对应权重TF-IDF值mFeatVec,     //输出,记录node id及其对应的图像 feature对应的索引4);                //4表示从叶节点向前数的层数

该函数的功能,就是把一幅图所有的描述子转换成 BoW向量,也就是把 vCurrentDesc 转换成 mBowVec 以及 mFeatVec。这里先介绍以下 mBowVec 与 mFeatVec 代表什么.

DBoW2::BowVector mBowVec: 其可以存储多个元素,每个元素包含了两个值: 叶子节点 id,其对应的权重
DBoW2::FeatureVector mFeatVec: 其可以存储多个元素,每个元素包含了两个值: 第一个值为叶子节点(word)所属节点 id(注意,并非父节点id),其与参数levelsup相关;第二个值为特征点,或者说 BRIEF 描述子的索引。

转换流程 其上图中紫色的箭头线,就是转换的流程,首先输入的 BRIEF描述子 与根节点的所有之节点的 BRIEF描述子 描述子进行比较,也就是与图像的 Level1 的节点进行比较。这里注意一个点: 除了叶子(word)节点,其于节点的 BRIEF描述子 都是为其子节点的平均值。通过汉明距离比较之后,找到 Level1 中与输入 BRIEF描述子 最近的节点 称为 node1。那么下一步就是与 best_node1节点所有子节点进行比较,从中再找到汉明距离最近的节点 best_node2,这样依次循环,直到寻找到最匹配的叶子节 best_word4 点为止,找到了叶子节点,就知道叶子节点的 id 与 其对应的权重,就能构建一个元素存储于 mBowVec 之中。

如果如上图所示,levelsup 参数为3,那么 mFeatVec 元素的第一个值,就是 best_node1节点的 id; 第二个值就是输入 BRIEF 描述子对应特征点在整张图像中的索引。因为 levelsup=3,所以从叶子节点往上数3级,级为其所属节点。对应于源码中变量 nid(后面有源码讲解) 。

二、源码转换过程

下面来说说源码的转换流程,其实总的来说与上面的流程差不多,这里再详细的梳理一下:
( 1 ) : \color{blue}{(1)}: (1):根据权重的计算方式,运行不同的代码,主要存在四种计算权重的方式,分别为 TF_IDF,TF,IDF,BINARY。
( 2 ) : \color{blue}{(2)}: (2):取出根节点的所有子节点,赋值给 nodes,计算输入BRIEF描述子与第一个子节点,即 nodes[0] 的汉明距离。再循环与其与子节点的距离,找到最距离最近的一个节点。然后再取出该节点的所有子节点,赋值给 nodes,重复前面的流程。
( 3 ) : \color{blue}{(3)}: (3):在步骤(2)的循环过程中,会对当前汉明距离最近节点进行判断,如果其为倒数 levelsup 层级,则记录下该节点 id,对应于代码中的 nid。
( 4 ) : \color{blue}{(4)}: (4):当跳出代码中的do while 循环,则表示已经找到最匹配的叶子节点。
( 5 ) : \color{blue}{(5)}: (5):通过前面的步骤,已经获得了输入BRIEF描述子对应的子节点,以及所属节点的id,与之对应的权重,再进一步把这些信息添加到 mBowVec 与 mFeatVec 之中。对应于源码如下:

        // 如果Word 权重大于0,将其添加到BowVector 和 FeatureVectorv.addWeight(id, w);fv.addFeature(nid, i_feature);

这里大家注意一点,就是再添加的时候,其会对节点id进行排序,每次会找到合适的位置插入。

三、源码注释

该源码位于在 src/Frame.cc 中被调用,调用源码如下:

/*** @brief 计算当前帧特征点对应的词袋Bow,主要是mBowVec 和 mFeatVec* */
void Frame::ComputeBoW()
{// 判断是否以前已经计算过了,计算过了就跳过if(mBowVec.empty()){// 将描述子mDescriptors转换为DBOW要求的输入格式vector<cv::Mat> vCurrentDesc = Converter::toDescriptorVector(mDescriptors);// 将特征点的描述子转换成词袋向量mBowVec以及特征向量mFeatVecmpORBvocabulary->transform(vCurrentDesc,    //当前的描述子vectormBowVec,          //输出,词袋向量,记录的是单词的id及其对应权重TF-IDF值mFeatVec,     //输出,记录node id及其对应的图像 feature对应的索引4);                //4表示从叶节点向前数的层数}
}

其上的 transform 实现于 src/Thirdpaty/DBoW2/DBoW2/TemplatedVocabulary.h 之中:


// --------------------------------------------------------------------------
/*** @brief 将描述子转化为Word id, Word weight,节点所属的父节点id(这里的父节点不是叶子的上一层,它距离叶子深度为levelsup)* * @tparam TDescriptor            * @tparam F * @param[in] feature                 特征描述子* @param[in & out] word_id           Word id* @param[in & out] weight            Word 权重* @param[in & out] nid               记录当前描述子转化为Word后所属的 node id,它距离叶子深度为levelsup* @param[in] levelsup                距离叶子的深度*/
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 treevector<NodeId> nodes;typename vector<NodeId>::const_iterator nit;// level at which the node must be stored in nid, if given// m_L: depth levels, m_L = 6 in ORB-SLAM2// nid_level 当前特征点转化为的Word 所属的 node id,方便索引const int nid_level = m_L - levelsup;if(nid_level <= 0 && nid != NULL) *nid = 0; // rootNodeId final_id = 0; // rootint current_level = 0;do{// 更新树的深度++current_level;// 取出当前节点所有子节点的idnodes = m_nodes[final_id].children;// 取子节点中第1个的id,用于后面距离比较的初始值final_id = nodes[0];// 取当前节点第一个子节点的描述子距离初始化最佳(小)距离double best_d = F::distance(feature, m_nodes[final_id].descriptor);// 遍历nodes中所有的描述子,找到最小距离对应的描述子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;}}// 记录当前描述子转化为Word后所属的 node id,它距离叶子深度为levelsupif(nid != NULL && current_level == nid_level)*nid = final_id;} while( !m_nodes[final_id].isLeaf() );// turn node id into word id// 取出 vocabulary tree中node距离当前feature 描述子距离最小的那个node的 Word id 和 weightword_id = m_nodes[final_id].word_id;weight = m_nodes[final_id].weight;
}// --------------------------------------------------------------------------
/*** @brief 将一幅图像所有的特征点转化为BowVector和FeatureVector* * @tparam TDescriptor * @tparam F * @param[in] features      图像中所有的特征点* @param[in & out] v       BowVector* @param[in & out] fv      FeatureVector* @param[in] levelsup      距离叶子的深度*/
template<class TDescriptor, class F>
void TemplatedVocabulary<TDescriptor,F>::transform(const std::vector<TDescriptor>& features,BowVector &v, FeatureVector &fv, int levelsup) const
{v.clear();fv.clear();if(empty()) // safe for subclasses{return;}// normalize // 根据选择的评分类型来确定是否需要将BowVector 归一化LNorm norm;bool must = m_scoring_object->mustNormalize(norm);typename vector<TDescriptor>::const_iterator fit;if(m_weighting == TF || m_weighting == TF_IDF){unsigned int i_feature = 0;// 遍历图像中所有的特征点for(fit = features.begin(); fit < features.end(); ++fit, ++i_feature){WordId id;        // 叶子节点的Word idNodeId nid;       // FeatureVector 里的NodeId,用于加速搜索WordValue w;      // 叶子节点Word对应的权重//  将当前描述子转化为Word id, Word weight,节点所属的父节点id(这里的父节点不是叶子的上一层,它距离叶子深度为levelsup)// w is the idf value if TF_IDF, 1 if TF transform(*fit, id, w, &nid, levelsup);if(w > 0) // not stopped{ // 如果Word 权重大于0,将其添加到BowVector 和 FeatureVectorv.addWeight(id, w);fv.addFeature(nid, i_feature);}}if(!v.empty() && !must){// unnecessary when normalizingconst double nd = v.size();for(BowVector::iterator vit = v.begin(); vit != v.end(); vit++) vit->second /= nd;}}else // IDF || BINARY{unsigned int i_feature = 0;for(fit = features.begin(); fit < features.end(); ++fit, ++i_feature){WordId id;NodeId nid;WordValue w;// w is idf if IDF, or 1 if BINARYtransform(*fit, id, w, &nid, levelsup);if(w > 0) // not stopped{v.addIfNotExist(id, w);fv.addFeature(nid, i_feature);}}} // if m_weighting == ...if(must) v.normalize(norm);
}

四、结语

通过该片博客,了解了 BRIEF描述子 如果通过 词袋BoW 转换成 BoW向量。既然了解了 BoW向量 的来源,那么下面是探讨如何对其进行利用了。

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

(01)ORB-SLAM2源码无死角解析-(31) ORB特征匹配→词袋BoW:BRIEF描述子转BoW向量相关推荐

  1. (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  2. (01)ORB-SLAM2源码无死角解析-(55) 闭环线程→计算Sim3:总体流程讲解ComputeSim3()

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  3. (01)ORB-SLAM2源码无死角解析-(63) BA优化(g2o)→局部建图线程:Optimizer::LocalBundleAdjustment→位姿与地图点优化

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  4. (01)ORB-SLAM2源码无死角解析-(24) 单目SFM地图初始化→CreateInitialMapMonocular()-细节分析:尺度不确定性

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  5. (01)ORB-SLAM2源码无死角解析-(01) 环境搭建,demo运行,ROS一键安装_清除各种疑难杂症

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析-接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  6. (01)ORB-SLAM2源码无死角解析-(06) 图像金字塔_ORB特征点

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  7. (01)ORB-SLAM2源码无死角解析-(37) EPnP 算法原理详解→理论基础一:控制点选取、透视投影约束

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下: (01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲 ...

  8. (01)ORB-SLAM2源码无死角解析-(58) 闭环线程→计算Sim3: 源码Sim3Solver::iterate()讲解

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  9. (01)ORB-SLAM2源码无死角解析-(57) 闭环线程→计算Sim3:理论推导(2)求解R,使用四元数

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

最新文章

  1. 使用C++实现一套简单的状态机模型——实例
  2. 记一次小机器的 Python 大数据分析
  3. Python命名空间
  4. file extension php,.php File Extension
  5. 当前计算机领域中 有关计算机的性能指标,计算机文化基础真题(十七)
  6. 30分钟正则表达式入门
  7. Qt学习笔记-----信号槽
  8. Android 滑动效果基础篇(四)—— Gallery + GridView
  9. Delta3d角色注册机制
  10. linux挂载目录到分区,Ubuntu7.10下挂载/home目录到一个分区的方法
  11. 早买早享受 晚买有折扣 !特斯拉大降价致消费者拉横幅抗议
  12. python银行排队系统_python-我需要基于Web的系统的消息/排队解决...
  13. “出道” 5 年采用率达 78%,Kubernetes 为何如此成功?
  14. android 调用wcf json,使用 JSONP
  15. 微分方程和差分方程的区别与联系
  16. mysql 客户端可以访问_Mysql客户端工具可以连接,但是代码访问就会报错
  17. 处理Windows快捷键占用冲突
  18. 【Unity】Unity3D RPG游戏制作实例(二)开发思路及概要设计
  19. HRNet-v1模型,用于人体形态检测
  20. OSG路径漫游实现与应用

热门文章

  1. web 页面加载速度优化实战
  2. html文件转换为PDF文档
  3. L1-028 判断素数 (10 分)(C语言版)
  4. plc的移位指令C语言实现,移位指令做流水灯-PLC中使用移位指令是如何实现移位动作的-电气资讯 - 电工屋...
  5. linux 服务器常规巡检并生成报表(一)
  6. 光盘安装win7系统显示缺少所需的cd dvd驱动器设备驱动程序
  7. 勇者斗恶龙C++(附注释)
  8. 2021年重庆高考成绩排名查询,2021重庆市地区高考成绩排名查询,重庆市高考各高中成绩喜报榜单...
  9. ThinkPHP 6 数据库查询,where 、or、and
  10. SqueezeNet论文与源码阅读