1. 程式人生 > >機器學習之用Hog+Svm人臉檢測、交通標誌和字元識別等(初學者)

機器學習之用Hog+Svm人臉檢測、交通標誌和字元識別等(初學者)

首先宣告,這裡主要用svm進行一個簡單的二分類,最後得到結果,我們把正樣本設為1,負樣本設為0。
這裡只是一個簡單的介紹,後面會有相關詳細介紹的連結,個人認為比較好的,對我們比較有幫助的連結,有興趣的可以去看看。當然,本文對初學者有點幫助,也特別歡迎大神來拍!
訓練樣本時候問題:
我在訓練的時候,用了各種樣本,總結了一點自己的經驗。如果要是用Haar特徵訓練時,正負樣本大概比至少為1:10時候才比較好。而svm訓練,更講究正負樣本的選擇。其實,機器學習也好,深入學習也好,樣本比演算法重要的多!(當然,你要是想仔細研究演算法內部並且自己寫的除外)。我們這裡先只是用opencv裡的自帶的svm和hog庫。
先說正負樣本的選擇。
對於正負樣本來說,儘可能的接近比隨便挑的效果要好太多。

比如說,我們負樣本的選擇是從幾張圖片隨機摳出幾萬張負樣本出來(過程很簡單,用opencv十幾二十幾行程式碼就可以,需要的可以評論,我發給你)。然後正樣本是我們需要的圖片。這時候,我們進行訓練完畢後,在做檢測,會發現結果相當不好,可以這麼說,除非你的檢測圖片正好是你的正樣本訓練圖片。否則基本檢測不出來!!
後來改變了選擇,正樣本不要只要需求的那一部分,比如,我們檢測交通標誌時,把交通標誌的旁邊一圈也划進來,當然不要太多。然後,我們把負樣本和正樣本的選擇儘可能相近,比如,我們訓練交通標誌時,選擇限速50為正樣本,那麼,我們就把限速40作為負樣本,這樣,最後得到的效果就會非常好。
對於樣本大小的選擇上,根據不同的樣本取不同大小吧
,很多人在這點都沒有提到,其實,對於初學者來說,往往不會選擇樣本大小,特別是還沒怎麼看svm和hog的,對於樣本大小往往是朝網上copy。我們選擇樣本大小時,一般,人臉,交通檢測這些,設定為64*64的就可以。而行人的,一般選擇長方形,即64*128這種的。正負樣本儘可能保持大小相同。
至於如何取得資料夾下所有的圖片目錄,和如何每隔一行加一個標誌符號。這裡,可以自己寫一個程式碼,當然,也可以直接利用.bat直接寫,具體方法是:
首先,拿正樣本為例。
在你放置正樣本的資料夾下,即與圖片的是同一級的目錄,新建一個.txt,名字可以自己取,例如為test.txt。然後,修改後綴名,為.bat。右鍵編輯(對於沒有編輯的win10來說…可以下載一個文字編輯器,我用的是EditPlus)開啟,然後,在裡面輸入
@echo off & setlocal EnableDelayedExpansion
for /f “delims=” %%i in (‘“dir /a/s/b/on .
“’) do (
set file=%%~fi
set file=!file:/=/!
echo !file! >> 路徑.txt
)
點選儲存,然後,直接雙擊這個檔案,就會得到一個名為路徑.txt,這裡面存放的就是資料夾下所有的路徑。
PS:至於有的人說的用cmd寫一個,dir /b/s/p/w *.jpg>train_list.txt,應該也可以,不過這個有個問題是如果路徑太多,不會全部寫進去,所以,還是用上一種方法比較好。
然後,開啟路徑,Ctrl+A,全部複製,然後放到word裡,在word裡,Ctrl+h,把jpg換為jpg^p1,其中^p是回車符號,1表示正樣本。
同理對負樣本這樣處理,然後,把所有目錄統一拷貝到一個.txt裡,放到需要的目錄下。
然後,我用的opencv是2.4.9版本。
說明:這是在別人的程式碼上(後面有連結),感覺不錯,改了一下,自己拿來用下。

#include "stdafx.h"

#include "cv.h"    
#include "highgui.h"    
#include "stdafx.h"    
#include <ml.h>    
#include <iostream>    
#include <fstream>    
#include <string>    
#include <vector>    
using namespace cv;    
using namespace std;

/**************************************************************
說明:把所有的訓練樣本(包括正樣本和負樣本)資訊,儲存到
F:\\MLHogSvm\\TrainData.txt中,格式為:
上面是:圖片路徑,其下面為標誌,1為正樣本,0為負樣本
每隔一行做一次,最後放到上面的txt中
最後的.xml放到F:\\MLHogSvm目錄下
**************************************************************/
int _tmain(int argc, _TCHAR* argv[])
{
    int ImgWidht = 64;  
    int ImgHeight = 64;//訓練影象大小,可以自己在這裡調節,不過最後要看看

    vector<string> img_path;//輸入檔名變數   
    vector<int> img_catg;    
    int nLine = 0;    
    string buf;    
    ifstream svm_data( "F:\\MLHogSvm\\TrainData.txt" );//首先,這裡搞一個檔案列表,把訓練樣本圖片的路徑都寫在這個txt檔案中,使用bat批處理檔案可以得到這個txt檔案     
    unsigned long n;    

    while( svm_data )//將訓練樣本檔案依次讀取進來    
    {    
        if( getline( svm_data, buf ) )    
        {    
            nLine ++;    
            if( nLine % 2 == 0 )//這裡的分類比較有意思,看得出來上面的SVM_DATA.txt文字中應該是一行是檔案路徑,接著下一行就是該圖片的類別,可以設定為0或者1,當然多個也無所謂   
            {    
                 img_catg.push_back( atoi( buf.c_str() ) );//atoi將字串轉換成整型,標誌(0,1),注意這裡至少要有兩個類別,否則會出錯    
            }    
            else    
            {    
                img_path.push_back( buf );//影象路徑    
            }    
        }    
    }    
    svm_data.close();//關閉檔案    

    CvMat *data_mat, *res_mat;    
    int nImgNum = nLine / 2; //讀入樣本數量 ,因為是每隔一行才是圖片路徑,所以要除以2   
    ////樣本矩陣,nImgNum:橫座標是樣本數量, WIDTH * HEIGHT:樣本特徵向量,即影象大小    
    data_mat = cvCreateMat( nImgNum, 3780, CV_32FC1 );  //這裡第二個引數,即矩陣的列是由下面的descriptors的大小決定的,可以由descriptors.size()得到,且對於不同大小的輸入訓練圖片,這個值是不同的  
    cvSetZero( data_mat );    
    //型別矩陣,儲存每個樣本的型別標誌    
    res_mat = cvCreateMat( nImgNum, 1, CV_32FC1 );    
    cvSetZero( res_mat );    

    IplImage* src;    
    IplImage* trainImg=cvCreateImage(cvSize(ImgWidht,ImgHeight),8,3);//需要分析的圖片,這裡預設設定圖片是64*64大小,所以上面定義了1764,如果要更改圖片大小,可以先用debug檢視一下descriptors是多少,然後設定好再執行    

    //開始搞HOG特徵  
    for( string::size_type i = 0; i != img_path.size(); i++ )    
    {    
            src=cvLoadImage(img_path[i].c_str(),1);    
            if( src == NULL )    
            {    
                cout<<" can not load the image: "<<img_path[i].c_str()<<endl;    
                continue;    
            }    

            cout<<" processing "<<img_path[i].c_str()<<endl;    

            cvResize(src,trainImg);   //讀取圖片       
            HOGDescriptor *hog=new HOGDescriptor(cvSize(ImgWidht,ImgHeight),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);  //具體意思見參考文章1,2       
            vector<float>descriptors;//結果陣列       
            hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //呼叫計算函式開始計算       
            cout<<"HOG dims: "<<descriptors.size()<<endl;    
            //CvMat* SVMtrainMat=cvCreateMat(descriptors.size(),1,CV_32FC1);    
            n=0;    
            for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)    
            {    
                cvmSet(data_mat,i,n,*iter);//把HOG儲存下來    
                n++;    
            }    
                //cout<<SVMtrainMat->rows<<endl;    
            cvmSet( res_mat, i, 0, img_catg[i] );    
            cout<<" end processing "<<img_path[i].c_str()<<" "<<img_catg[i]<<endl;    
    }    


    CvSVM svm;//新建一個SVM 
    CvSVMParams param;//這裡是引數  
    CvTermCriteria criteria;      
    criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON );      
    param = CvSVMParams( CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria );      

/*  SVM種類:CvSVM::C_SVC     
    Kernel的種類:CvSVM::RBF     
    degree:10.0(此次不使用)     
    gamma:8.0     
    coef0:1.0(此次不使用)     
    C:10.0     
    nu:0.5(此次不使用)     
    p:0.1(此次不使用)     
    然後對訓練資料正規化處理,並放在CvMat型的數組裡。     
                                                        */         
    //☆☆☆☆☆☆☆☆☆(5)SVM學習☆☆☆☆☆☆☆☆☆☆☆☆           
    svm.train( data_mat, res_mat, NULL, NULL, param );//訓練啦      
    //☆☆利用訓練資料和確定的學習引數,進行SVM學習☆☆☆☆       
    svm.save( "F:\\MLHogSvm\\SVM_DATA.xml" );  

    cvReleaseMat( &data_mat );    
    cvReleaseMat( &res_mat );
    return 0;
}

然後是進行檢測的程式碼,我們平時寫的時候,也建議把這兩個分開來寫,可以寫成兩個工程,或者用MFC寫成兩個不同的模組都可以的。

#include "stdafx.h"

#include "cv.h"    
#include "highgui.h"    
#include "stdafx.h"    
#include <ml.h>    
#include <iostream>    
#include <fstream>    
#include <string>    
#include <vector>    
using namespace cv;    
using namespace std;

/*********************************************************************
說明:
讀取svm.load("F:\\MLHogSvm\\SVM_DATA.xml"),讀入原來寫入的.xml
先F:\\MLHogSvm\\TrainTest.txt中,存放的是要進行測試的圖片的資訊。
預測結果(1為正樣本,0為負樣本)放入"F:\\MLHogSvm\\SVM_PREDICT.txt" 中
**********************************************************************/

int _tmain(int argc, _TCHAR* argv[])
{
    int ImgWidht = 64;  
    int ImgHeight = 64;//訓練影象大小,可以自己在這裡調節,不過最後要看看
    IplImage *test;    
    vector<string> img_tst_path;    
    ifstream img_tst( "F:\\MLHogSvm\\TrainTest.txt" );//輸入測試圖片路徑資訊  
    string buf;
    while( img_tst )    
    {    
        if( getline( img_tst, buf ) )    
        {    
            img_tst_path.push_back( buf );    
        }    
    }    
    img_tst.close();
    CvMat *test_hog = cvCreateMat( 1, 3780, CV_32FC1 );//注意這裡的1764,同上面一樣    
    char line[512];    
    ofstream predict_txt( "F:\\MLHogSvm\\SVM_PREDICT.txt" );//把預測結果儲存在這個文字中    
    for( string::size_type j = 0; j != img_tst_path.size(); j++ )//依次遍歷所有的待檢測圖片    
    {    
        test = cvLoadImage( img_tst_path[j].c_str(), 1);    
        if( test == NULL )    
        {    
             cout<<" can not load the image: "<<img_tst_path[j].c_str()<<endl;    
               continue;    
         }    

        IplImage* trainImg=cvCreateImage(cvSize(ImgWidht,ImgHeight),8,3);   
        cvZero(trainImg);    
        cvResize(test,trainImg);   //讀取圖片       
        HOGDescriptor *hog=new HOGDescriptor(cvSize(ImgWidht,ImgHeight),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);       
        vector<float>descriptors;//結果陣列       
        hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //呼叫計算函式開始計算       
        cout<<"HOG dims: "<<descriptors.size()<<endl;    
        CvMat* SVMtrainMat=cvCreateMat(1,descriptors.size(),CV_32FC1);    
        unsigned long n=0;    
        for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)    
            {    
                cvmSet(SVMtrainMat,0,n,*iter);    
                n++;    
            }    

        CvSVM svm;//新建一個SVM
        svm.load("F:\\MLHogSvm\\SVM_DATA.xml");
        int ret = svm.predict(SVMtrainMat);//獲取最終檢測結果,這個predict的用法見 OpenCV的文件   
        std::sprintf( line, "%s %d\r\n", img_tst_path[j].c_str(), ret );    
        predict_txt<<line;    
    }    
    predict_txt.close();
    return 0;
}

說明一下,這會在”F:\MLHogSvm\SVM_PREDICT.txt” 裡進行說明,如果檢測出來的測試圖片為需要的,就是標記為1,如果是無關的圖片,就會被標記為0(這裡隨意取,也可以自己設定為其他數,不過要記住的,在後面的檢測會用到)。

其實,我們從這段程式碼也可以看出,用hog和svm檢測,最後得出的,只是一個分類結果,就是,可以把一張圖片,分成是否是正樣本還是負樣本的型別。如果是需要的,就分到正樣本里(當然,這是完全理想狀態下)。

然後,進行檢測時,還是需要再次處理,比如,一幅圖片,如何才能得到我們想要的大體正樣本的框的地方。
參考了N篇論文之後,之後得到一個結果…碩士論文不靠譜。。不是,是大體都一樣,簡單來講,如果你真的需要一個實時線上檢測的,即使你用了多執行緒GPU加速之類的,還是達不到要求。說明演算法真的是有問題。
最後總結來說,如果是離線檢測,那麼,RGB轉HSI,再進行邊緣檢測,顏色填充,再邊緣檢測,再進行模式匹配的效果比較好。
如果你要求速率很高,推薦一種顏色空間,叫做SVF向量顏色空間,最後得到的效果不錯,當然,也可以用基於RGB的,不過效果一般。其實,最後要是想要快速結果,GPU加速應該是必不可少的,否則,用個HOG檢測就用了8、9秒,還玩個錘子!線上檢測,一般用SVF或者基於HSI的吧,就正負樣本多采集一點才好。

好了,最後總結說,正負樣本多多益善,儘可能相近。對於機器學習來講,採集樣本往往重要的多,如果你按照程式碼測試過後,還是發現得不到想要的結果。那基本可以斷定,你所取得樣本是有問題的。所以,弄好樣本!才是機器學習的重中之重!!!