讲解关于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以及特征向量mFeatVec
mpORBvocabulary->transform(vCurrentDesc, //当前的描述子vector
mBowVec, //输出,词袋向量,记录的是单词的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 和 FeatureVector
v.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 vCurrentDesc = Converter::toDescriptorVector(mDescriptors);
// 将特征点的描述子转换成词袋向量mBowVec以及特征向量mFeatVec
mpORBvocabulary->transform(vCurrentDesc, //当前的描述子vector
mBowVec, //输出,词袋向量,记录的是单词的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
void TemplatedVocabulary::transform(const TDescriptor &feature,
WordId &word_id, WordValue &weight, NodeId *nid, int levelsup) const
{
// propagate the feature down the tree
vector nodes;
typename vector::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 second /= nd;
}
}
else // IDF || BINARY
{
unsigned int i_feature = 0;
for(fit = features.begin(); fit 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 课程课件