美文网首页
DBoW:字典的生成

DBoW:字典的生成

作者: 小幸运Penny | 来源:发表于2019-12-12 09:14 被阅读0次

心血来潮的想看看回环检测,然后发现词袋模型是怎么产生的都不会(这真是一个悲伤的故事),所以就仔细看了一下它的代码,除此之外还问了问做自然语言处理的室友,室友说这个方法已经很老了(不禁泪目,只能解释传统的才是优秀的),但最起码还是问明白了一些。以下内容都是我自己的理解,如果有人看到觉得不对的请批评指正。

词袋模型的生成

总的来说,如果要用DBoW3产生一本字典,首先需要对一幅图像提取特征并且生成描述子,在ORBSLAM中,使用的是BREIF描述子,这样一幅图像就可以使用描述子来表示了。之后训练一个字典则需要调用以下函数:

DBOW3:Vocabulary vocab;
vocab.create(descriptors);
vocab.save("vocabulary.yml.gz");

以上代码的descriptors是从多幅图像中提取的描述子,他的类型可以是vector<cv::Mat>或者是vector<vector<cv::Mat>>。create函数就是具体生成词袋模型的函数,下面可以具体看一下create函数。源码中重载了多种create函数,但是最核心的部分还是void Vocabulary::create (const std::vector<std::vector<cv::Mat> > &training_features )。在该函数中主要的算法有以下几个部分,下面会根据不同的部分来进行分析。

// create root
    m_nodes.push_back ( Node ( 0 ) ); // root

    // create the tree
    HKmeansStep ( 0, features, 1 );

    // create the words
    createWords();

    // and set the weight of each node of the tree
    setNodeWeights ( training_features );

K叉树节点的形式

为了提升查找效率,DBoW使用了K叉树的方式来对描述子进行存储并且查找。对于树结构来说,最重要的是需要保存他的父节点和子节点,除此之外,它的id值也可以进行存储。所以DBoW3中,每个节点的形式代码所示,整个K叉树如图所示:

 /// Tree node
  struct Node 
  {
    /// Node id
    NodeId id;   //unsigned int
    /// Weight if the node is a word
    WordValue weight;  //double
    /// Children 
    std::vector<NodeId> children;
    /// Parent node (undefined in case of root)
    NodeId parent;
    /// Node descriptor
    cv::Mat descriptor;

    /// Word id if the node is a word
    WordId word_id;

    /**
     * Empty constructor
     */
    Node(): id(0), weight(0), parent(0), word_id(0){}
    
    /**
     * Constructor
     * @param _id node id
     */
    Node(NodeId _id): id(_id), weight(0), parent(0), word_id(0){}

    /**
     * Returns whether the node is a leaf node
     * @return true iff the node is a leaf
     */
    inline bool isLeaf() const { return children.empty(); }
  };
K叉树.png

在树中,根据根节点个数以及层数的不同,树的节点共有\frac{k^{(L+1)}}{k-1}个,而id值就是从0开始一共有这个多个,而最底层叶子节点是存储了每个描述子的信息,而上面的每一层中的节点值都代表他们的聚类中心,根据每一类中心的不同,找单词的时候就比原来效率提高了许多。word_id指的是单词的id值,他从0开始共有k^L个,只有叶子节点才会有这个word id值。
聚类主要是使用了KMeans++算法,它相较于KMeans多了一个自主选择初始聚类中心的过程,他们的算法如下所示。

Kmeans++

该算法主要是为了选出合适的聚类中心,因为对于Kmeans来说,聚类中心的选取是随机的并不能很好的表现出数据的特点,所以使用KMeans++可以得到合适的聚类中心,它主要的算法流程为:

1、从数据点中均匀随机选取一个数据作为中心点。
2、对于每一个中心点x,计算其他点与x之间的最短距离D(x)。
3、如果D(x)越大,则证明他被选取为中心点的可能性越大,使用轮盘法选出下一个聚类中心。
4、重复步骤2和3,直到得到k个聚类中心。
5、至此,就得到了出事的聚类中心

初始化聚类中心的代码在Vocabulary::initiateClustersKMpp中,我觉得最核心的代码就是通过轮盘法计算聚类中心的过程。

double cut_d;
do
{
    cut_d = ( double ( rand() ) / double ( RAND_MAX ) ) * dist_sum;   //randomly choose one value between the sum of the distance
}
while ( cut_d == 0.0 );

double d_up_now = 0;
for ( dit = min_dists.begin(); dit != min_dists.end(); ++dit )
{
    d_up_now += *dit;
    if ( d_up_now >= cut_d ) break;   //choose the value 
 }

if ( dit == min_dists.end() )  //choose the center index
    ifeature = pfeatures.size()-1;
else
    ifeature = dit - min_dists.begin();  

该段代码的核心思想就是在总的距离之间随机选取一个值,可以想象,如果距离的值越大,在总和之中占据的比例也越大,随机选取得到的点在该区间的概率也越大,总而言之,该随机选取得到的值在大值中的可能性也越大,这样就有可能选取到与当前聚类中心相聚比较远的点。如果并不是很理解的话,可以参考K-means与K-means++。在距离计算的时候,该代码使用的是bit运算,具体的可以参考Bit Twiddling Hacks,是一个介绍bit运算非常好的网站。

KMeans

KMeans算法的主要步骤为:

1、随机选取得到k个样本作为聚类中心:c_1, c_2,...,c_k(该步骤已经通过KMeans++得到);
2、对于每一个样本,计算他们与中心点之间的距离,取最小的距离的中心作为他们的归类;
3、重新计算每个类的中心点;
4、如果每个样本的中心都不再变化,则算法收敛,可以退出;否则返回1。

该算法的主要代码在Vocabulary::HKmeansStep中,具体操作详见代码,这里就不展开讨论了。

树的生成

比如在第1层得到k个聚类中心以及每个中心中对应的特征点集合之后,就需要将其生成树节点,每个树节点产生的形式如下:

// create nodes
    for ( unsigned int i = 0; i < clusters.size(); ++i )
    {
        NodeId id = m_nodes.size();
        m_nodes.push_back ( Node ( id ) );   //m_nodes represents the tree,
        m_nodes.back().descriptor = clusters[i];  //represent the cluster
        m_nodes.back().parent = parent_id; 
        m_nodes[parent_id].children.push_back ( id );   //save the children's information
    }

如果层数没有到达L,则再继续对每个节点进行聚类。

// go on with the next level
    if ( current_level < m_L )
    {
        // iterate again with the resulting clusters
        const std::vector<NodeId> &children_ids = m_nodes[parent_id].children;
        for ( unsigned int i = 0; i < clusters.size(); ++i )
        {
            NodeId id = children_ids[i];

            std::vector<cv::Mat> child_features;
            child_features.reserve ( groups[i].size() );
            //groups reserve the descriptors of every node
            std::vector<unsigned int>::const_iterator vit;
            for ( vit = groups[i].begin(); vit != groups[i].end(); ++vit )
            {
                child_features.push_back ( descriptors[*vit] );
            }

            if ( child_features.size() > 1 )
            {
                HKmeansStep ( id, child_features, current_level + 1 );
            }
        }
    }

单词的产生

单词产生的函数如以下代码所示,他主要的目的就是给叶子节点的word_id赋值,并且设置单词(描述子)的值。

void Vocabulary::createWords()
{
    m_words.resize ( 0 );

    if ( !m_nodes.empty() )
    {
        m_words.reserve ( ( int ) pow ( ( double ) m_k, ( double ) m_L ) );


        auto  nit = m_nodes.begin(); // ignore root
        for ( ++nit; nit != m_nodes.end(); ++nit )
        {
            if ( nit->isLeaf() )
            {
                nit->word_id = m_words.size();
                m_words.push_back ( & ( *nit ) );
            }
        }
    }
}

设置节点权重

在文本处理中,对于每一个单词的重要性是不一样的,比如说常见的字眼“的”、“是”等等,他们出现的频率是很高,可是他们的区分度并不高,所以他的并没有太大的重要性,而“蜜蜂”、“盐”等等一些名词,并不是所有的句子都会存在的,则他们的区分度可能就会高一点,重要性也会增加。因此,在文件检索中,一种常用的方法就是TF-IDF(Term Frequency-Inverse Document Frequency)。TF指的是某单词在一幅图像中经常出现,它的区分度就高。而IDF指某单词在字典中出现的频率越低,则分类图像时区分度越高。之前我一直不知道这个内容有啥用,在请教了室友之后知道,这个权重可以在原本的特征维数上再加一维用来表示重要程度,这一维数据会使得匹配结果更加的准确。所以一副图像就可以表示为:
I = \{(w_1,\eta _1) , ...,(w_N,\eta _N)\} = \mathbf{v}_I
其中w_i表示TF-IDF的权重,\eta_i表示图像中提取得到的描述子。在DBoW3中,描述子的权重如以下代码所示:

void Vocabulary::setNodeWeights
( const std::vector<std::vector<cv::Mat> > &training_features )
{
    const unsigned int NWords = m_words.size();
    const unsigned int NDocs = training_features.size();

    if ( m_weighting == TF || m_weighting == BINARY )
    {
        // idf part must be 1 always
        for ( unsigned int i = 0; i < NWords; i++ )
            m_words[i]->weight = 1;
    }
    else if ( m_weighting == IDF || m_weighting == TF_IDF )
    {
        // IDF and TF-IDF: we calculte the idf path now

        // Note: this actually calculates the idf part of the tf-idf score.
        // The complete tf-idf score is calculated in ::transform

        std::vector<unsigned int> Ni ( NWords, 0 );
        std::vector<bool> counted ( NWords, false );


        for ( auto mit = training_features.begin(); mit != training_features.end(); ++mit )
        {
            fill ( counted.begin(), counted.end(), false );

            for ( auto 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;
                }
            }
        }

        // set ln(N/Ni)
        for ( unsigned int i = 0; i < NWords; i++ )
        {
            if ( Ni[i] > 0 )
            {
                m_words[i]->weight = log ( ( double ) NDocs / ( double ) Ni[i] );
            }// else // This cannot occur if using kmeans++
        }

    }

}

至此,字典就正式生成了,描述子的内容和权重存储在m_words中,而m_nodes存储了每个节点的信息。

字典的保存

字典保存的函数在void Vocabulary::save ( cv::FileStorage &f,const std::string &name ) const中,具体内容就不详述了。

参考资料

DBow3代码
视觉SLAM十四讲

相关文章

  • DBoW:字典的生成

    心血来潮的想看看回环检测,然后发现词袋模型是怎么产生的都不会(这真是一个悲伤的故事),所以就仔细看了一下它的代码,...

  • 创建DBow离线词典用于ORB SLAM2

    前言 无论是DBow2,还是DBow3,它们创建的字典文件的都是.yml格式,不能直接应用于ORB SLAM2。文...

  • Python之集合筛选

    如何在列表,字典,集合中根据条件筛选 核心:使用生成式 列表: 生成随机列表: 筛选方法: 字典: 生成字典: 筛...

  • 列表生成式和字典生成式

    列表生成式 字典生成式

  • 从字典中提取子集 --字典推导式

    介绍使用字典推导式生成一个字典子集的3种方式: 1、提取出价格大于200的字典子集 生成包含如下集合中键的字典 2...

  • python 生成式

    1. 列表生成式 2. 字典生成式 3. 集合生成式

  • Crunch 生成字典

    Crunch 常用于生成字典,对于获取了目标密码创建规律的情况下,其生成的字典是非常有用的。 特殊标记字符 '%'...

  • plist文件的生成

    一、 通过数组生成plist文件 二、通过字典数据生成plist文件

  • Python字典

    1. 字典的一些知识点 字典特性可变、可存储任意类型对象、无序 字典的生成?直接用dict 字典的排序?sorte...

  • wifi破密

    配合密码生成器、密码字典使用把最有可能的密码组合先放里面生成一定量的密码字典,减少工作量。 (2)

网友评论

      本文标题:DBoW:字典的生成

      本文链接:https://www.haomeiwen.com/subject/wklggctx.html