機器學習之用Hog+Svm人臉檢測、交通標誌和字元識別等(初學者)
首先宣告,這裡主要用svm進行一個簡單的二分類,最後得到結果,我們把正樣本設為1,負樣本設為0。
這裡只是一個簡單的介紹,後面會有相關詳細介紹的連結,個人認為比較好的,對我們比較有幫助的連結,有興趣的可以去看看。當然,本文對初學者有點幫助,也特別歡迎大神來拍!
訓練樣本時候問題:
我在訓練的時候,用了各種樣本,總結了一點自己的經驗。如果要是用Haar特徵訓練時,正負樣本大概比至少為1:10時候才比較好。而svm訓練,更講究正負樣本的選擇。其實,機器學習也好,深入學習也好,樣本比演算法重要的多!(當然,你要是想仔細研究演算法內部並且自己寫的除外)。我們這裡先只是用opencv裡的自帶的svm和hog庫。
先說正負樣本的選擇。
對於正負樣本來說,儘可能的接近比隨便挑的效果要好太多。
後來改變了選擇,正樣本不要只要需求的那一部分,比如,我們檢測交通標誌時,把交通標誌的旁邊一圈也划進來,當然不要太多。然後,我們把負樣本和正樣本的選擇儘可能相近,比如,我們訓練交通標誌時,選擇限速50為正樣本,那麼,我們就把限速40作為負樣本,這樣,最後得到的效果就會非常好。
對於樣本大小的選擇上,根據不同的樣本取不同大小吧
至於如何取得資料夾下所有的圖片目錄,和如何每隔一行加一個標誌符號。這裡,可以自己寫一個程式碼,當然,也可以直接利用.bat直接寫,具體方法是:
首先,拿正樣本為例。
在你放置正樣本的資料夾下,即與圖片的是同一級的目錄,新建一個.txt,名字可以自己取,例如為test.txt。然後,修改後綴名,為.bat。右鍵編輯(對於沒有編輯的win10來說…可以下載一個文字編輯器,我用的是EditPlus)開啟,然後,在裡面輸入
@echo off & setlocal EnableDelayedExpansion
for /f “delims=” %%i in (‘“dir /a/s/b/on .
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的吧,就正負樣本多采集一點才好。
好了,最後總結說,正負樣本多多益善,儘可能相近。對於機器學習來講,採集樣本往往重要的多,如果你按照程式碼測試過後,還是發現得不到想要的結果。那基本可以斷定,你所取得樣本是有問題的。所以,弄好樣本!才是機器學習的重中之重!!!