一、BoW算法

  用OpenCV实现了最简单的BoW算法进行了一次小规模的图像检索任务,使用UKbench数据库,算法原理和网上的描述差不多,使用K-means算法进行聚类,这里使用KDTree算法进行特征量化,按照自己的理解计算了TF-IDF权重,使用余弦距离计算图像之间的相似性。下面给出关键函数依赖于OpenCV的实现:

如TF-IDF权重的计算,这里只是按照自己的理解实现了算法,有的地方传参不是很合适,不过不影响效果:

  1. std::vector<double> compute_TF(cv::Mat& descriptors, cv::Mat& labels)
  2. {
  3. std::vector<double> tf(Num_clu, 0.0);
  4. for (int i = ; i < descriptors.rows; i++)
  5. {
  6. tf[labels.at<int>(i)] ++;
  7. }
  8.  
  9. for (unsigned int i = ; i < tf.size(); i++)
  10. {
  11. tf[i] /= (float)descriptors.rows;
  12. }
  13. return tf;
  14. }
  15.  
  16. std::vector<double> comput_IDF(std::vector<cv::Mat>& descriptors, std::vector<cv::Mat> &labels)
  17. {
  18. std::vector<double> idf(Num_clu, 1.0);
  19.  
  20. for (unsigned int i = ; i < descriptors.size(); i++)
  21. {
  22. std::vector<int> idf_tmp(Num_clu, );
  23. for (int j = ; j < descriptors[i].rows; j++)
  24. {
  25. idf_tmp[labels[i].at<int>(j)] ++;
  26. }
  27. for (unsigned int j = ; j < idf_tmp.size(); j++)
  28. {
  29. if (idf_tmp[j] != ) idf[j] ++;
  30. }
  31. }
  32.  
  33. for (unsigned int i = ; i < idf.size(); i++)
  34. {
  35. idf[i] = log(Num_img / idf[i]);
  36. }
  37.  
  38. return idf;
  39. }

  有一点需要注意,这里的IDF应该是只计算一次,而TF则是对每一幅图像计算一次。

  有了TF-IDF函数的实现就可以计算BoW向量了,首先是计算训练图像的BoW向量:

  1. cv::Mat TrainingBowVector(cv::Mat & centers, std::vector<double>& IDF)
  2. {
  3. cv::SurfFeatureDetector detector;
  4. cv::SurfDescriptorExtractor extractor;
  5.  
  6. char image_name[];
  7. std::vector<cv::Mat> descriptor_all;
  8. descriptor_all.reserve(Num_img);
  9.  
  10. //Find the keypoints and compute the descriptors;
  11. for (int i = ; i <= Num_img; i++)
  12. {
  13. std::cout << "I:" << i << std::endl;
  14. sprintf_s(image_name, "D:\\DataBase\\UKbench\\TestImage\\%d.jpg", i);
  15. cv::Mat image = cv::imread(image_name, );
  16. std::vector<cv::KeyPoint> keypoints;
  17. cv::Mat descriptors;
  18. detector.detect(image, keypoints);
  19. std::cout << "Keypoints:" << keypoints.size() << std::endl;
  20. extractor.compute(image, keypoints, descriptors);
  21. descriptor_all.push_back(descriptors);
  22. }
  23.  
  24. //Get the training descriptors;
  25. std::cout << "Get the training descriptors." << std::endl;
  26. cv::Mat descriptor_train;
  27. for (int j = ; j < Num_tra; j++)
  28. descriptor_train.push_back(descriptor_all[j]);
  29.  
  30. cv::Mat labels_k;
  31. cv::kmeans(descriptor_train, Num_clu, labels_k, cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, , 0.01)
  32. , , cv::KMEANS_PP_CENTERS, centers);
  33.  
  34. const int tk = , Emax = INT_MAX;
  35. cv::KDTree T(centers, false);
  36. std::vector<cv::Mat> labels(Num_img);
  37. for (int i = ; i < Num_img; i++)
  38. {
  39. cv::Mat descriptor_img = descriptor_all[i];
  40. for (int j = ; j < descriptor_img.rows; j++)
  41. {
  42. std::vector<float> desc_vec(descriptor_img.row(j));
  43. std::vector<int> idx_tmp(tk);
  44. T.findNearest(desc_vec, tk, Emax, idx_tmp, cv::noArray(), cv::noArray());
  45. labels[i].push_back(idx_tmp[]);
  46. }
  47. }
  48.  
  49. std::cout << "Compute the TF-IDF." << std::endl;
  50. cv::Mat BowVec;
  51. //Compute the TF-IDF for each image;
  52. IDF = comput_IDF(descriptor_all, labels);
  53. for (int i = ; i < Num_img; i++)
  54. {
  55. std::vector<double> TF = compute_TF(descriptor_all[i], labels[i]);
  56. cv::Mat BowVec_tmp;
  57. for (unsigned int j = ; j < IDF.size(); j++)
  58. {
  59. BowVec_tmp.push_back(TF[j] * IDF[j]);
  60. //BowVec_tmp.push_back(TF[j]);
  61. }
  62. BowVec_tmp = BowVec_tmp.t();
  63. cv::normalize(BowVec_tmp, BowVec_tmp);
  64. BowVec.push_back(BowVec_tmp);
  65. }
  66. return BowVec;
  67. }

  计算测试图片的BoW向量和上面类似。有了训练图像和测试图像的BoW向量就可以根据余弦距离计算相似度了,最后使用堆排序获得最相似的图像ID。

  而Vocabuliary Tree算法的代码实现和上面的不同点在于码书的训练方式。

二、DBoW2库的使用

  使用DBoW2库训练码书,并根据bow打分完成图像检索,根据正向索引完成特征匹配,在ORB里面没注意到倒排索引加速图像检索的部分。

  首先是码书的训练(“盗用”代码:http://www.cnblogs.com/jian-li/p/5666556.html):

  1. #include <iostream>
  2. #include <vector>
  3. #include "Thirdparty/DBoW2/DBoW2/FORB.h"
  4. #include "Thirdparty/DBoW2/DBoW2/TemplatedVocabulary.h"
  5.  
  6. // OpenCV
  7. #include <opencv2/opencv.hpp>
  8. #include "opencv2/core/core.hpp"
  9. #include <opencv/cv.h>
  10. #include <opencv/highgui.h>
  11. #include <opencv2/nonfree/features2d.hpp>
  12.  
  13. // ROS
  14. #include <rosbag/bag.h>
  15. #include <rosbag/view.h>
  16. #include <ros/ros.h>
  17. #include <sensor_msgs/Image.h>
  18. #include <boost/foreach.hpp>
  19. #include <cv_bridge/cv_bridge.h>
  20. #include "ORBextractor.h"
  21. #include <dirent.h>
  22. #include <string.h>
  23.  
  24. using namespace DBoW2;
  25. using namespace DUtils;
  26. using namespace std;
  27. using namespace ORB_SLAM;
  28. // - - - - - --- - - - -- - - - - -
  29.  
  30. /// ORB Vocabulary
  31. typedef DBoW2::TemplatedVocabulary<DBoW2::FORB::TDescriptor, DBoW2::FORB>
  32. ORBVocabulary;
  33. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  34.  
  35. void extractORBFeatures(cv::Mat &image, vector<vector<cv::Mat> > &features, ORBextractor* extractor);
  36. void changeStructureORB( const cv::Mat &descriptor,vector<bool> &mask, vector<cv::Mat> &out);
  37. void isInImage(vector<cv::KeyPoint> &keys, float &cx, float &cy, float &rMin, float &rMax, vector<bool> &mask);
  38. void createVocabularyFile(ORBVocabulary &voc, std::string &fileName, const vector<vector<cv::Mat> > &features);
  39. // ----------------------------------------------------------------------------
  40.  
  41. int main()
  42. {
  43. //Extracting ORB features from image folder
  44. vector<std::string> filenames;
  45. std::string folder = "/home/saodiseng/FRONTAL/";
  46. cv::glob(folder, filenames);
  47.  
  48. // initialze ORBextractor
  49. int nLevels = 5;//6;
  50. ORBextractor* extractor = new ORBextractor(1000,1.2,nLevels,1,20);
  51. int nImages = filenames.size();
  52.  
  53. vector<vector<cv::Mat > > features;
  54. features.clear();
  55. features.reserve(nImages);
  56.  
  57. cv::Mat image;
  58. cout << "> Extracting Features from " << nImages << " images..." << endl;
  59. for(int i = 0; i < nImages; ++i)
  60. {
  61. std::cout << "Processing the " << i <<" image " << std::endl;
  62. cv::Mat src = cv::imread(filenames[i]);
  63. imshow("View", src);
  64. cv::waitKey(1);
  65. if (!src.empty())
  66. {
  67. cv::cvtColor(src, image, CV_RGB2GRAY);
  68. extractORBFeatures(image, features, extractor);
  69. }
  70. }
  71. cout << "... Extraction done!" << endl;
  72.  
  73. // Creating the Vocabulary
  74. // define vocabulary
  75. const int k = 10; // branching factor
  76. const WeightingType weight = TF_IDF;
  77. const ScoringType score = L1_NORM;
  78. ORBVocabulary voc(k, nLevels, weight, score);
  79. std::string vociName = "vociOmni.txt";
  80. createVocabularyFile(voc, vociName, features);
  81. cout << "--- THE END ---" << endl;
  82.  
  83. return 0;
  84. }
  85. // ----------------------------------------------------------------------------
  86.  
  87. void extractORBFeatures(cv::Mat &image, vector<vector<cv::Mat> > &features, ORBextractor* extractor) {
  88. vector<cv::KeyPoint> keypoints;
  89. cv::Mat descriptorORB;
  90. (*extractor)(image, cv::Mat(), keypoints, descriptorORB);
  91.  
  92. // reject features outside region of interest
  93. vector<bool> mask;
  94. float cx = 0; float cy = 0;
  95. float rMin = 0; float rMax = 0;
  96. isInImage(keypoints, cx, cy, rMin, rMax, mask);
  97. // create descriptor vector for the vocabulary
  98. features.push_back(vector<cv::Mat>());
  99. changeStructureORB(descriptorORB, mask, features.back());
  100. imshow("ORBFeature", features.back().back());
  101. }
  102.  
  103. // ----------------------------------------------------------------------------
  104.  
  105. void changeStructureORB( const cv::Mat &descriptor,vector<bool> &mask, vector<cv::Mat> &out) {
  106. for (int i = 0; i < descriptor.rows; i++) {
  107. if(mask[i]) {
  108. out.push_back(descriptor.row(i));
  109. }
  110. }
  111. }
  112.  
  113. // ----------------------------------------------------------------------------
  114.  
  115. void isInImage(vector<cv::KeyPoint> &keys, float &cx, float &cy, float &rMin, float &rMax, vector<bool> &mask) {
  116. int N = keys.size();
  117. mask = vector<bool>(N, false);
  118. int num = 0;
  119. for(int i=0; i<N; i++) {
  120. cv::KeyPoint kp = keys[i];
  121. float u = kp.pt.x;
  122. float v = kp.pt.y;
  123. if(u>20 && u<320-20 && v>20 && v<240-20)
  124. {
  125. mask[i] = true;
  126. num ++;
  127. }
  128. }
  129. std::cout << "In image number " << num << std::endl;
  130. }
  131.  
  132. // ----------------------------------------------------------------------------
  133.  
  134. void createVocabularyFile(ORBVocabulary &voc, std::string &fileName, const vector<vector<cv::Mat> > &features)
  135. {
  136. cout << "> Creating vocabulary. May take some time ..." << endl;
  137. voc.create(features);
  138. cout << "... done!" << endl;
  139. cout << "> Vocabulary information: " << endl
  140. << voc << endl << endl;
  141. // save the vocabulary to disk
  142. cout << endl << "> Saving vocabulary..." << endl;
  143. voc.saveToTextFile(fileName);
  144. cout << "... saved to file: " << fileName << endl;
  145. }

  也可以直接使用ORB给定的码书。

  再下面就是训练BoW向量并计算打分:

  1. void FrameRecog::ComputeBoW()
  2. {
  3. //数据类型转换;
  4. vector<cv::Mat>vFrDesc = Converter::toDescriptorVector(Descriptors);
  5. //BowVec为BoW特征向量,FeatVec为正向索引;
  6. pORBVocabulary->transform(vFrDesc, BowVec, FeatVec, );
  7. }
  8.  
  9. float score = pORBVocabulary->score(BowVec, vBowVec[i]);

ComputeBoW()函数计算了当前帧的BowVec向量,以及它的第4层正向索引值FeatVec。下面一句即计算了两个BoW向量的相似性打分。当打分满足某个阈值之后,还需要通过正向索引值进行特征匹配:

  1. int FrameRecog::FeatMatchByBoW( const int idx )
  2. {
  3. int nmatches = ;
  4. const int TH_LOW = ;
  5. const int HISTO_LENGTH = ;
  6. const int factor = 1.0f/HISTO_LENGTH;
  7.  
  8. const DBoW2::FeatureVector &vFeatVecTD = vFeatVec[idx];
  9. const DBoW2::FeatureVector &vFeatVecCD = FeatVec;
  10.  
  11. DBoW2::FeatureVector::const_iterator TDit = vFeatVecTD.begin();
  12. DBoW2::FeatureVector::const_iterator CDit = vFeatVecCD.begin();
  13. DBoW2::FeatureVector::const_iterator TDend= vFeatVecTD.end();
  14. DBoW2::FeatureVector::const_iterator CDend= vFeatVecCD.end();
  15.  
  16. while( TDit != TDend && CDit != CDend )
  17. {
  18. //first为单词的索引,second则对应为该单词索引下的ORB特征集合;
  19. if( TDit->first == CDit->first)
  20. {
  21.         //second是要循环的对象
  22. const vector<unsigned int> vIndicesTD = TDit->second;
  23. const vector<unsigned int> vIndicesCD = CDit->second;
  24.               
  25.         //循环关键帧和当前帧对应单词下的特征集合,计算相似性;
  26. for ( size_t iTD = ; iTD < vIndicesTD.size(); iTD ++ )
  27. {
  28. const unsigned int realIdxTD = vIndicesTD[iTD];
  29. const cv::Mat &dTD = vDescriptors[idx].row(realIdxTD);
  30.  
  31. int bestDist1 = ;
  32. int bestIdxF = -;
  33. int bestDist2 = ;
  34.  
  35. for ( size_t iCD = ; iCD < vIndicesCD.size(); iCD ++ )
  36. {
  37. const unsigned int realIdxCD = vIndicesCD[iCD];
  38. const cv::Mat &dCD = Descriptors.row(realIdxCD);
  39. const int dist = DescriptorDistance(dTD, dCD);
  40.              //这里注意是双阈值;
  41. if( dist < bestDist1 )
  42. {
  43. bestDist2 = bestDist1;
  44. bestDist1 = dist;
  45. bestIdxF = realIdxCD;
  46. }
  47. else if( dist < bestDist2 )
  48. {
  49. bestDist2 = dist;
  50. }
  51. }
  52.  
  53.            //这里有两个输入参数,一个是TH_LOW,是指两个特征的最小距离阈值;
  54.            //第二个是0.95,它是指相似特征的最小距离小于第二小距离的百分之九十五;
  55.            //第二个参数的含义是,当该参数越接近于1时,该式越接近于成立,而越小时说明要求越高,
  56.            //即最小距离远大于第二小距离,所以两特征是相似特征的概率非常大
  57. if(bestDist1 <= TH_LOW)
  58. {
  59. if( static_cast<float>(bestDist1)<0.95 * static_cast<float>(bestDist2))
  60. nmatches ++;
  61. }
  62. }
  63. TDit ++;
  64. CDit ++;
  65. }
  66. else if( TDit->first < CDit->first )
  67. {
  68. TDit = vFeatVecTD.lower_bound(CDit->first);
  69. }
  70. else
  71. {
  72. CDit = vFeatVecCD.lower_bound(TDit->first);
  73. }
  74. }
  75.  
  76.    //原函数中还有特征对应的3D地图点的输出,以及根据ORB特征的主方向进一步判断特征是否相似的代码,这里略去;
  77. return nmatches;
  78. }
  1. int FrameRecog::DescriptorDistance(const cv::Mat &a, const cv::Mat &b)
  2. {
  3. const int *pa = a.ptr<int32_t>();
  4. const int *pb = b.ptr<int32_t>();
  5.  
  6. int dist = ;
  7. for ( int i = ; i < ; i ++, pa ++, pb ++ )
  8. {
  9. unsigned int v = *pa ^ *pb;
  10. v = v - ((v>>) & 0x55555555);
  11. v = (v & 0x33333333) + ((v >> ) & 0x33333333);
  12. dist += (((v + (v >> )) & 0xF0F0F0F) * 0x1010101) >> ;
  13. }
  14. return dist;
  15. }
  1.  

上面的源文件在ORBmatches.cc中的

int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches) 函数中。即通过正向索引给出特征匹配数或匹配的特征以及对应的3D点。

BoW算法及DBoW2库简介(二)的更多相关文章

  1. BoW算法及DBoW2库简介

    由于在ORB-SLAM2中扩展图像识别模块,因此总结一下BoW算法,并对DBoW2库做简单介绍. 1. BoW算法 BoW算法即Bag of Words模型,是图像检索领域最常用的方法,也是基于内容的 ...

  2. DBoW2库介绍

    DBoW2库是University of Zaragoza里的Lopez等人开发的开源软件库. 由于在SLAM回环检测上的优异表现(特别是ORB-SLAM2),DBoW2库受到了广大SLAM爱好者的关 ...

  3. Python3.x:第三方库简介

    Python3.x:第三方库简介 环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex ...

  4. BerkeleyDB库简介

    BerkeleyDB库简介 BerkeleyDB(简称为BDB)是一种以key-value为结构的嵌入式数据库引擎: 嵌入式:bdb提供了一系列应用程序接口(API),调用这些接口很简单,应用程序和b ...

  5. LevelDB库简介

    LevelDB库简介 一.LevelDB入门 LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询 ...

  6. {Django基础七之Ajax} 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解)

    Django基础七之Ajax 本节目录 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解) 一 Ajax简介 ...

  7. MXNet深度学习库简介

    MXNet深度学习库简介 摘要: MXNet是一个深度学习库, 支持C++, Python, R, Scala, Julia, Matlab以及JavaScript等语言; 支持命令和符号编程; 可以 ...

  8. php spl标准库简介(SPL是Standard PHP Library(PHP标准库)(直接看代码实例,特别方便)

    php spl标准库简介(SPL是Standard PHP Library(PHP标准库)(直接看代码实例,特别方便) 一.总结 直接看代码实例,特别方便易懂 thinkphp控制器利眠宁不支持(说明 ...

  9. python学习--大数据与科学计算第三方库简介

    大数据与科学计算  库名称 简介 pycuda/opencl GPU高性能并发计算 Pandas python实现的类似R语言的数据统计.分析平台.基于NumPy和Matplotlib开发的,主要用于 ...

随机推荐

  1. 为Dynamics 365 USD设置打开调试面板的自定义快捷键

    我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...

  2. 如何计算Data Guard环境中Redo所需的网络带宽传输 (Doc ID 736755.1)

    How To Calculate The Required Network Bandwidth Transfer Of Redo In Data Guard Environments (Doc ID ...

  3. 017.Kubernetes二进制部署检查集群

    一 验证集群功能 1.1 检查节点状态 [root@k8smaster01 ~]# kubectl get nodes 1.2 创建测试文件 [root@k8smaster01 ~]# cd /opt ...

  4. 【使用篇二】配置文件application.properties参数详解(21)

    springboot提供了许多启动器starter,大部分的启动器都有配置属性,这些配置属性一般可以在这里找到: xxxxxxxx-autoconfigure-xxxxx.jar/META-INF/s ...

  5. 如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)

    背景 Java 中 Stack(栈) 与 Heap(堆) 是面试中被经常问到的一个话题. 有没有对 Java 中 Stack(栈) 与 Heap(堆) 烂熟于心的童鞋,请举手!!!(怎么没人举手-) ...

  6. 第一篇随笔:用VB.NET搞点简单事情(1)

    网络上能搜索到的爬虫文章大多是用python做的,也有少部分是C#做的(小声:所以用VB.NET也可以做爬虫.本文写的是第一步:获取网页) 使用代码前先imports以下内容 Imports Syst ...

  7. WM消息大全,windows消息大全

    WM消息大全 消息名 消息值 说明 WM_CREATE 0x0001 应用程序创建一个窗口 WM_DESTROY 0x0002 一个窗口被销毁 WM_MOVE 0x0003 移动一个窗口 WM_SIZ ...

  8. for循环使用element的折叠面板遇到的问题-1

    首先,效果是点击添加折叠面板,折叠面板的title右侧是关闭的小按钮,每次添加的面板都自动展开,其他的面板自动关闭,但其中发现一个问题是,每次点击关闭的时候,虽然上一个面板被关闭了,但他的下一个会自动 ...

  9. Cesium专栏-热力图(附源码下载)

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  10. arcgis api 4.x for js 自定义 Draw 绘制手绘面以及手绘线,只针对二维视图(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 4.x for js:esri 官网 api,里面详细的介绍 arcgis api 4.x 各个类 ...