提取訓練集中圖片的featureios
將這些feature聚成n類。這n類中的每一類就至關因而圖片的"單詞",全部的n個類別構成"詞彙表"。實現中n取1000,若是訓練集很大,應增大取值。算法
對訓練集中的圖片構造bag of words,就是將圖片中的feature歸到不一樣的類中,而後統計每一類的feature的頻率。這至關於統計一個文本中每個單詞出現的頻率。數組
訓練一個多類分類器,將每張圖片的bag of words做爲feature vector,將該張圖片的類別做爲label。ide
對於未知類別的圖片,計算它的bag of words,使用訓練的分類器進行分類。函數
這一步用於提取待訓練中全部圖片的特徵值並保存到一個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, 使用bowDescriptorExtractor根據上一步獲取到的vocab進行setVocabulary,把vocab傳遞給它,而後用一張圖片的特徵點做爲輸入,就能計算每一類的特徵點的頻率ui
// 遍歷每一張圖片,提取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的類別spa
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; }