DBoW2中的词典创建原理k-means++算法及代码分析
ORBSLAM中的词典是通过一个145.3MB的txt文件加载的,VINS-Mono中的词典是通过一个60MB的bin文件加载的,它们都是事先已经生成好的词典,我们需要用到的时候直接按照相应的格式调用就可以了。本文主要讲述的是我们如何通过手头上的图像,自己去创建一个词典。至于应该选取多少图像,选取什么场景下的图像才能创建出稳健的词典,这里就不讨论了。
1.k-means++算法简介
k均值聚类算法(k-means)是一种迭代求解的无监督聚类分析算法,它可以将数据集中在某些方面相似的数据成员进行分类,而k-means++算法是在k-means算法的基础上改进而来的。在介绍k-means++算法前,先从下表来认识一下k-means算法的基本实现步骤,
k-means++算法也只是对初始点的选择有改进而已,其他步骤和k-means算法一样。初始聚类中心选取的基本思路就是,初始的聚类中心之间的相互距离要尽可能的远,其具体实现步骤如下表所示,
2.DBoW2创建词典源码分析
进入到TemplatedVocabulary.h文件下,词典类的构造函数声明
TemplatedVocabulary(int k = 10, int L = 5,
WeightingType weighting = TF_IDF, ScoringType scoring = L1_NORM);
词典类的构造函数定义
template<class TDescriptor, class F>
TemplatedVocabulary<TDescriptor,F>::TemplatedVocabulary
(int k, int L, WeightingType weighting, ScoringType scoring)
: m_k(k), m_L(L), m_weighting(weighting), m_scoring(scoring),
m_scoring_object(NULL)
{
createScoringObject();
}
实例化词典类时,如果不指定树的分叉数和层数等,那么它们将使用默认值,分叉数m_k默认值为10,层数m_L默认值为5,权重类型m_weighting默认为TF_IDF,评分算法m_scoring默认为L1_NORM。
接下来找到下面的create()函数,它的参数为所有图像的BRIEF描述子,
template<class TDescriptor, class F>
void TemplatedVocabulary<TDescriptor,F>::create(
const std::vector<std::vector<TDescriptor> > &training_features)
{
m_nodes.clear();//树形结构的各层节点,包括叶子
m_words.clear();//单词(树形结构的叶子)
// 节点总数,这其实就是一个等比数列的求和
int expected_nodes =
(int)((pow((double)m_k, (double)m_L + 1) - 1)/(m_k - 1));
//节点存储空间的预置
m_nodes.reserve(expected_nodes); // avoid allocations when creating the tree
// 将所有描述子集合到一个vector
vector<pDescriptor> features;
getFeatures(training_features, features);
// create root
// 生成根节点
m_nodes.push_back(Node(0)); // root
// create the tree
// 利用k-means++算法(内有k-means++算法的递归嵌套)
HKmeansStep(0, features, 1);
// create the words
// 建立一个只有叶节点的序列m_words
createWords();
// and set the weight of each node of the tree
// 为每个叶节点生成权重,此处计算IDF部分,如果不用IDF,则设为1
setNodeWeights(training_features);
}
接下来先分析HKmeansStep()函数
/**
* @brief
* @param parent_id 父节点id
* @param descriptors 该父节点包含的描述子集合
* @param current_level 当前层数
*/
template<class TDescriptor, class F>
void TemplatedVocabulary<TDescriptor,F>::HKmeansStep(NodeId parent_id,
const vector<pDescriptor> &descriptors, int current_level)
{
if(descriptors.empty()) return;
// 用来存储所有子节点本身的描述子
vector<TDescriptor> clusters;
// 用来存储每个子节点包含的描述子(可能不包含子节点本身的描述子)在descriptors向量中的id
// groups[i] = [j1, j2, ...],表示的意义为
// id号为j1, j2, ...的描述子包含在子节点i中
vector<vector<unsigned int> > groups;
clusters.reserve(m_k);
groups.reserve(m_k);
// 如果特征描述个数小于m_k,直接分类
if((int)descriptors.size() <= m_k)
{
groups.resize(descriptors.size());
for(unsigned int i = 0; i < descriptors