使用HOG特征+BP神經網絡進行車標識別



先挖個坑,快期末考試了,有空填上w

好了,今晚剛好有點閑,就把坑填上吧。

//-------------------------------開篇-------------------------------------------

首先講一下,這篇隨筆不是講HOG特征是什么,怎么提取(這種圖像特征網上一搜一大把),也不是講BP神經網絡工作原理,發展史啥的(機器學習小白,ANN深究我也不懂)。在這里我要講的是,車標識別怎么code,怎么使用OpenCV自帶的BP神經網絡訓練,以及識別。好了廢話不多說,咱們開始吧。

//------------------------要准備的東西---------------------------------

正式講代碼前講一下正式完成本工程需要什么准備工作。

1、配置Opencv 2.4.6及以上版本的VS 2010+,OpenCV 3可能改動的比較大,對本工程來說不建議。

2、一個車標庫,盡量多一點,本工程共使用900來張車標樣本,分為訓練集(560張)和測試集(371張)

    (工程遷移到我的Github倉庫了)

 

//--------------------------正篇-----------------------------------------

准備好了以上的東西,我們就可以開始了。

我將結合代碼分幾步來講怎么識別圖像特征、喂給BP神經網絡來識別車標,一步一步擴充代碼

////第一步  建立工程

        簡單講一下工程預處理吧,本工程就一個main.cpp文件,因此在建好工程后,建一個main.cpp就可以了。然后將我們的車標庫test測試集和train訓練集放在和整個工程同一目錄下。

        為了讀取圖片方便,我們使用兩個txt文件trainpath、和testpath來保存每張圖片的路徑(路徑文件在車標庫里有,但是如果要使用這兩個文件,就必須把他們放在main.cpp同一個目錄下,同時,車標庫也必須放在和工程同一目錄下)

這是車標庫和工程的存放位置關系

  

這是圖片路徑文件和main.cpp的關系

        為main.cpp添加opencv的頭文件,因為對OpenCV的結構不是特別熟,因此大家只要把萬用的頭文件一股腦寫出來就行

1 #include <iostream>
2 #include <fstream>
3 #include <opencv.hpp>
4 #include <highgui.h>
5 #include <cxcore.h>
6 #include <cstring>
7 #include <ml.h>
8 #include <vector>
9 #include <iomanip>

     本工程用了以上頭文件。

   哦對了,還有兩個名字空間,寫一下吧

1 using namespace std;
2 using namespace cv;

有些頭文件就用了一兩個函數啦,比如cstring就用到了個memset()23333,還有些OpenCV 1.x版本的頭文件也挺多余的,不過還是都寫上吧,免得報錯了

 

////第二步    初始化工作

 然后我們就開始帶代碼的編寫工作了,我們按照main.cpp的代碼順序來講。

我們先定義代碼要用到的全局變量:

1 //----------------------------全局變量定義---------------------------------
2     vector<float> descriptors;               //HOG特征存放向量 
3     float    data[m_NUM][F_NUM];             //樣本特征存放數組
4     float    f[1][F_NUM];                        //測試樣本特征存放數組
5     float    dataCls[m_NUM][CLASSNUM];       //樣本所屬類別
6     int      mClass ;                        //訓練樣本所屬類別
7     int      dNum;                           //訓練樣本個數

其中有幾個宏要在之前定義一下

1 #define  F_NUM    1764     //7*7*9*4  車標特征維數
2 #define  m_NUM    560      //訓練樣本個數
3 #define  CLASSNUM 4        //車標種類 

解釋一下兩段數據的設置

首先講一下特征數目吧,HOG特征其實是一個1×N維的特征矩陣,N的確定由檢測窗口大小、塊大小、胞元大小決定。每個胞元9個bin。

本實驗檢測窗口定為64×64,就是整張圖片的大小,塊大小16×16,胞元8×8,步進8×8,這樣一張圖片就有(64/8-1)*(64/8-1)*9*(16*16)/(8*8)=1764維特征

那么560個樣本就有560*1764個特征,就構成了特征矩陣data[560][1764]。

來看看OpenCV的神經網絡訓練函數

1 int CvANN_MLP::train(const Mat& inputs, const Mat& outputs, const Mat& sampleWeights, const Mat& sampleIdx=Mat(), CvANN_MLP_TrainParams params=CvANN_MLP_TrainParams(), int flags=0 );

這是我們之后要用到的關鍵函數,OpenCV自帶的神經網絡訓練函數,我們依次來看下參數

第一個input是一個num×F_NUM的訓練數據輸入矩陣,num是樣本個數,F_NUM是每個樣本的特征數目,是不是剛好對應了我們的data矩陣。但是我們看到,data是浮點型數組,inputs是Mat陣,怎么統一呢?其實OpenCV在初始化Mat的時候,就可以使用一般的二維數組進行賦值,只要數據類型匹配,並且維度相等就行了,就像下面一樣

1 Mat trainDate(m_NUM,F_NUM,CV_32FC1,data);

這里使用一個data的首地址初始化了trainData這個輸入陣。

再來解釋下dataCls為什么是560×4的。

繼續看trian()函數的第二個參數,outputs,是一個num×CLASSNUM的數據陣,num是樣本個數,CLASSNUM是樣本的總類別數。

當然對於560個數據,每個數據都要有一個類別。CLASSNUM是4,那么這個陣具體是怎么樣初始化的呢?

舉個例子,0號樣本屬於第1類,那么dataCls[0]={1,0,0,0}   也就是說,對應的那一類初始化為1,其余的都是0。

我們同樣使用上述的初始化數據陣的方法將dataCls的內容復制到trainLable中(注意,dataCls和data數組要嚴格對齊,就是說,x號樣本的特征放在data[x]里,所屬類別放在dataCls[x]里)

1 Mat trainLable(m_NUM,CLASSNUM,CV_32FC1,dataCls);

對於train的其他參數,除了params需要注意下,其他都是默認的。

 

////第三步   讀取訓練樣本,填充數據矩陣data和類別矩陣dataCls

首先,我們定義了全局變量,要進行初始化工作,那么寫完void main()后的第一件事就是調用init()函數,進行初始化工作,init()代碼如下

/**************************************************
*名稱:init()
*參數:void
*返回值:void
*作用:初始化各類參數
****************************************************/
void  init()
{
    memset(data,0,sizeof(data));           
    memset(dataCls,0,sizeof(dataCls));
     mClass = -1;                //初始類別為-1
       dNum = 0;                //統計樣本個數,其實沒軟用,對於本工程樣本個數固定為560
}

之后是讀入圖像和提取HOG特征,並記錄樣本所屬類別和填充數據矩陣,代碼如下

    init();
    ifstream in("trainpath.txt");string s,ss;
    while( in >> s){
        if(ss != s.substr(0,19)){
            mClass++;            //類別是0,1,2,3
            cout<<mClass<<endl;
        }
        ss = s.substr(0,19);
         cout<<s<<endl;
//------------------------讀入圖像,縮放圖像----------------------------
        Mat imge = imread(s),img;  
        if(imge.empty())
        {
            cout<<"image load error!"<<endl;
            system("pause");
            return 0;
        }
        resize(imge,img,Size(64,64)); 

//------------------------提取HOG特征,放入特征數組---------------------
        getHOG(img);

        packData(sta);        //填充特征數組和類別數組

    }

稍微解釋一下流程。

先定義一個文件流用於讀取訓練集路徑文件trainpath.txt,這個txt文件保存了所有訓練集的文件路徑,一行一個,像這樣

..\..\data\train\Citroen\X-雪鐵龍_1350198-01_201502010833146800.jpg 
..\..\data\train\Citroen\X-雪鐵龍_1350198-01_201502010841008800.jpg 
..\..\data\train\Citroen\X-雪鐵龍_1350198-01_201502010845367300.jpg 

  而且,不同類別的車標放在一起,舉個例子,共400行,前100行是雪鐵龍,再100行本田,再100行一汽,再100行福田(每個字符串的前17行是一樣的,19行肯定不一樣)

這樣有個好處,可以方便地統計這個圖片路徑對應的圖片屬於哪個種類的車。我們從代碼來看這個過程。

先定義兩個字符串ss和s,文件流一行行讀入並保存一行到s,取前19行,如果不等於ss,就讓mClass+1。

可以看到,初始mClass=-1.並且第一個字符串肯定不等於ss(因為此時ss為空),那么第一個圖片數據就屬於類別0,之后保存ss為s的前19位。

這樣,讀完整個圖片路徑,4種車標就可以很清楚地被區分了。

 

然后后面這個是讀圖保護,不管他,

 

然后讀入圖片,使用resize函數將其壓縮到64×64,看到沒,這就是我們提取HOG時候的檢測窗口大小。

 

調用getHog(img)獲取圖像的HOG特征,這個getHog是自定義函數,寫在main函數前面就行,代碼如下:

 1 /**************************************************
 2 *名稱:getHOG()
 3 *參數:Mat& img
 4 *返回值:void
 5 *作用:獲取圖像的HOG特征
 6 ****************************************************/
 7 void getHOG(Mat& img)
 8 {
 9     HOGDescriptor *hog = new HOGDescriptor(             
10             Size(64,64),      //win_size  檢測窗口大小,這里即完整的圖
11             Size(16,16),      //block_size  塊大小
12             Size(8,8),        //blackStride 步進
13             Size(8,8),        //cell_size  細胞塊大小
14             9                   //9個bin
15             );
16     hog -> compute(           //提取HOG特征向量
17         img, 
18         descriptors,          //存放特征向量
19         Size(64,64),            //滑動步進
20         Size(0,0)
21         );    
22     delete hog;
23     hog = NULL;
24 }

這里其實就調用了幾個openCV自帶的函數,對傳進來的圖片進行特征提取而已。有一點要注意,compute函數的第二個參數

descriptors是全局變量,記不起來的可以去前面的全局變量定義的地方找找,它就是用來保存提取到的HOG特征。

剛才我們也計算過了,一張圖1764個特征,也就是一次提取,descriptors就放一次1×1764的數據。

那么提取到一張圖的特征后,我們要把他放到data里,那么就是packData了,同樣,packData是一個全局函數

void packData()
{
        int p = 0;
        for (vector<float>::iterator it = descriptors.begin(); it != descriptors.end(); it++)
        {
            data[dNum][p++] = *it;
        }
        dataCls[dNum++][mClass] = 1.0;
}

前一半的for循環用來從descriptors中的向量填到data矩陣中,后一個語句就是更新它對應的類別矩陣。

 

循環執行完,我們的數據也填充完畢了,接下來就是建立網絡訓練了。

////第四步   建立神經網絡  訓練參數矩陣

先上這部分代碼

 1 //------------------------建BP神經網絡,開始訓練------------------------
 2     CvANN_MLP bp;
 3 
 4     CvANN_MLP_TrainParams params;
 5     params.term_crit=cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,7000,0.001);  //迭代次數7000,最小誤差0.001
 6     params.train_method=CvANN_MLP_TrainParams::BACKPROP;   //訓練方法反向傳播
 7     params.bp_moment_scale=0.1;
 8     params.bp_dw_scale=0.1;
 9    
10 
11     Mat layerSizes = (Mat_<int>(1,3) << F_NUM,48,4 );  //3層神經網絡
12     Mat trainDate(m_NUM,F_NUM,CV_32FC1,data);
13     Mat trainLable(m_NUM,CLASSNUM,CV_32FC1,dataCls);
14     bp.create(layerSizes, CvANN_MLP::SIGMOID_SYM);          //激活函數sigmoid
15     system("cls");
16     cout<<"訓練中...訓練時間大致需要6分鍾,請耐心等待";
17     bp.train(trainDate,trainLable, Mat(),Mat(), params);  //開始訓練
18     
19     system("cls");
20     cout << "訓練完成!!" <<endl;

 

CvANN_MLP是openCV自帶的人工神經網絡類,可以直接用,很方便吧。

我們先定義了一個CvANN_MLP類,然后看第二塊,第二塊就是神經網絡的一些參數的設定,具體注釋都有,就不講了

 

第三塊:第11行,定義神經網絡層數為3層,第一層:F_NUM個神經元,還記得F_NUM嗎?全局變量,就是特征數1764!

      總之神經網絡就是1764,48,4共3層,每次節點數就是這么幾個。

12-13行,看見沒,這就是把我們填充完的數據數組和類別數組賦值給Mat陣,之后就能調用create函數啦,創建一個網絡,使用SIGMOID函數什么的。

然后是訓練,train()之前也說過。SO EASY!

 

等待6分鍾左右,訓練就結束了,之后就是測試了

////第五步   測試神經網絡  

老規矩,上代碼再說

 1 //---------------------------------讀入圖像,開始測試--------------------------
 2     system("cls");
 3     cout<<"開始測試..."<<endl;
 4     system("cls");
 5     Mat imge,img;
 6 
 7     ifstream ins("testpath.txt");
 8 
 9     int cls = -1;
10     int num=0,c_num=0;
11     while( ins >> s){
12         memset(f,0,sizeof(f));
13         if(ss != s.substr(0,19)){
14             cls++;
15             cout<<cls<<endl;
16         }
17         cout<<s<<endl;
18         ss = s.substr(0,19);
19         imge = imread(s);
20         resize(imge,img,Size(64,64));         //使用線性插值
21         num++;
22         if (classifier(img,bp) == cls)
23         {
24             c_num++;
25         }
26     
27     }
28     system("cls");
29     cout<<"測試完成"<<endl;
30     cout<<"***************************************"<<endl;
31     cout<<"*樣本個數:"<<num<<endl;
32     cout<<"*正確個數:"<<c_num<<endl;
33     cout<<"*正確率:"<<setprecision(4)<<(float)c_num/num*100<<"%"<<endl;
34     cout<<"***************************************"<<endl;
35     system("pause");

測試就不說太多了了,無非讀一下測試路徑集,匹配一下,唯一要講的就是那個第22行的classiffier函數,這個也是個全局函數,上代碼吧2333

/**************************************************
*名稱:classifier()
*參數:Mat&  CvANN_MLP&
*返回值:int
*作用:求解測試結果最相鄰類別
****************************************************/
int classifier(Mat& image,CvANN_MLP& bp)
{
    

    getHOG(image);
    int p = 0;
for (vector<float>::iterator it = descriptors.begin(); it != descriptors.end(); it++)
 {
         f[0][p++] = *it;
}
Mat nearest(1, CLASSNUM, CV_32FC1, Scalar(0)); Mat charFeature(1, F_NUM, CV_32FC1,f); bp.predict(charFeature, nearest); Point maxLoc; minMaxLoc(nearest, NULL, NULL, NULL, &maxLoc); int result = maxLoc.x; return result; }

這個函數返回神經網絡預測測試圖片最可能的所屬類別。之后就是統計正確個數了。

 

//--------------------------結語-----------------------------------------

代碼全寫在一個cpp里了2333,為了方便講解,也方便自己學習嘛,不知道你有沒有看明白我講的呢ww。

可能以上講解也有疏漏,如果建完工程還是有問題的話,就直接下載下面的工程對照着這個講解再看一遍吧Orz(注意,運行前保證環境搭好,而且文件路徑不要更改)

(附完整工程下載地址:https://github.com/Holy-Shine/carLogoRec)  

有興趣的小伙伴star一下倉庫吧嘻嘻。

 

 

 

 

 

 

 


免責聲明!

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



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