BOW其实就是bag of word的缩写,在OpenCV中关于此框架的有3个类。
第一个是一个基类,算是BOW训练的类型,
class BOWTrainer
{
public:
BOWTrainer(){}
virtual ~BOWTrainer(){}
void add( const Mat &descriptors);
const vector<Mat> &getDescriptors() const;
int descriptorsCount() const;
virtual void clear();
virtual Mat cluster() const = 0;
virtual Mat cluster( const Mat &descriptors );
protected:
…
}
第二个类别是我们在应用的时候真正的BOW训练的接口类别叫做BOWKmeansTrainer, 此类继承来自 BOWTrainer类别(不知道这么说对不对,C++还在学习中)
class BOWKmeansTrainer : public BOWTrainer
{
public:
BOWKmeansTrainer( int clusterCount, const TermCriteria &termcrit = TermCriteria(),
int attempts = 3, int flags = KMEANS_PP_CENTERS);
virtual ~BOWKmeansTrainer(){};
virtual Mat cluster() const;
virtual Mat cluster( const Mat &descriptors ) const;
protected:
…
}
利用此类先定义一个 bowTraining;
BOWKmeansTrainer bowTraining(1000); //定义聚类中心1000个,其余的默认参数;
然后,将得到的特征,例如SIFT特征,将每一副图的SIFT特征利用add函数加入到bowTraining中去。
for(int i=0; i<numOfPictures; i++)
bowTraining.add( descriptors( i ) );
将所有的特征加进去后,就可以进行聚类训练了:
Mat dictionary = bowTraining.cluster(); 这一步的时间根据特征的维度以及定义的词典中心的个数相关。
或者,将得到的特征合并成一个矩阵,这里贴出OpenCV的BOW内部合成矩阵的代码
int descCount = 0;
for( size_t i = 0; i < descriptors.size(); i++ )
descCount += descriptors[i].rows;
Mat mergedDescriptors( descCount, descriptors[0].cols, descriptors[0].type() );
for( size_t i = 0, start = 0; i < descriptors.size(); i++ )
{
Mat submut = mergedDescriptors.rowRange((int)start, (int)(start + descriptors[i].rows));
descriptors[i].copyTo(submut);
start += descriptors[i].rows;
}
同样:Mat dictionary = bowTraining.cluster( mergedDescriptors );
得到词典后,就要利用另一个类来进行图像BOW特征的提取----BOWImgDescriptorExtractor
class BOWImgDescriptorExtractor
{
public:
BOWImgDescriptorExtractor( const Ptr<DescriptorExtractor> &dextractor, const Ptr<DescriptorMatcher> & dmatcher );
virtual ~BOWImgDescriptorExtractor(){}
void setVocabulary( const Mat& vocabulary );
const Mat& getVocabulary() const;
void compute( const Mat& image, vector<KeyPoint> & keypoints,
Mat& imgDescriptor,
vector<vector<int> >* pointIdxOfClusters = 0,
Mat* descriptors = 0 );
int descriptorSize() const;
int descriptorType() const;
protected:
…
}
利用上面这BOW的第三个类别定义一个变量;
Ptr<DescriptorExtractor> extractor = DescriptorMatcher::create("SIFT"); //引号里面修改特征种类。
Ptr<DescriptorMatcher> matcher = DescriptorExtractor::create("BruteForce"); //引号里面修改匹配类型;
BOWImgDescriptorExtractor bowDE(extractor, matcher);
前面两个定义是为了方便初始化类的定义,在BOW图像特征定义完成后,便可以对每一副图片提取BOW的特征。
bowDE.setVocabulary(dictionary); //dictionary是通过前面聚类得到的词典;
for(int i=0; i<numOfPictures; i++)
{
vector<KeyPoint> keypoints;
SiftFeatureDetector detector;
detector.detect(pictures[i], keypoints);
bowDE.compute(pictures[i], keypoints, descriptors);
}
这样,整个BOW特征提取过程就结束了。
_____________________________
_______________________________
过程简介
-
提取训练集中图片的feature
-
将这些feature聚成n类。这n类中的每一类就相当于是图片的"单词",所有的n个类别构成"词汇表"。实现中n取1000,如果训练集很大,应增大取值。
-
对训练集中的图片构造bag of words,就是将图片中的feature归到不同的类中,然后统计每一类的feature的频率。这相当于统计一个文本中每一个单词出现的频率。
-
训练一个多类分类器,将每张图片的bag of words作为feature vector,将该张图片的类别作为label。
-
对于未知类别的图片,计算它的bag of words,使用训练的分类器进行分类。
步骤详解
提取feature并进行聚类
这一步用于提取待训练中所有图片的特征值并保存到一个vocab_descriptors(vector数组)中, 再使用bowtrainer对vocab_descriptors进行聚类的出单词本vocab(Mat 类型)
Mat vocab_descriptors;
// 遍历每一张图片,提取SURF特征值,存入到vocab_descriptors中
multimap<string,Mat> ::iterator i=train_set.begin();
for(;i!=train_set.end();i++)
{
vector<KeyPoint>kp;//关键点
Mat templ=(*i).second; //图片
Mat descrip; //特征值
//featureDectre是surf算法提取特征值
featureDecter->detect(templ,kp);
featureDecter->compute(templ,kp,descrip);
//push_back(Mat);在原来的Mat的最后一行后再加几行,元素为Mat时, 其类型和列的数目 必须和矩阵容器是相同的
vocab_descriptors.push_back(descrip);
}
//将每一副图的surf特征加入到bowTraining中去,就可以进行聚类训练了
vocab=bowtrainer->cluster(vocab_descriptors);
构造bag of words
这一步根据每张图片的特征点,统计这张图片各个类别出现的频率,作为这张图片的bag of words, 使用bowDescriptorExtractor根据上一步获取到的vocab进行setVocabulary,把vocab传递给它,然后用一张图片的特征点作为输入,就能计算每一类的特征点的频率
// 遍历每一张图片,提取SURF关键点,统计每一类的特征点频率
multimap<string,Mat> ::iterator i=train_set.begin();
for(;i!=train_set.end();i++)
{
vector<KeyPoint>kp; //关键点
string cate_nam=(*i).first; //类别名称, 根据文件夹目录名称
Mat tem_image=(*i).second; //对应的图片
Mat imageDescriptor; //统计出来的特征点频率
featureDecter->detect(tem_image,kp);
bowDescriptorExtractor->compute(tem_image,kp,imageDescriptor);
//push_back(Mat);在原来的Mat的最后一行后再加几行,元素为Mat时, 其类型和列的数目 必须和矩阵容器是相同的
//allsamples_bow的value的Mat中, 每一行都表示一张图片的bag of words
allsamples_bow[cate_nam].push_back(imageDescriptor);
}
训练分类器
使用的分类器是svm,用经典的1 vs all方法实现多类分类。对每一个类别都训练一个二元分类器。训练好后,对于待分类的feature vector,使用每一个分类器计算分在该类的可能性,然后选择那个可能性最高的类别作为这个feature vector的类别
stor_svms=new Ptr<SVM>[categories_size]; //初始化一个svm训练器
for(int i=0;i<categories_size;i++)
{
Mat tem_Samples( 0, allsamples_bow.at( category_name[i] ).cols, allsamples_bow.at( category_name[i] ).type() ); //获取上一步构建好的bag of word
Mat responses( 0, 1, CV_32SC1 );
tem_Samples.push_back( allsamples_bow.at( category_name[i] ) );
Mat posResponses( allsamples_bow.at( category_name[i]).rows, 1, CV_32SC1, Scalar::all(1) );
responses.push_back( posResponses );
for ( map<string,Mat>::iterator itr = allsamples_bow.begin(); itr != allsamples_bow.end(); ++itr )
{
if ( itr -> first == category_name[i] ) {
continue;
}
tem_Samples.push_back( itr -> second );
Mat response( itr -> second.rows, 1, CV_32SC1, Scalar::all( -1 ) );
responses.push_back( response );
}
//设置训练参数
stor_svms[i] = SVM::create();
stor_svms[i]->setType(SVM::C_SVC);
stor_svms[i]->setKernel(SVM::LINEAR);
stor_svms[i]->setGamma(3);
stor_svms[i]->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 100, 1e-6));
stor_svms[i]->train( tem_Samples, ROW_SAMPLE, responses); //关键步骤, 进行svm训练器的构建
}
对未知图片分类
使用某张待分类图片的bag of words作为feature vector输入,使用每一类的分类器计算判为该类的可能性,然后使用可能性最高的那个类别作为这张图片的类别。
Mat input_pic=imread(train_pic_path); //获取待分类图片
// 提取BOW描述子
vector<KeyPoint>kp;
Mat test;
featureDecter->detect(input_pic,kp);
bowDescriptorExtractor->compute(input_pic,kp,test);
int sign=0;
float best_score = -2.0f;
for(int i=0;i<categories_size;i++)
{
if(sign==0)
{
float scoreValue = stor_svms[i]->predict( test, noArray(), true );
float classValue = stor_svms[i]->predict( test, noArray(), false );
sign = ( scoreValue < 0.0f ) == ( classValue < 0.0f )? 1 : -1;
}
curConfidence = sign * stor_svms[i]->predict( test, noArray(), true );
if(curConfidence>best_score)
{
best_score=curConfidence;
prediction_category=cate_na;
}
}
cout<<"这张图属于:"<<prediction_category<<endl;
完整源码
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/ml/ml.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iostream>
#include <fstream>
#include <cstring>
#include <iterator>
#include <vector>
#include <map>
#include<fstream>
using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;
using namespace cv::ml;
#define DATA_FOLDER "data/"
#define TRAIN_FOLDER "data/train_images/"
#define TEMPLATE_FOLDER "data/templates/"
#define TEST_FOLDER "data/test_image"
#define RESULT_FOLDER "data/result_image/"
class categorizer
{
private :
// //从类目名称到数据的map映射
// map<string,Mat> result_objects;
//存放所有训练图片的BOW
map<string,Mat> allsamples_bow;
//从类目名称到训练图集的映射,关键字可以重复出现
multimap<string,Mat> train_set;
// 训练得到的SVM
Ptr<SVM> *stor_svms;
//类目名称,也就是TRAIN_FOLDER设置的目录名
vector<string> category_name;
//类目数目
int categories_size;
//用SURF特征构造视觉词库的聚类数目
int clusters;
//存放训练图片词典
Mat vocab;
Ptr<SURF> featureDecter;
Ptr<BOWKMeansTrainer> bowtrainer;
Ptr<BFMatcher> descriptorMacher;
Ptr<BOWImgDescriptorExtractor> bowDescriptorExtractor;
//构造训练集合
void make_train_set();
// 移除扩展名,用来讲模板组织成类目
string remove_extention(string);
public:
//构造函数
categorizer(int);
// 聚类得出词典
void bulid_vacab();
//构造BOW
void compute_bow_image();
//训练分类器
void trainSvm();
//将测试图片分类
void category_By_svm();
};
// 移除扩展名,用来讲模板组织成类目
string categorizer::remove_extention(string full_name)
{
//find_last_of找出字符最后一次出现的地方
int last_index=full_name.find_last_of(".");
string name=full_name.substr(0,last_index);
return name;
}
// 构造函数
categorizer::categorizer(int _clusters)
{
cout<<"开始初始化..."<<endl;
clusters=_clusters;
//初始化指针
int minHessian = 400;
featureDecter = SURF::create( minHessian );
bowtrainer = new BOWKMeansTrainer(clusters);
descriptorMacher = BFMatcher::create();
bowDescriptorExtractor = new BOWImgDescriptorExtractor(featureDecter,descriptorMacher);
// //boost库文件 遍历数据文件夹 directory_iterator(p)就是迭代器的起点,无参数的directory_iterator()就是迭代器的终点。
// boost::filesystem::directory_iterator begin_iter(TEMPLATE_FOLDER);
// boost::filesystem::directory_iterator end_iter;
// //获取该目录下的所有文件名
// for(;begin_iter!=end_iter;++begin_iter)
// {
// //文件的路径 data/templates/airplanes.jpg
// string filename=string(TEMPLATE_FOLDER)+begin_iter->path().filename().string();
// //文件夹名称 airplanes
// string sub_category =remove_extention(begin_iter->path().filename().string());
// //读入模板图片
// if(begin_iter->path().filename().string() != ".DS_Store") {
// Mat image=imread(filename);
// Mat templ_image;
// //存储原图模板
// result_objects[sub_category]=image;
// }
// }
cout<<"初始化完毕..."<<endl;
//读取训练集
make_train_set();
}
//构造训练集合
void categorizer::make_train_set()
{
cout<<"读取训练集..."<<endl;
string categor;
//递归迭代rescursive 直接定义两个迭代器:i为迭代起点(有参数),end_iter迭代终点
for(boost::filesystem::recursive_directory_iterator i(TRAIN_FOLDER),end_iter;i!=end_iter;i++)
{
// level == 0即为目录,因为TRAIN__FOLDER中设置如此
if(i.level()==0)
{
// 将类目名称设置为目录的名称
if((i->path()).filename().string() != ".DS_Store") {
categor=(i->path()).filename().string();
category_name.push_back(categor);
}
}
else
{
// 读取文件夹下的文件。level 1表示这是一副训练图,通过multimap容器来建立由类目名称到训练图的一对多的映射
string filename=string(TRAIN_FOLDER)+categor+string("/")+(i->path()).filename().string();
if((i->path()).filename().string() != ".DS_Store") {
Mat temp=imread(filename,CV_LOAD_IMAGE_GRAYSCALE);
pair<string,Mat> p(categor,temp);
//得到训练集
train_set.insert(p);
}
}
}
categories_size=category_name.size();
cout<<"发现 "<<categories_size<<"种类别物体..."<<endl;
}
// 训练图片feature聚类,得出词典
void categorizer::bulid_vacab()
{
FileStorage vacab_fs(DATA_FOLDER "vocab.xml",FileStorage::READ);
//如果之前已经生成好,就不需要重新聚类生成词典
if(vacab_fs.isOpened())
{
cout<<"图片已经聚类,词典已经存在.."<<endl;
vacab_fs.release();
}else
{
Mat vocab_descriptors;
// 对于每一幅模板,提取SURF算子,存入到vocab_descriptors中
multimap<string,Mat> ::iterator i=train_set.begin();
for(;i!=train_set.end();i++)
{
vector<KeyPoint>kp;
Mat templ=(*i).second;
Mat descrip;
featureDecter->detect(templ,kp);
featureDecter->compute(templ,kp,descrip);
//push_back(Mat);在原来的Mat的最后一行后再加几行,元素为Mat时, 其类型和列的数目 必须和矩阵容器是相同的
vocab_descriptors.push_back(descrip);
}
// vocab_descriptors.convertTo(vocab_descriptors, CV_32F);
cout << "训练图片开始聚类..." << endl;
//将每一副图的ORB特征加入到bowTraining中去,就可以进行聚类训练了
// 对ORB描述子进行聚类
vocab=bowtrainer->cluster(vocab_descriptors);
cout<<"聚类完毕,得出词典..."<<endl;
//以文件格式保存词典
FileStorage file_stor(DATA_FOLDER "vocab.xml",FileStorage::WRITE);
file_stor<<"vocabulary"<<vocab;
file_stor.release();
}
}
//构造bag of words
void categorizer::compute_bow_image()
{
cout<<"构造bag of words..."<<endl;
FileStorage va_fs(DATA_FOLDER "vocab.xml",FileStorage::READ);
//如果词典存在则直接读取
if(va_fs.isOpened())
{
Mat temp_vacab;
va_fs["vocabulary"] >> temp_vacab;
bowDescriptorExtractor->setVocabulary(temp_vacab);
va_fs.release();
}
else
{
//对每张图片的特征点,统计这张图片各个类别出现的频率,作为这张图片的bag of words
bowDescriptorExtractor->setVocabulary(vocab);
}
//如果bow.txt已经存在说明之前已经训练过了,下面就不用重新构造BOW
string bow_path=string(DATA_FOLDER)+string("bow.txt");
boost::filesystem::ifstream read_file(bow_path);
// //如BOW已经存在,则不需要构造
if(read_file.is_open())
{
cout<<"BOW 已经准备好..."<<endl;
}
else{
// 对于每一幅模板,提取SURF算子,存入到vocab_descriptors中
multimap<string,Mat> ::iterator i=train_set.begin();
for(;i!=train_set.end();i++)
{
vector<KeyPoint>kp;
string cate_nam=(*i).first;
Mat tem_image=(*i).second;
Mat imageDescriptor;
featureDecter->detect(tem_image,kp);
bowDescriptorExtractor->compute(tem_image,kp,imageDescriptor);
//push_back(Mat);在原来的Mat的最后一行后再加几行,元素为Mat时, 其类型和列的数目 必须和矩阵容器是相同的
allsamples_bow[cate_nam].push_back(imageDescriptor);
}
//简单输出一个文本,为后面判断做准备
boost::filesystem::ofstream ous(bow_path);
ous<<"flag";
cout<<"bag of words构造完毕..."<<endl;
}
}
//训练分类器
void categorizer::trainSvm()
{
int flag=0;
for(int k=0;k<categories_size;k++)
{
string svm_file_path=string(DATA_FOLDER) + category_name[k] + string("SVM.xml");
FileStorage svm_fil(svm_file_path,FileStorage::READ);
//判断训练结果是否存在
if(svm_fil.isOpened())
{
svm_fil.release();
continue;
}
else
{
flag=-1;
break;
}
}
//如果训练结果已经存在则不需要重新训练
if(flag!=-1)
{
cout<<"分类器已经训练完毕..."<<endl;
}else
{
stor_svms=new Ptr<SVM>[categories_size];
cout<<"训练分类器..."<<endl;
for(int i=0;i<categories_size;i++)
{
Mat tem_Samples( 0, allsamples_bow.at( category_name[i] ).cols, allsamples_bow.at( category_name[i] ).type() );
Mat responses( 0, 1, CV_32SC1 );
tem_Samples.push_back( allsamples_bow.at( category_name[i] ) );
Mat posResponses( allsamples_bow.at( category_name[i]).rows, 1, CV_32SC1, Scalar::all(1) );
responses.push_back( posResponses );
for ( map<string,Mat>::iterator itr = allsamples_bow.begin(); itr != allsamples_bow.end(); ++itr )
{
if ( itr -> first == category_name[i] ) {
continue;
}
tem_Samples.push_back( itr -> second );
Mat response( itr -> second.rows, 1, CV_32SC1, Scalar::all( -1 ) );
responses.push_back( response );
}
//设置训练参数
stor_svms[i] = SVM::create();
stor_svms[i]->setType(SVM::C_SVC);
stor_svms[i]->setKernel(SVM::LINEAR);
stor_svms[i]->setGamma(3);
stor_svms[i]->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 100, 1e-6));
stor_svms[i]->train( tem_Samples, ROW_SAMPLE, responses);
//存储svm
string svm_filename=string(DATA_FOLDER) + category_name[i] + string("SVM.xml");
cout<<svm_filename.c_str()<<endl;
stor_svms[i]->save(svm_filename.c_str());
}
cout<<"分类器训练完毕..."<<endl;
}
}
//对测试图片进行分类
void categorizer::category_By_svm()
{
cout<<"物体分类开始..."<<endl;
Mat gray_pic;
Mat threshold_image;
string prediction_category;
float curConfidence;
boost::filesystem::directory_iterator begin_train(TEST_FOLDER);
boost::filesystem::directory_iterator end_train;
for(;begin_train!=end_train;++begin_train)
{
//获取该目录下的图片名
string train_pic_name=(begin_train->path()).filename().string();
string train_pic_path=string(TEST_FOLDER)+string("/")+(begin_train->path()).filename().string();
//读取图片
if((begin_train->path()).filename().string() == ".DS_Store") {
continue;
}
Mat input_pic=imread(train_pic_path);
cvtColor(input_pic,gray_pic,CV_BGR2GRAY);
// 提取BOW描述子
vector<KeyPoint>kp;
Mat test;
featureDecter->detect(gray_pic,kp);
bowDescriptorExtractor->compute(gray_pic,kp,test);
int sign=0;
float best_score = -2.0f;
for(int i=0;i<categories_size;i++)
{
string cate_na=category_name[i];
string f_path=string(DATA_FOLDER)+cate_na + string("SVM.xml");
FileStorage svm_fs(f_path,FileStorage::READ);
//读取SVM.xml
if(svm_fs.isOpened())
{
svm_fs.release();
Ptr<SVM> st_svm = Algorithm::load<SVM>(f_path.c_str());
if(sign==0)
{
float score_Value = st_svm->predict( test, noArray(), true );
float class_Value = st_svm->predict( test, noArray(), false );
sign = ( score_Value < 0.0f ) == ( class_Value < 0.0f )? 1 : -1;
}
curConfidence = sign * st_svm->predict( test, noArray(), true );
}
else
{
if(sign==0)
{
float scoreValue = stor_svms[i]->predict( test, noArray(), true );
float classValue = stor_svms[i]->predict( test, noArray(), false );
sign = ( scoreValue < 0.0f ) == ( classValue < 0.0f )? 1 : -1;
}
curConfidence = sign * stor_svms[i]->predict( test, noArray(), true );
}
if(curConfidence>best_score)
{
best_score=curConfidence;
prediction_category=cate_na;
}
}
//将图片写入相应的文件夹下
boost::filesystem::directory_iterator begin_iterater(RESULT_FOLDER);
boost::filesystem::directory_iterator end_iterator;
//获取该目录下的文件名
for(;begin_iterater!=end_iterator;++begin_iterater)
{
if(begin_iterater->path().filename().string()==prediction_category)
{
string filename=string(RESULT_FOLDER)+prediction_category+string("/")+train_pic_name;
imwrite(filename,input_pic);
}
}
cout<<"这张图属于:"<<prediction_category<<endl;
}
}
int main(void)
{
int clusters=1000;
categorizer c(clusters);
//特征聚类
c.bulid_vacab();
//构造BOW
c.compute_bow_image();
//训练分类器
c.trainSvm();
//将测试图片分类
c.category_By_svm();
return 0;
}
__________________________
__________________________
采用SIFT+BOW来进行实现。关于SIFT特征提取的介绍很多,一般都比较复杂难懂,尤其是对我这种数学不怎么好的人。看了几天还是很朦胧。OpenCV中有对图像SIFT特征提取的函数,下面给出提取的过程:
image = imread(path);
//sift关键点检测
SiftFeatureDetector detector;
detector.detect(image, keyPoints);
//sift关键点描述,角度,强度等
SiftDescriptorExtractor extractor;
extractor.compute(image, keyPoints, descriptor);
另外函数的头文件是:#include <opencv2/nonfree/features2d.hpp>,之前的版本是放在#include "opencv2/features2d/features2d.hpp"中的。还是多注意一下吧。
实现原理:
BOW模型的处理过程:
1.SIFT特征提取。SIFT 特征提取是求出图像的关键点信息,包括角度,大小以及强度。关键点,也就是能够代表图像关键信息的部分,这也是Bag of words中单词的组成。一个图像通常有很多的关键点。
2.聚类。我们将每幅图像中的关键点信息添加到词袋中,并定义聚类中心的数量N。然后将词袋中的关键点通过Kmeans算法聚类到N个类中。同时得到这N个类的中心点组成N*128的dictionary,每个中心都可以代表这个类。
3.求图像的直方图。将图像的关键点信息重新放到词包中,根据落在每个类中关键点的数量来得到图像的直方图,大小为1*N。将每幅图像进行处理,得到图像在BOW模型下的特征。
4.图像匹配。将测试图像进行相同的处理,同样也得到1*N的特征。根据测试图像与训练图像特征之间的距离,并将距离较小的图像作为检索的结果。
实现过程:
OpenCV中已经对步骤中的过程进行了封装,我们只需要简单的调用就可以。上面的代码中我们已经完成了第一步。
第二步和第三步BOW模型的实现,我们可以采用调用函数BOWKmeansTrainer进行实现。
int clusterNum =260;
//clusterNum代表有多少词
BOWKMeansTrainer trainer(clusterNum);
同时需要将提取到的SIFT特征描述添加到trainer中
//descriptor是每幅图像的sift关键点描述
trainer.add(descriptor);
所有图像的descriptor添加完成后,进行聚类得到dictionary,也就是聚类的中心。
Mat dictionary = trainer.cluster();
接下来需要得到每幅图像直方图。过程如下
Ptr<DescriptorExtractor> extractor = DescriptorExtractor::create("SIFT");
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce");
BOWImgDescriptorExtractor bowDE(extractor, matcher);
bowDE.setVocabulary(dictionary);
对每幅图像图像进行如下操作:
Mat BOWdescriptor;
//sift关键点检测
vector<KeyPoint> keyPoints;
SiftFeatureDetector detector;
detector.detect(curImg, keyPoints);
//BOWdecriptor表示每个图像的bow码本,即直方图,大小为1*clusterNum
bowDE.compute(curImg, keyPoints, BOWdescriptor);
//归一化
normalize(BOWdescriptor, BOWdescriptor, 1.0, 0.0, NORM_MINMAX);
得到的BOWdescriptor就是每个图像的直方图表示,可用做图像检索的特征。最简单的方法就是求测试图像的直方图与训练图像之间的欧明距离,得到检索图像。不过检索的方式不一样,效率和质量也不同。
过程中长的姿势:
刚开始写的时候,不知道怎么求图像的码本,就不停的翻看OpenCV中关于函数BOWImgDescriptorExtractor::compute的解释,刚开始看的是中文解释,看了很久也没有看懂,后面找到了英文的注释,顿时就明白了。看来还是要英语好啊!!!!下面给出英语版的,中文的就算了。。。
BOWImgDescriptorExtractor::compute
Computes an image descriptor using the set visual vocabulary.
C++: void BOWImgDescriptorExtractor::compute(const Mat& image, vector<KeyPoint>& keypoints, Mat& imgDescriptor, vector<vector<int>>* pointIdxsOfClusters=0, Mat*descriptors=0 )¶
Parameters:
image – Image, for which the descriptor is computed.
keypoints – Keypoints detected in the input image.
imgDescriptor – Computed output image descriptor.
pointIdxsOfClusters – Indices of keypoints that belong to the cluster. This means that pointIdxsOfClusters[i] are keypoint indices that belong to the i -th cluster (word of vocabulary) returned if it is non-zero.
descriptors – Descriptors of the image keypoints that are returned if they are non-zero.