[學習opencv]bow 演算法實現圖片分類bag of word
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
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.