讲解关于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<cv::Mat> 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<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
// 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; // root
NodeId final_id = 0; // root
int current_level = 0;
do
{
// 更新树的深度
++current_level;
// 取出当前节点所有子节点的id
nodes = 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,它距离叶子深度为levelsup
if(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 和 weight
word_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 id
NodeId 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 和 FeatureVector
v.addWeight(id, w);
fv.addFeature(nid, i_feature);
}
}
if(!v.empty() && !must)
{
// unnecessary when normalizing
const 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 BINARY
transform(*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 课程课件