opencv中的SVM圖像分類(二)


 

opencv中的SVM圖像分類(二)

標簽: svm圖像
 分類:
 

目錄(?)[+]

 

原創作品 轉載請注明出http://blog.csdn.net/always2015/article/details/47107129

上一篇博文對圖像分類理論部分做了比較詳細的講解,這一篇主要是對圖像分類代碼的實現進行分析。理論部分我們談到了使用BOW模型,但是BOW模型如何構建以及整個步驟是怎么樣的呢?可以參考下面的博客http://www.cnblogs.com/yxy8023ustc/p/3369867.html,這一篇博客很詳細講解了BOW模型的步驟了,主要包含以下四個步驟:

  1. 提取訓練集中圖片的feature
  2. 將這些feature聚成n類。這n類中的每一類就相當於是圖片的“單詞”,所有的n個類別構成“詞匯表”。我的實現中n取1000,如果訓練集很大,應增大取值。
  3. 對訓練集中的圖片構造bag of words,就是將圖片中的feature歸到不同的類中,然后統計每一類的feature的頻率。這相當於統計一個文本中每一個單詞出現的頻率
  4. 訓練一個多類分類器,將每張圖片的bag of words作為feature vector,將該張圖片的類別作為label。

對於未知類別的圖片,計算它的bag of words,使用訓練的分類器進行分類。 
上面整個工程步驟所涉及到的函數,我都放在一個類categorizer里, 
下面按步驟說明具體實現,程序示例有所省略,完整的程序可看工程源碼。

NO.1、特征提取

對圖片特征的提取包括對每張訓練圖片的特征提取和每張待檢測圖片特征的提取,我使用的是surf,所以使用OpenCV的SurfFeatureDetector檢測特征點,然后再用SurfDescriptorExtractor抽取特征點描述符。對於特征點的檢測和特征描述符的講解可以參考中文opencv中文官網http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/feature_detection/feature_detection.html#feature-detection以及http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/feature_description/feature_description.html#feature-description 
我訓練圖片特征提取的示例代碼如下:

         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); descriptorExtractor->compute(templ,kp,descrip); //push_back(Mat);在原來的Mat的最后一行后再加幾行,元素為Mat時, 其類型和列的數目 必須和矩陣容器是相同的 vocab_descriptors.push_back(descrip); }

注意:上述代碼只是工程的一個很小的部分,有些變量在類中已經定義,在這里沒有貼出來,例如上述的train_set訓練圖片的映射,定義為:

//從類目名稱到訓練圖集的映射,關鍵字可以重復出現 multimap<string,Mat> train_set;

將每張圖片的特征描述符存儲起來vocab_descriptors,然后為后面聚類和構造訓練圖片詞典做准備。

NO.2、feature聚類

由於opencv封裝了一個類BOWKMeansExtractor[2],這一步非常簡單,將所有圖片的feature vector丟給這個類,然后調用cluster()就可以訓練(使用KMeans方法)出指定數量(步驟介紹中提到的n)的類別。輸入vocab_descriptors就是第1步計算得到的結果,返回的vocab是一千個向量,每個向量是某個類別的feature的中心點。 
示例代碼如下:

//將每一副圖的Surf特征利用add函數加入到bowTraining中去,就可以進行聚類訓練了 bowtrainer->add(vocab_descriptors); // 對SURF描述子進行聚類 vocab=bowtrainer->cluster();

bowtrainer的定義如下:

bowtrainer=new BOWKMeansTrainer(clusters);

NO.3、構造bag of words

對每張圖片的特征點,將其歸到前面計算的類別中,統計這張圖片各個類別出現的頻率,作為這張圖片的bag of words。由於opencv封裝了BOWImgDescriptorExtractor[2]這個類,這一步也走得十分輕松,只需要把上面計算的vocab丟給它,然后用一張圖片的特征點作為輸入,它就會計算每一類的特征點的頻率。

allsamples_bow這個map的key就是某個類別,value就是這個類別中所有圖片的bag of words,即Mat中每一行都表示一張圖片的bag of words。

//對每張圖片的特征點,統計這張圖片各個類別出現的頻率,作為這張圖片的bag of words bowDescriptorExtractor->setVocabulary(vocab); } // 對於每一幅模板,提取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); }

上面部分變量的定義如下:

//存放所有訓練圖片的BOW map<string,Mat> allsamples_bow; //特征檢測器detectors與描述子提取器extractors 泛型句柄類Ptr Ptr<FeatureDetector> featureDecter; Ptr<DescriptorExtractor> descriptorExtractor; Ptr<BOWKMeansTrainer> bowtrainer; Ptr<BOWImgDescriptorExtractor> bowDescriptorExtractor; Ptr<FlannBasedMatcher> descriptorMacher;

NO.4、訓練分類器

我使用的分類器是svm,用經典的1 vs all方法實現多類分類。對每一個類別都訓練一個二元分類器。訓練好后,對於待分類的feature vector,使用每一個分類器計算分在該類的可能性,然后選擇那個可能性最高的類別作為這個feature vector的類別。

訓練二元分類器

allsamples_bow:第3步中得到的結果。 
category_name:針對哪個類別訓練分類器。 
svmParams:訓練svm使用的參數。 
stor_svms:針對category_name的分類器。 
屬於category_name的樣本,label為1;不屬於的為-1。准備好每個樣本及其對應的label之后,調用CvSvm的train方法就可以了。

示例代碼如下:

        stor_svms=new CvSVM[categories_size]; //設置訓練參數 SVMParams svmParams; svmParams.svm_type = CvSVM::C_SVC; svmParams.kernel_type = CvSVM::LINEAR; svmParams.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6); 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 ( auto 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].train( tem_Samples, responses, Mat(), Mat(), svmParams ); //存儲svm string svm_filename=string(DATA_FOLDER) + category_name[i] + string("SVM.xml"); stor_svms[i].save(svm_filename.c_str()); }

對於SVM的參數以及函數調用的介紹可以參考中文官網http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html#introductiontosvms

部分變量的定義如下:

   // 訓練得到的SVM CvSVM *stor_svms; //類目名稱,也就是TRAIN_FOLDER設置的目錄名 vector<string> category_name; //類目數目 int categories_size; 

NO.5、對未知圖片進行分類

分類

使用某張待分類圖片的bag of words作為feature vector輸入,使用每一類的分類器計算判為該類的可能性,然后使用可能性最高的那個類別作為這張圖片的類別。

prediction_category就是結果,test就是某張待分類圖片的bag of words。示例代碼如下:

        Mat input_pic=imread(train_pic_path);
        imshow("輸入圖片:",input_pic); cvtColor(input_pic,gray_pic,CV_BGR2GRAY); // 提取BOW描述子 vector<KeyPoint>kp; Mat test; featureDecter->detect(gray_pic,kp); bowDescriptorExtractor->compute(gray_pic,kp,test); float scoreValue = stor_svms[i].predict( test, true ); float classValue = stor_svms[i].predict( test, false ); sign = ( scoreValue < 0.0f ) == ( classValue < 0.0f )? 1 : -1; curConfidence = sign * stor_svms[i].predict( test, true ); if(curConfidence>best_score) { best_score=curConfidence; prediction_category=cate_na; }

上面就是四個主要步驟的部分示例代碼,很多其他部分代碼沒有貼出來,比如說如何遍歷文件夾下面的所有不同類別的圖片,因為訓練圖片的樣本比較多的話,訓練圖片是一個時間比較長久的,那么如何在對一張待測圖片進行分類的時候,不需要每次都重復訓練樣本,而是直接讀取之前已經訓練好的BOW。。。。很多很多。

我的main函數實現如下:

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; }

下面來看看我的工程部分運行結果如下: 
這里寫圖片描述

部分分類下圖所示: 
這里寫圖片描述
這里寫圖片描述 
這里寫圖片描述 
這里寫圖片描述

左邊為輸入圖片,右邊為所匹配的類別模型。准確率為百分之八九十。

我的整個工程文件以及我的所有訓練的圖片存放在這里http://download.csdn.net/detail/always2015/8944973以及http://download.csdn.net/detail/always2015/8944959,需要的可以下載,自己在找訓練圖片寫代碼花了很多時間,下載完后自行解壓,project data文件夾直接放在D盤就行,里面存放訓練的圖片和待測試圖片,以及訓練過程中生成的中間文件,另一個文件夾object_classfication_end則是工程文件,我用的是vs2010打開即可,下面工程里有幾個要注意的地方:

1、在這個模塊中使用到了c++的boost庫,但是在這里有一個版本的限制。這個模塊的代碼只能在boost版本1.46以上使用,這個版本以下的就不能用了,直接運行就會出錯,這是最需要注意的。因為在1.46版本以上中對比CsSVM這個類一些成員函數做了一些私有化的修改,所以在使用該類初始化對象時候需要注意。

2、我的模塊所使用到的函數和產生的中間結果都是在一個categorizer類中聲明的,由於不同的執行階段中間結果有很多個,例如:訓練圖片聚類后所得到單詞表矩陣,svm分類器的訓練的結果等,中間結果的產生是相當耗時的,所以在剛開始就考慮到第一次運行時候把他以文件XML的格式保存下來,下次使用到的時候在讀取。將一個矩陣存入文本的時候可以直接用輸出流的方式將一個矩陣存入,但是讀取時候如果用輸入流直接一個矩陣變量的形式讀取,那就肯定報錯,因為輸入流不支持直接對矩陣的操作,所以這時候只能對矩陣的元素一個一個進行讀取了。

3、在測試的時候,如果輸入的圖片太小,或者全為黑色,當經過特征提取和單詞構造完成使用svm進行分類時候會出現錯誤。經過調試代碼,發現上述圖片在生成該圖片的單詞的時候所得到的單詞矩陣會是一個空矩陣,即該矩陣的行列數都為0,所以在使用svm分類器時候就出錯。所以在使用每個輸入圖片的單詞矩陣的時候先做一個判斷,如果該矩陣行列數都為0,那么該圖片直接跳過。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM