1. 程式人生 > >BoW(SIFT/SURF/...)+SVM/KNN的OpenCV 實現

BoW(SIFT/SURF/...)+SVM/KNN的OpenCV 實現

程式包:猛戳我

物體分類

物體分類是計算機視覺中一個很有意思的問題,有一些已經歸類好的圖片作為輸入,對一些未知類別的圖片進行預測。

下面會說明我使用OpenCV實現的兩種方法,第一種方法是經典的bag of words的實現;第二種方法基於第一種方法,但使用的分類方法有所不同。

在此之前,有必要說明一下輸入的格式,輸入訓練資料資料夾,和CalTech 101的組織類似。如下所示,每一類圖片都放在一個資料夾裡,資料夾的名字就是類別的名字,不需要特別的說明檔案。

test/
    category1/
        img01.jpg
        img02.jpg
        …
    category2/
        img01.jpg
        img03.jpg
        …
    …

完整的程式碼和可使用的訓練樣本可在這裡找到,下面程式碼示例的開頭註釋為該段程式碼所在函式。

第一種方法:Bag of words

步驟描述

如[1]所言,這個方法有4個步驟:

  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,使用訓練的分類器進行分類。

下面按步驟說明具體實現,程式示例有所省略,完整的程式可看原始碼,我已經很努力地壓縮了程式碼量,而沒有降低可讀性。

1 提取feature

這一步比較簡單,對訓練集中的每一張圖片,使用opencv的FeatureDetector檢測特徵點,然後再用DescriptorExtractor抽取特徵點描述符。

01// BuildVocabulary
02Mat allDescriptors;
03loop over each category {
04    loop over each image in current category {
05
        Mat image = imread( filepath );
06        vector<KeyPoint> keyPoints;
07        Mat descriptors;
08        detector -> detect( image, keyPoints);
09        extractor -> compute( image, keyPoints, descriptors );
10        allDescriptors.push_back( descriptors );   
11    }
12}

2 feature聚類

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

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

1// BuildVocabulary
2BOWKMeansTrainer bowTrainer( wordCount );
3Mat vocabulary = bowTrainer.cluster( allDescriptors );

3 構造bag of words

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

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

01// ComputeBowImageDescriptors
02map<string, Mat> samples;
03Ptr<BOWImgDescriptorExtractor> bowExtractor;
04loop over each category {
05    loop over each image in current category {
06        Mat image = imread( filepath );
07        vector<KeyPoint> keyPoints;
08        detector -> detect( image, keyPoints );
09        Mat imageDescriptor;
10        bowExtractor -> compute( image, keyPoints, imageDescriptor );
11        samples[current category].push_back( imageDescriptor );
12    }
13}

4 訓練分類器

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

訓練二元分類器

  • samples:第3步中得到的結果。
  • category:針對哪個類別訓練分類器。
  • svmParams:訓練svm使用的引數。
  • svm:針對category的分類器。

屬於category的樣本,label為1;不屬於的為-1。準備好每個樣本及其對應的label之後,呼叫CvSvm的train方法就可以了。

01voidTrainSvm(constmap<string, Mat>& samples,
02              conststring& category,
03              constCvSVMParams& svmParams,
04               CvSVM* svm ) {
05    Mat allSamples(0, samples.at( category ).cols, samples.at( category ).type() );
06    Mat responses(0,1, CV_32SC1 );
07    allSamples.push_back( samples.at( category ) );
08    Mat posResponses( samples.at( category ).rows,1, CV_32SC1, Scalar::all(1) );
09    responses.push_back( posResponses );
10   for(autoitr = samples.begin(); itr != samples.end(); ++itr ) {
11       if( itr -> first == category ) {
12           continue;
13        }
14        allSamples.push_back( itr -> second );
15        Mat response( itr -> second.rows,1, CV_32SC1, Scalar::all( -1) );
16        responses.push_back( response );
17       
18    }
19    svm -> train( allSamples, responses, Mat(), Mat(), svmParams );
20}

分類

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

category就是結果,queryDescriptor就是某張待分類圖片的bag of words。

01// ClassifyBySvm
02floatconfidence = -2.0f;
03string category;
04for(autoitr = samples.begin(); itr != samples.end(); ++itr ) {
05    CvSVM svm;
06    TrainSvm( samples, itr->first, svmParams, &svm );
07   floatcurConfidence=sign*svm.predict(queryDescriptor,true);
08   if( curConfidence > confidence ) {
09            confidence = curConfidence;
10            category = itr -> first;
11    }
12}

第二種方法:相關性排序

這種方法的前面1-3步和bag of words一樣,只是分類的時候有些別出心裁。利用上面的類比,每張圖片的bag of words就好比是詞彙表中每個單詞出現的頻率,我們完全有理由相信相同類別的圖片的頻率直方圖比較接近。由此受到啟發,可以找出已有資料庫待中與待分類的圖片的最接近的圖片,將該圖片的類別作為待分類圖片的類別。

在實現的時候,我並沒有僅僅使用一張最接近的圖片,而是找出資料庫中最接近的9張圖片,最後的結果類別就是包含這9張圖片中最多張數的那一類。

01// ClassifyByMatch
02structMatch{
03       stringcategory;
04       floatdistance;
05};
06priority_queue<Match,vector<Match>>matchesMinQueue;
07Ptr<DescriptorMatcher>histogramMatcher=newBFMatcher(normType);
08constintnumNearestMatch=9;
09for(autoitr=samples.begin();itr!=samples.end();++itr){
10   vector<vector<DMatch>>matches;
11   histogramMatcher->knnMatch(queryDescriptor,itr->second,matches,numNearestMatch);
12   for(autoitr2=matches[0].begin();itr2!=matches[0].end();++itr2){
13       matchesMinQueue.push(Match(itr->first,itr2->distance) );
14   }
15}

找出包含這9張圖片中最多張數的那一類。

01// ClassifyByMatch
02string category;
03intmaxCount =0;
04map<string, size_t> categoryCounts;
05size_t select = std::min(static_cast<size_t>( numNearestMatch ), matchesMinQueue.size() );
06for( size_t i =0; i < select; ++i ) {
07    string& c = matchesMinQueue.top().category;
08    ++categoryCounts[c];
09   intcurrentCount = categoryCounts[c];
10   if( currentCount > maxCount ) {
11        maxCount = currentCount;
12        category = c;
13    }
14    matchesMinQueue.pop();
15}

快取結果

該操作出現的函式: main, BuildVocabulary, ComputeBowImageDescriptors。

在第一次處理之後,我將“詞彙表”,每張圖片的bag of words,每個類別的svm分別儲存在了(相對於結果資料夾)vocabulary.xml.gz,bagOfWords資料夾和svms資料夾中。這樣下一次對某張圖片進行分類的時候,就可以直接讀取這些檔案而不必每次都計算,訓練樣本很多的時候,這些計算十分耗時。

不足之處

Bag of words方法沒有考慮特徵點的相對位置,而每類物體大都有自己特定的結構,這方面的資訊沒有利用起來。用上面一貫的類比,就好像搜尋引擎只使用了單詞頻率,而沒有考慮句子一樣,沒有結構的分析。

效果

對於我打包在作業資料夾中的訓練資料和測試資料,第一種方法有80%的圖被正確分類,第二種方法有67%的圖被正確分類,均高出20%的隨機猜測很多。

左側的圖是使用Bag of words方法的所有結果,右側的圖是使用第二種方法的所有結果。

clip_image001clip_image002

參考資料

[1] Csurka, Gabriella, et al. Visual categorization with bags of keypoints.Workshop on statistical learning in computer vision, ECCV. Vol. 1. 2004.

[2] http://docs.opencv.org/modules/features2d/doc/object_categorization.html

No related posts.