opencv bow 算法實現圖片分類

過程簡介

  1. 提取訓練集中圖片的featureios

  2. 將這些feature聚成n類。這n類中的每一類就至關因而圖片的"單詞",全部的n個類別構成"詞彙表"。實現中n取1000,若是訓練集很大,應增大取值。算法

  3. 對訓練集中的圖片構造bag of words,就是將圖片中的feature歸到不一樣的類中,而後統計每一類的feature的頻率。這至關於統計一個文本中每個單詞出現的頻率。數組

  4. 訓練一個多類分類器,將每張圖片的bag of words做爲feature vector,將該張圖片的類別做爲label。ide

  5. 對於未知類別的圖片,計算它的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傳遞給它,而後用一張圖片的特徵點做爲輸入,就能計算每一類的特徵點的頻率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;
}
相關文章
相關標籤/搜索