DBoW3库的使用

DBoW库介绍

  • DBoW3是DBoW2的增强版,这是一个开源的C++库,用于给图像特征排序,并将图像转化成视觉词袋表示。它采用层级树状结构将相近的图像特征在物理存储上聚集在一起,创建一个视觉词典。DBoW3还生成一个图像数据库,带有顺序索引和逆序索引,可以使图像特征的检索和对比非常快。

  • DBoW3有两个主要的类:Vocabulary和Database。视觉词典将图像转化成视觉词袋向量,图像数据库对图像进行索引。
    在这里插入图片描述
    参考论文:
    Galvez-Lopez D, Tardos J D. Real-time loop detection with bags of binary words[C]// IEEE/RSJ International Conference on Intelligent Robots & Systems. 2011:51-58.
    Galvez-López D, Tardos J D. Bags of Binary Words for Fast Place Recognition in Image Sequences[J]. IEEE Transactions on Robotics, 2012, 28(5):1188-1197.


1. 特征提取与描述

论文中关于词袋的讲解,主要以FAST+Brief为主,DBoW3库中提供orb、brisk、akaze、surf等特征点的词典创建类型

2. 单词权重计算

  • TF-IDF是文本检索中常用的一种加权方式,TF的思想:某单词在一副图像中经常出现,它的区分度就高;IDF的思想:某单词在字典中出现的频率出现的越低,分类图像时区分度越高
  • TF部分是指某个特征在单幅图像中出现的频率:
    T F i = n i n TF_i=\frac{n_i}{n} TFi=nni
  • IDF部分是指某个叶子节点的特征数量相对于所有特征数量的比例:
    I D F i = l o g n n i IDF_i=log\frac{n}{n_i} IDFi=lognin
  • 于是, w i w_i wi的权重等于TF乘IDF之积:
    η i = T F i × I D F i \eta_i=TF_i\times IDF_i ηi=TFi×IDFi

3. 相似度计算

计算两个向量v 和w的相似度有几种度量方法:

  • 点积
  • L1范数
    L 1 −  score  s ( v 1 , V 2 ) = 1 − 1 2 ∣ V 1 ∣ V 1 ∣ − V 2 ∣ v 2 ∣ ∣ L_{1}-\text { score } s\left(\mathrm{v}_{1}, \mathrm{V}_{2}\right)=1-\frac{1}{2}\left|\frac{\mathrm{V}_{1}}{\left|\mathrm{V}_{1}\right|}-\frac{\mathrm{V}_{2}}{\left|\mathrm{v}_{2}\right|}\right| L1 score s(v1,V2)=121V1V1v2V2
  • L2范数
  • KL散度/相对熵
  • 巴式系数
  • 卡方距离

4. 词袋组成与结构

  • 使用词袋模型分成两步,先离线生成一个词典,再产生一个数据库,用于快速索引

4.1 词典生成流程

一个词典由很多个单词组成,每个单词是一些特征聚类的结果,具体怎么聚类(kmeans++),聚类成多少,取决于我们选择的K叉树的结构
在这里插入图片描述

    const int k = 9;   //k代表k叉树
    const int L = 3;  //L代表深度,叶节点为第0层,根节点为第L层,K^L代表该词典最大能记录多少个单词(即叶节点的个数)
    const WeightingType weight = TF_IDF;  //权重计算方式
    const ScoringType score = L1_NORM;  //评分公式

    DBoW3::Vocabulary voc(k, L, weight, score);
	vocab.create( descriptors );   //descriptors是图像的特征点集合
    vocab.save( "vocabulary.yml.gz" );  //生成离线词典

4.2 数据库的生成

  • 一个数据库是由词典、逆序索引和正序索引三部分组成
  • 逆序索引
    在这里插入图片描述
    1、逆序索引单词涵盖了所有出现的单词
    2、每一个单词指向包含它的一系列图像
    3、采用投票机制选取候选者,加速搜索
    在这里插入图片描述

在这里插入图片描述

Database db(voc, false, 0); // false说明不需要顺序索引,此时不用看第三个参数
for(size_t i = 0; i < features.size(); i++)
        db.add(features[i]);
 QueryResults ret;
 for(size_t i = 0; i < features.size(); i++)
 {
       db.query(features[i], ret, 4);
       cout << "Searching for Image " << i << ". " << ret << endl;
  }
  • 顺序索引
    在这里插入图片描述
    在回环检测的最后阶段——几何结构验证阶段,可以加速匹配候选图像与当前图像之间的特征点对

使用正向索引时需要指定一个层数,即在与该层下的某个节点所包含的特征进行特征匹配,这同样也大大降低了特征匹配的规模,这种方法在ORB-SLAM中也用于特征匹配的加速,如跟踪时的关键帧模型等。

正向索引的数据结构如下,继承自std::map,NodeId为第4层上node节点的编号,其范围在 [ 0 , k l ) [0,k^l) [0,kl)内,l表示当前层数(这里以最上层为0层),std::vector是所有经过该node节点特征编号集合

/// Vector of nodes with indexes of local features
class FeatureVector: public std::map<NodeId, std::vector<unsigned int> >
DBoW3::Database db(voc, true, 2);
DBoW3::FeatureVector fv1, fv2;
db.add(d1);
db.add(d2);
fv1 = db.retrieveFeatures(0);
fv2 = db.retrieveFeatures(1);
for (auto i : fv1) {
	for (auto j : fv2) {
		if (i.first == j.first) {
			//match i.second with j.second
		}
 
	}
}

3. 回环检测流程

3.1 数据库查询

  • 查询数据库就是上述在线更新和维护的invese index word,利用inverse index words数据库检索过程,不采用暴力匹配,只匹配包含相同单词的个别图像信息,加快检索过程。
  • 选取一个先验相似度 s ( v t , v t − Δ t s(v_t,v_{t-\Delta t} s(vt,vtΔt,它表示某时刻关键帧图像与上一时刻的关键帧的相似性,然后,其他的分值都参照这个值进行归一化:
    s ( v t , v t j ) ′ = s ( v t , v t j ) / s ( v t , v t − Δ t ) s(v_t,v_{t_j})'=s(v_t,v_{t_j})/s(v_t,v_{t-\Delta t}) s(vt,vtj)=s(vt,vtj)/s(vt,vtΔt)

如果当前帧与之前某关键帧的相似度超过当前帧与上一个关键帧相似度的n倍,就认为存在回环,该步骤避免了引入绝对的相似度阈值,使算法具有更好的自适应性。

3.2 时间一致性匹配

  • 一段时间内一直检测的回环才当做是回环(n,n+1,n+2……帧都和关键帧像,才当做是回环)

3.3 结构一致性匹配

  • 把回环上的两帧进行特征匹配,估计相机运动。把运动放到Pose Graph中,检查与之前估计是否有很大出入。

在使用词袋模型检测中,未考虑特征之间的结构约束,即各个特征点在空间中的位置是唯一不变的,以基础矩阵建立约束
过程关键点:
1、使用direct image index,加速校验前特征点对的获取
2、同时为了获取足够数量的特征点,不能直接选取words作为匹配索引;同时凸显特征之间的区分度,也不能采用采用较高层数
的节点(词袋树形结构)。

4. 实验demo

#include<iostream>
#include<opencv2/opencv.hpp>
#include<DBoW3/DBoW3.h>
#include<string>
using namespace std;
using namespace cv;

int main(int argc,char** argv)
{
    try
    {
        cout<<"reading images..."<<endl;
        vector<Mat> images;
        int n=stoi(argv[1]);
        for(int i=1;i<=n;i++){
            string path="./data/"+to_string(i)+".png";
            images.push_back(imread(path));
        }

        cout<<"extracting "<<string(argv[2])<<" features!";
        Ptr<Feature2D> detector;
        string descriptor=argv[2];
        if(descriptor=="orb") 
            detector=ORB::create();
        else if(descriptor=="brisk") 
            detector=BRISK::create();
        else 
            throw runtime_error("Invide Descriptor!");

        vector<Mat> descriptors(n);
        int i=0;
        for(Mat& image:images){
            vector<KeyPoint> keypoints;
            detector->detectAndCompute(image,Mat(),keypoints,descriptors[i++]); 
        }
        
        cout<<"creating vocabulary ..."<<endl;
        const int k = 10;   
        const int L = 5;
        const DBoW3::WeightingType weight = DBoW3::TF_IDF;
        const DBoW3::ScoringType score = DBoW3::L1_NORM;
        DBoW3::Vocabulary vocab(k, L, weight, score);
        vocab.create(descriptors);
        vocab.save("vocabulary.yml.gz");    //保存训练好的词典
        cout<<"vocabulary created done!"<<endl;

        //或加载已有训练好的词典
        //DBoW3::Vocabulary voc("small_voc.yml.gz");
        //voc.load("small_voc.yml.gz");   
        cout<<"vocabulary info: "<< endl << vocab <<endl;

        cout<<"Matching iamges aginst themselves(0 low,1 high):"<<endl;
        DBoW3::BowVector v1,v2;    
        for(size_t i=0;i<descriptors.size();i++){
            vocab.transform(descriptors[i],v1);        //图像转成词袋向量
            for(size_t j=0;j<descriptors.size();j++){
                vocab.transform(descriptors[j],v2); 
                double score=vocab.score(v1,v2);     //相似性比较
                cout<<"Image "<<i<<" vs Image "<<j<<": "<<score<<endl;
            }
        }

        cout << "Creating a small database..." << endl;
        DBoW3::Database db(vocab, true, 3); // 使用正序索引,层数为3最佳
        for (size_t i = 0; i < descriptors.size(); i++)
			db.add(descriptors[i]);
		cout << "Database done!" << endl;

		cout << "Database information: " << endl << db << endl;     
        cout << "Querying the database: " << endl;      //逆序查询
		DBoW3::QueryResults ret;
		for (size_t i = 0; i < descriptors.size(); i++)
		{
			db.query(descriptors[i], ret, 4); //离目标图像最近的4张图像
			cout << "Searching for Image " << i << ". " << ret << endl;    //对ret的<<重载过
		}

        //正序的特征点匹配
        DBoW3::FeatureVector fv1, fv2;
        fv1 = db.retrieveFeatures(0);    //排过序的
        fv2 = db.retrieveFeatures(1);
        cout<<fv1.size()<<endl;
        
        DBoW3::FeatureVector::const_iterator fv1_begin = fv1.begin();             
        DBoW3::FeatureVector::const_iterator fv2_begin = fv2.begin();             
        DBoW3::FeatureVector::const_iterator fv1_end = fv1.end();             
        DBoW3::FeatureVector::const_iterator fv2_end = fv2.end();             
                                                                            
        while(fv1_begin != fv1_end && fv2_begin != fv2_end)                                         
        {                                                                            
            //分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)                       
            if(fv1_begin->first == fv2_begin->first)                                           
            {                                                                       
                const vector<unsigned int> vIndicesfv1 = fv1_begin->second;               
                const vector<unsigned int> vIndicesfv2 = fv2_begin->second;                 
                                                                            
                //遍历KF中属于该node的特征点                                                 
                for(size_t ifv1=0; ifv1<vIndicesfv1.size(); ifv1++)                     
                {                                                                   
                    const unsigned int realIdxKF = vIndicesfv1[ifv1];                                                                                             
                    
                }
            }
            else 
                fv1_begin->first < fv2_begin->first ? fv1_begin++ :fv2_begin++;
            
        }

        cout << "Saving database..." << endl;
		db.save("small_db.yml.gz");
		cout << "... done!" << endl;

    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    
    return 0;
}
  • 10
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值