BoW算法及DBoW2库简介(二)
一、BoW算法
用OpenCV实现了最简单的BoW算法进行了一次小规模的图像检索任务,使用UKbench数据库,算法原理和网上的描述差不多,使用K-means算法进行聚类,这里使用KDTree算法进行特征量化,按照自己的理解计算了TF-IDF权重,使用余弦距离计算图像之间的相似性。下面给出关键函数依赖于OpenCV的实现:
如TF-IDF权重的计算,这里只是按照自己的理解实现了算法,有的地方传参不是很合适,不过不影响效果:
- std::vector<double> compute_TF(cv::Mat& descriptors, cv::Mat& labels)
- {
- std::vector<double> tf(Num_clu, 0.0);
- for (int i = ; i < descriptors.rows; i++)
- {
- tf[labels.at<int>(i)] ++;
- }
- for (unsigned int i = ; i < tf.size(); i++)
- {
- tf[i] /= (float)descriptors.rows;
- }
- return tf;
- }
- std::vector<double> comput_IDF(std::vector<cv::Mat>& descriptors, std::vector<cv::Mat> &labels)
- {
- std::vector<double> idf(Num_clu, 1.0);
- for (unsigned int i = ; i < descriptors.size(); i++)
- {
- std::vector<int> idf_tmp(Num_clu, );
- for (int j = ; j < descriptors[i].rows; j++)
- {
- idf_tmp[labels[i].at<int>(j)] ++;
- }
- for (unsigned int j = ; j < idf_tmp.size(); j++)
- {
- if (idf_tmp[j] != ) idf[j] ++;
- }
- }
- for (unsigned int i = ; i < idf.size(); i++)
- {
- idf[i] = log(Num_img / idf[i]);
- }
- return idf;
- }
有一点需要注意,这里的IDF应该是只计算一次,而TF则是对每一幅图像计算一次。
有了TF-IDF函数的实现就可以计算BoW向量了,首先是计算训练图像的BoW向量:
- cv::Mat TrainingBowVector(cv::Mat & centers, std::vector<double>& IDF)
- {
- cv::SurfFeatureDetector detector;
- cv::SurfDescriptorExtractor extractor;
- char image_name[];
- std::vector<cv::Mat> descriptor_all;
- descriptor_all.reserve(Num_img);
- //Find the keypoints and compute the descriptors;
- for (int i = ; i <= Num_img; i++)
- {
- std::cout << "I:" << i << std::endl;
- sprintf_s(image_name, "D:\\DataBase\\UKbench\\TestImage\\%d.jpg", i);
- cv::Mat image = cv::imread(image_name, );
- std::vector<cv::KeyPoint> keypoints;
- cv::Mat descriptors;
- detector.detect(image, keypoints);
- std::cout << "Keypoints:" << keypoints.size() << std::endl;
- extractor.compute(image, keypoints, descriptors);
- descriptor_all.push_back(descriptors);
- }
- //Get the training descriptors;
- std::cout << "Get the training descriptors." << std::endl;
- cv::Mat descriptor_train;
- for (int j = ; j < Num_tra; j++)
- descriptor_train.push_back(descriptor_all[j]);
- cv::Mat labels_k;
- cv::kmeans(descriptor_train, Num_clu, labels_k, cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, , 0.01)
- , , cv::KMEANS_PP_CENTERS, centers);
- const int tk = , Emax = INT_MAX;
- cv::KDTree T(centers, false);
- std::vector<cv::Mat> labels(Num_img);
- for (int i = ; i < Num_img; i++)
- {
- cv::Mat descriptor_img = descriptor_all[i];
- for (int j = ; j < descriptor_img.rows; j++)
- {
- std::vector<float> desc_vec(descriptor_img.row(j));
- std::vector<int> idx_tmp(tk);
- T.findNearest(desc_vec, tk, Emax, idx_tmp, cv::noArray(), cv::noArray());
- labels[i].push_back(idx_tmp[]);
- }
- }
- std::cout << "Compute the TF-IDF." << std::endl;
- cv::Mat BowVec;
- //Compute the TF-IDF for each image;
- IDF = comput_IDF(descriptor_all, labels);
- for (int i = ; i < Num_img; i++)
- {
- std::vector<double> TF = compute_TF(descriptor_all[i], labels[i]);
- cv::Mat BowVec_tmp;
- for (unsigned int j = ; j < IDF.size(); j++)
- {
- BowVec_tmp.push_back(TF[j] * IDF[j]);
- //BowVec_tmp.push_back(TF[j]);
- }
- BowVec_tmp = BowVec_tmp.t();
- cv::normalize(BowVec_tmp, BowVec_tmp);
- BowVec.push_back(BowVec_tmp);
- }
- return BowVec;
- }
计算测试图片的BoW向量和上面类似。有了训练图像和测试图像的BoW向量就可以根据余弦距离计算相似度了,最后使用堆排序获得最相似的图像ID。
而Vocabuliary Tree算法的代码实现和上面的不同点在于码书的训练方式。
二、DBoW2库的使用
使用DBoW2库训练码书,并根据bow打分完成图像检索,根据正向索引完成特征匹配,在ORB里面没注意到倒排索引加速图像检索的部分。
首先是码书的训练(“盗用”代码:http://www.cnblogs.com/jian-li/p/5666556.html):
- #include <iostream>
- #include <vector>
- #include "Thirdparty/DBoW2/DBoW2/FORB.h"
- #include "Thirdparty/DBoW2/DBoW2/TemplatedVocabulary.h"
- // OpenCV
- #include <opencv2/opencv.hpp>
- #include "opencv2/core/core.hpp"
- #include <opencv/cv.h>
- #include <opencv/highgui.h>
- #include <opencv2/nonfree/features2d.hpp>
- // ROS
- #include <rosbag/bag.h>
- #include <rosbag/view.h>
- #include <ros/ros.h>
- #include <sensor_msgs/Image.h>
- #include <boost/foreach.hpp>
- #include <cv_bridge/cv_bridge.h>
- #include "ORBextractor.h"
- #include <dirent.h>
- #include <string.h>
- using namespace DBoW2;
- using namespace DUtils;
- using namespace std;
- using namespace ORB_SLAM;
- // - - - - - --- - - - -- - - - - -
- /// ORB Vocabulary
- typedef DBoW2::TemplatedVocabulary<DBoW2::FORB::TDescriptor, DBoW2::FORB>
- ORBVocabulary;
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void extractORBFeatures(cv::Mat &image, vector<vector<cv::Mat> > &features, ORBextractor* extractor);
- void changeStructureORB( const cv::Mat &descriptor,vector<bool> &mask, vector<cv::Mat> &out);
- void isInImage(vector<cv::KeyPoint> &keys, float &cx, float &cy, float &rMin, float &rMax, vector<bool> &mask);
- void createVocabularyFile(ORBVocabulary &voc, std::string &fileName, const vector<vector<cv::Mat> > &features);
- // ----------------------------------------------------------------------------
- int main()
- {
- //Extracting ORB features from image folder
- vector<std::string> filenames;
- std::string folder = "/home/saodiseng/FRONTAL/";
- cv::glob(folder, filenames);
- // initialze ORBextractor
- int nLevels = 5;//6;
- ORBextractor* extractor = new ORBextractor(1000,1.2,nLevels,1,20);
- int nImages = filenames.size();
- vector<vector<cv::Mat > > features;
- features.clear();
- features.reserve(nImages);
- cv::Mat image;
- cout << "> Extracting Features from " << nImages << " images..." << endl;
- for(int i = 0; i < nImages; ++i)
- {
- std::cout << "Processing the " << i <<" image " << std::endl;
- cv::Mat src = cv::imread(filenames[i]);
- imshow("View", src);
- cv::waitKey(1);
- if (!src.empty())
- {
- cv::cvtColor(src, image, CV_RGB2GRAY);
- extractORBFeatures(image, features, extractor);
- }
- }
- cout << "... Extraction done!" << endl;
- // Creating the Vocabulary
- // define vocabulary
- const int k = 10; // branching factor
- const WeightingType weight = TF_IDF;
- const ScoringType score = L1_NORM;
- ORBVocabulary voc(k, nLevels, weight, score);
- std::string vociName = "vociOmni.txt";
- createVocabularyFile(voc, vociName, features);
- cout << "--- THE END ---" << endl;
- return 0;
- }
- // ----------------------------------------------------------------------------
- void extractORBFeatures(cv::Mat &image, vector<vector<cv::Mat> > &features, ORBextractor* extractor) {
- vector<cv::KeyPoint> keypoints;
- cv::Mat descriptorORB;
- (*extractor)(image, cv::Mat(), keypoints, descriptorORB);
- // reject features outside region of interest
- vector<bool> mask;
- float cx = 0; float cy = 0;
- float rMin = 0; float rMax = 0;
- isInImage(keypoints, cx, cy, rMin, rMax, mask);
- // create descriptor vector for the vocabulary
- features.push_back(vector<cv::Mat>());
- changeStructureORB(descriptorORB, mask, features.back());
- imshow("ORBFeature", features.back().back());
- }
- // ----------------------------------------------------------------------------
- void changeStructureORB( const cv::Mat &descriptor,vector<bool> &mask, vector<cv::Mat> &out) {
- for (int i = 0; i < descriptor.rows; i++) {
- if(mask[i]) {
- out.push_back(descriptor.row(i));
- }
- }
- }
- // ----------------------------------------------------------------------------
- void isInImage(vector<cv::KeyPoint> &keys, float &cx, float &cy, float &rMin, float &rMax, vector<bool> &mask) {
- int N = keys.size();
- mask = vector<bool>(N, false);
- int num = 0;
- for(int i=0; i<N; i++) {
- cv::KeyPoint kp = keys[i];
- float u = kp.pt.x;
- float v = kp.pt.y;
- if(u>20 && u<320-20 && v>20 && v<240-20)
- {
- mask[i] = true;
- num ++;
- }
- }
- std::cout << "In image number " << num << std::endl;
- }
- // ----------------------------------------------------------------------------
- void createVocabularyFile(ORBVocabulary &voc, std::string &fileName, const vector<vector<cv::Mat> > &features)
- {
- cout << "> Creating vocabulary. May take some time ..." << endl;
- voc.create(features);
- cout << "... done!" << endl;
- cout << "> Vocabulary information: " << endl
- << voc << endl << endl;
- // save the vocabulary to disk
- cout << endl << "> Saving vocabulary..." << endl;
- voc.saveToTextFile(fileName);
- cout << "... saved to file: " << fileName << endl;
- }
也可以直接使用ORB给定的码书。
再下面就是训练BoW向量并计算打分:
- void FrameRecog::ComputeBoW()
- {
- //数据类型转换;
- vector<cv::Mat>vFrDesc = Converter::toDescriptorVector(Descriptors);
- //BowVec为BoW特征向量,FeatVec为正向索引;
- pORBVocabulary->transform(vFrDesc, BowVec, FeatVec, );
- }
- float score = pORBVocabulary->score(BowVec, vBowVec[i]);
ComputeBoW()函数计算了当前帧的BowVec向量,以及它的第4层正向索引值FeatVec。下面一句即计算了两个BoW向量的相似性打分。当打分满足某个阈值之后,还需要通过正向索引值进行特征匹配:
- int FrameRecog::FeatMatchByBoW( const int idx )
- {
- int nmatches = ;
- const int TH_LOW = ;
- const int HISTO_LENGTH = ;
- const int factor = 1.0f/HISTO_LENGTH;
- const DBoW2::FeatureVector &vFeatVecTD = vFeatVec[idx];
- const DBoW2::FeatureVector &vFeatVecCD = FeatVec;
- DBoW2::FeatureVector::const_iterator TDit = vFeatVecTD.begin();
- DBoW2::FeatureVector::const_iterator CDit = vFeatVecCD.begin();
- DBoW2::FeatureVector::const_iterator TDend= vFeatVecTD.end();
- DBoW2::FeatureVector::const_iterator CDend= vFeatVecCD.end();
- while( TDit != TDend && CDit != CDend )
- {
- //first为单词的索引,second则对应为该单词索引下的ORB特征集合;
- if( TDit->first == CDit->first)
- {
- //second是要循环的对象
- const vector<unsigned int> vIndicesTD = TDit->second;
- const vector<unsigned int> vIndicesCD = CDit->second;
- //循环关键帧和当前帧对应单词下的特征集合,计算相似性;
- for ( size_t iTD = ; iTD < vIndicesTD.size(); iTD ++ )
- {
- const unsigned int realIdxTD = vIndicesTD[iTD];
- const cv::Mat &dTD = vDescriptors[idx].row(realIdxTD);
- int bestDist1 = ;
- int bestIdxF = -;
- int bestDist2 = ;
- for ( size_t iCD = ; iCD < vIndicesCD.size(); iCD ++ )
- {
- const unsigned int realIdxCD = vIndicesCD[iCD];
- const cv::Mat &dCD = Descriptors.row(realIdxCD);
- const int dist = DescriptorDistance(dTD, dCD);
- //这里注意是双阈值;
- if( dist < bestDist1 )
- {
- bestDist2 = bestDist1;
- bestDist1 = dist;
- bestIdxF = realIdxCD;
- }
- else if( dist < bestDist2 )
- {
- bestDist2 = dist;
- }
- }
- //这里有两个输入参数,一个是TH_LOW,是指两个特征的最小距离阈值;
- //第二个是0.95,它是指相似特征的最小距离小于第二小距离的百分之九十五;
- //第二个参数的含义是,当该参数越接近于1时,该式越接近于成立,而越小时说明要求越高,
- //即最小距离远大于第二小距离,所以两特征是相似特征的概率非常大
- if(bestDist1 <= TH_LOW)
- {
- if( static_cast<float>(bestDist1)<0.95 * static_cast<float>(bestDist2))
- nmatches ++;
- }
- }
- TDit ++;
- CDit ++;
- }
- else if( TDit->first < CDit->first )
- {
- TDit = vFeatVecTD.lower_bound(CDit->first);
- }
- else
- {
- CDit = vFeatVecCD.lower_bound(TDit->first);
- }
- }
- //原函数中还有特征对应的3D地图点的输出,以及根据ORB特征的主方向进一步判断特征是否相似的代码,这里略去;
- return nmatches;
- }
- int FrameRecog::DescriptorDistance(const cv::Mat &a, const cv::Mat &b)
- {
- const int *pa = a.ptr<int32_t>();
- const int *pb = b.ptr<int32_t>();
- int dist = ;
- for ( int i = ; i < ; i ++, pa ++, pb ++ )
- {
- unsigned int v = *pa ^ *pb;
- v = v - ((v>>) & 0x55555555);
- v = (v & 0x33333333) + ((v >> ) & 0x33333333);
- dist += (((v + (v >> )) & 0xF0F0F0F) * 0x1010101) >> ;
- }
- return dist;
- }
上面的源文件在ORBmatches.cc中的
int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector<MapPoint*> &vpMapPointMatches) 函数中。即通过正向索引给出特征匹配数或匹配的特征以及对应的3D点。
BoW算法及DBoW2库简介(二)的更多相关文章
- BoW算法及DBoW2库简介
由于在ORB-SLAM2中扩展图像识别模块,因此总结一下BoW算法,并对DBoW2库做简单介绍. 1. BoW算法 BoW算法即Bag of Words模型,是图像检索领域最常用的方法,也是基于内容的 ...
- DBoW2库介绍
DBoW2库是University of Zaragoza里的Lopez等人开发的开源软件库. 由于在SLAM回环检测上的优异表现(特别是ORB-SLAM2),DBoW2库受到了广大SLAM爱好者的关 ...
- Python3.x:第三方库简介
Python3.x:第三方库简介 环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex ...
- BerkeleyDB库简介
BerkeleyDB库简介 BerkeleyDB(简称为BDB)是一种以key-value为结构的嵌入式数据库引擎: 嵌入式:bdb提供了一系列应用程序接口(API),调用这些接口很简单,应用程序和b ...
- LevelDB库简介
LevelDB库简介 一.LevelDB入门 LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询 ...
- {Django基础七之Ajax} 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解)
Django基础七之Ajax 本节目录 一 Ajax简介 二 Ajax使用 三 Ajax请求设置csrf_token 四 关于json 五 补充一个SweetAlert插件(了解) 一 Ajax简介 ...
- MXNet深度学习库简介
MXNet深度学习库简介 摘要: MXNet是一个深度学习库, 支持C++, Python, R, Scala, Julia, Matlab以及JavaScript等语言; 支持命令和符号编程; 可以 ...
- php spl标准库简介(SPL是Standard PHP Library(PHP标准库)(直接看代码实例,特别方便)
php spl标准库简介(SPL是Standard PHP Library(PHP标准库)(直接看代码实例,特别方便) 一.总结 直接看代码实例,特别方便易懂 thinkphp控制器利眠宁不支持(说明 ...
- python学习--大数据与科学计算第三方库简介
大数据与科学计算 库名称 简介 pycuda/opencl GPU高性能并发计算 Pandas python实现的类似R语言的数据统计.分析平台.基于NumPy和Matplotlib开发的,主要用于 ...
随机推荐
- 为Dynamics 365 USD设置打开调试面板的自定义快捷键
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...
- 如何计算Data Guard环境中Redo所需的网络带宽传输 (Doc ID 736755.1)
How To Calculate The Required Network Bandwidth Transfer Of Redo In Data Guard Environments (Doc ID ...
- 017.Kubernetes二进制部署检查集群
一 验证集群功能 1.1 检查节点状态 [root@k8smaster01 ~]# kubectl get nodes 1.2 创建测试文件 [root@k8smaster01 ~]# cd /opt ...
- 【使用篇二】配置文件application.properties参数详解(21)
springboot提供了许多启动器starter,大部分的启动器都有配置属性,这些配置属性一般可以在这里找到: xxxxxxxx-autoconfigure-xxxxx.jar/META-INF/s ...
- 如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)
背景 Java 中 Stack(栈) 与 Heap(堆) 是面试中被经常问到的一个话题. 有没有对 Java 中 Stack(栈) 与 Heap(堆) 烂熟于心的童鞋,请举手!!!(怎么没人举手-) ...
- 第一篇随笔:用VB.NET搞点简单事情(1)
网络上能搜索到的爬虫文章大多是用python做的,也有少部分是C#做的(小声:所以用VB.NET也可以做爬虫.本文写的是第一步:获取网页) 使用代码前先imports以下内容 Imports Syst ...
- WM消息大全,windows消息大全
WM消息大全 消息名 消息值 说明 WM_CREATE 0x0001 应用程序创建一个窗口 WM_DESTROY 0x0002 一个窗口被销毁 WM_MOVE 0x0003 移动一个窗口 WM_SIZ ...
- for循环使用element的折叠面板遇到的问题-1
首先,效果是点击添加折叠面板,折叠面板的title右侧是关闭的小按钮,每次添加的面板都自动展开,其他的面板自动关闭,但其中发现一个问题是,每次点击关闭的时候,虽然上一个面板被关闭了,但他的下一个会自动 ...
- Cesium专栏-热力图(附源码下载)
Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...
- arcgis api 4.x for js 自定义 Draw 绘制手绘面以及手绘线,只针对二维视图(附源码下载)
前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 4.x for js:esri 官网 api,里面详细的介绍 arcgis api 4.x 各个类 ...