【機器學習】HOG+SVM進行車輛檢測的流程及原始碼
在進行機器學習檢測車道線時,參考了這篇博文,基於LBP+SVM實現了車道線檢測的初步效果。覺得講解很到位,程式碼也容易理解和修改,故在此分享,供更多人學習。原地址:https://www.cnblogs.com/louyihang-loves-baiyan/p/4658478.html
HOG SVM 車輛檢測
近期需要對卡口車輛的車臉進行檢測,首先選用一個常規的檢測方法即是hog特徵與SVM,Hog特徵是由dalal在2005年提出的用於道路中行人檢測的方法,並且取的了不錯的識別效果。在人臉檢測方面目前主流的方法,先不考慮複雜的深度學習,大多采用Haar和Adaboost的手段來實現。我接下來將會用著兩種方法來實現對卡口的車輛檢測。
首先引出 Hog特徵,Hog特徵是梯度方向直方圖,是一種底層的視覺特徵,主要描述的是影象中的梯度分佈情況,而梯度分佈資訊主要是集中在影象中不同內容之間的邊界之處,可以較好的反應影象的基本輪廓面貌。在此處並不展開對描述子的詳細介紹,給出一個我當時看的部落格連結,對描述子原理分析的比較透徹。
接下來是整個特徵提取、訓練、檢測的流程:
1.首先是準備訓練樣本,分別是正樣本和負樣本以及測試樣本。正負樣本一般來說負樣本最好是正樣本的2-3倍比較好,覆蓋面不要是亂七八糟的影象,要貼合實際應用時的場景來選取,樣本對訓練過程很重要,很重要,很重要,不要以為隨隨便便弄一些照片就OK。
2.在程式中匯入測試樣本,分別提取相應的Hog特徵,這個地方我有兩點要說明:
2.1.樣本的尺度要正則化,也就是樣本的尺寸要一樣,這樣可以排除訓練樣本尺度對模型訓練的影響,在正則化的時候,儘量是不要改變其比例。
2.2.在hog特徵描述子初始化的時候,需要設定視窗大小,塊大小,塊滑動大小,以及細胞大小和直方圖相應的bin的數目,視窗大小要和輸入的訓練樣本的尺寸一樣。
3.提取正負樣本的hog特徵,我在這裡採用的是128×128的規模,是正方形的車臉,描述子規模是8100維。
4.SVM採用opencv中自帶的,其實opencv中採用的也是某一個版本的LIBSVM,只是重新封裝了介面的操作而已。
5.在SVM處,需要注意的是如果之後你要用SVM中自帶的detector,也就是用setSVMDetector的話,這個檢測器已經是寫好了的專門用了處理線性核訓練的模型,因為當時dalal用的就是Hog與線性的SVM特徵,而且opencv自帶的只支援線性的,如果你要用高斯特徵即RBF核,不可以採用setSVMDetector,你用了就會出錯,根本檢測不到真實的位置,這裡非常關鍵,你如果要做分類的話可以直接呼叫predict,但此處應該只是對車臉與非車臉做,而不是在一張圖中找出車臉,如果你要找出目標物,需要自己寫相應的detector,來應用你訓練好的模型!!!
6.在檢測時,檢測視窗的大小必須和訓練樣本的尺寸是一樣的,就是訓練時的Hog視窗大小和檢測時Hog視窗大小必須保持一致,剩下的就是檢測過程中看看沒有沒巢狀什麼的,程式碼如下:
#include<opencv/cv.h>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
#include<opencv2/gpu/gpu.hpp>
#include<opencv2/ml/ml.hpp>
#include<opencv2/objdetect/objdetect.hpp>
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
using namespace std;
using namespace cv;
#define TRAIN//開關控制是否訓練還是直接載入訓練好的模型
class MySVM: public CvSVM
{
public:
double * get_alpha_data()
{
return this->decision_func->alpha;
}
double get_rho_data()
{
return this->decision_func->rho;
}
};
void main(int argc, char ** argv)
{
MySVM SVM;
int descriptorDim;
string buffer;
string trainImg;
vector<string> posSamples;
vector<string> negSamples;
vector<string> testSamples;
int posSampleNum;
int negSampleNum;
int testSampleNum;
string basePath = "";//相對路徑之前加上基地址,如果訓練樣本中是相對地址,則都加上基地址
double rho;
#ifdef TRAIN
ifstream fInPos("D:\\DataSet\\CarFaceDataSet\\PositiveSample.txt");//讀取正樣本
ifstream fInNeg("D:\\DataSet\\CarFaceDataSet\\NegtiveSample.txt");//讀取負樣本
while (fInPos)//講正樣本讀入imgPathList中
{
if(getline(fInPos, buffer))
posSamples.push_back(basePath + buffer);
}
posSampleNum = posSamples.size();
fInPos.close();
while(fInNeg)//讀取負樣本
{
if (getline(fInNeg, buffer))
negSamples.push_back(basePath + buffer);
}
negSampleNum = negSamples.size();
fInNeg.close();
Mat sampleFeatureMat;//樣本特徵向量矩陣
Mat sampleLabelMat;//樣本標籤
HOGDescriptor * hog = new HOGDescriptor (cvSize(128, 128), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
vector<float> descriptor;
for(int i = 0 ; i < posSampleNum; i++)// 處理正樣本
{
Mat inputImg = imread(posSamples[i]);
cout<<"processing "<<i<<"/"<<posSampleNum<<" "<<posSamples[i]<<endl;
Size dsize = Size(128,128);
Mat trainImg = Mat(dsize, CV_32S);
resize(inputImg, trainImg, dsize);
hog->compute(trainImg, descriptor, Size(8, 8));
descriptorDim = descriptor.size();
if(i == 0)//首次特殊處理根據檢測到的維數確定特徵矩陣的尺寸
{
sampleFeatureMat = Mat::zeros(posSampleNum + negSampleNum, descriptorDim, CV_32FC1);
sampleLabelMat = Mat::zeros(posSampleNum + negSampleNum, 1, CV_32FC1);
}
for(int j = 0; j < descriptorDim; j++)//將特徵向量複製到矩陣中
{
sampleFeatureMat.at<float>(i, j) = descriptor[j];
}
sampleLabelMat.at<float>(i, 0) = 1;
}
cout<<"extract posSampleFeature done"<<endl;
for(int i = 0 ; i < negSampleNum; i++)//處理負樣本
{
Mat inputImg = imread(negSamples[i]);
cout<<"processing "<<i<<"/"<<negSampleNum<<" "<<negSamples[i]<<endl;
Size dsize = Size(128,128);
Mat trainImg = Mat(dsize, CV_32S);
resize(inputImg, trainImg, dsize);
hog->compute(trainImg, descriptor, Size(8,8));
descriptorDim = descriptor.size();
for(int j = 0; j < descriptorDim; j++)//將特徵向量複製到矩陣中
{
sampleFeatureMat.at<float>(posSampleNum + i, j) = descriptor[j];
}
sampleLabelMat.at<float>(posSampleNum + i, 0) = -1;
}
cout<<"extract negSampleFeature done"<<endl;
//此處先預留hard example 訓練後再新增
ofstream foutFeature("SampleFeatureMat.txt");//儲存特徵向量檔案
for(int i = 0; i < posSampleNum + negSampleNum; i++)
{
for(int j = 0; j < descriptorDim; j++)
{
foutFeature<<sampleFeatureMat.at<float>(i, j)<<" ";
}
foutFeature<<"\n";
}
foutFeature.close();
cout<<"output posSample and negSample Feature done"<<endl;
CvTermCriteria criteria = cvTermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON);
CvSVMParams params(CvSVM::C_SVC, CvSVM::LINEAR, 0, 1, 0, 0.01, 0, 0, 0, criteria); //這裡一定要注意,LINEAR代表的是線性核,RBF代表的是高斯核,如果要用opencv自帶的detector必須用線性核,如果自己寫,或者只是判斷是否為車臉的2分類問題則可以用RBF,在此應用環境中線性核的效能還是不錯的
cout<<"SVM Training Start..."<<endl;
SVM.train_auto(sampleFeatureMat, sampleLabelMat, Mat(), Mat(), params);
SVM.save("SVM_Model.xml");
cout<<"SVM Training Complete"<<endl;
#endif
#ifndef TRAIN
SVM.load("SVM_Model.xml");//載入模型檔案
#endif
descriptorDim = SVM.get_var_count();
int supportVectorNum = SVM.get_support_vector_count();
cout<<"support vector num: "<< supportVectorNum <<endl;
Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);
Mat supportVectorMat = Mat::zeros(supportVectorNum, descriptorDim, CV_32FC1);
Mat resultMat = Mat::zeros(1, descriptorDim, CV_32FC1);
for (int i = 0; i < supportVectorNum; i++)//複製支援向量矩陣
{
const float * pSupportVectorData = SVM.get_support_vector(i);
for(int j = 0 ;j < descriptorDim; j++)
{
supportVectorMat.at<float>(i,j) = pSupportVectorData[j];
}
}
double *pAlphaData = SVM.get_alpha_data();
for (int i = 0; i < supportVectorNum; i++)//複製函式中的alpha 記住決策公式Y= wx+b
{
alphaMat.at<float>(0, i) = pAlphaData[i];
}
resultMat = -1 * alphaMat * supportVectorMat; //alphaMat就是權重向量
//cout<<resultMat;
cout<<"描述子維數 "<<descriptorDim<<endl;
vector<float> myDetector;
for (int i = 0 ;i < descriptorDim; i++)
{
myDetector.push_back(resultMat.at<float>(0, i));
}
rho = SVM.get_rho_data();
myDetector.push_back(rho);
cout<<"檢測子維數 "<<myDetector.size()<<endl;
HOGDescriptor myHOG (Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
myHOG.setSVMDetector(myDetector);//設定檢測子
//儲存檢測子
int minusNum = 0;
int posNum = 0;
ofstream foutDetector("HogDetectorForCarFace.txt");
for (int i = 0 ;i < myDetector.size(); i++)
{
foutDetector<<myDetector[i]<<" ";
//cout<<myDetector[i]<<" ";
}
//cout<<endl<<"posNum "<<posNum<<endl;
//cout<<endl<<"minusNum "<<minusNum<<endl;
foutDetector.close();
//test part
ifstream fInTest("D:\\DataSet\\CarFaceDataSet\\testSample.txt");
while (fInTest)
{
if(getline(fInTest, buffer))
{
testSamples.push_back(basePath + buffer);
}
}
testSampleNum = testSamples.size();
fInTest.close();
for (int i = 0; i < testSamples.size(); i++)
{
Mat testImg = imread(testSamples[i]);
Size dsize = Size(320, 240);
Mat testImgNorm (dsize, CV_32S);
resize(testImg, testImgNorm, dsize);
vector<Rect> found, foundFiltered;
cout<<"MultiScale detect "<<endl;
myHOG.detectMultiScale(testImgNorm, found, 0, Size(8,8), Size(0,0), 1.05, 2);
cout<<"Detected Rect Num"<< found.size()<<endl;
for (int i = 0; i < found.size(); i++)//檢視是否有巢狀的矩形框
{
Rect r = found[i];
int j = 0;
for (; j < found.size(); j++)
{
if ( i != j && (r & found[j]) == r)
{
break;
}
}
if(j == found.size())
foundFiltered.push_back(r);
}
for( int i = 0; i < foundFiltered.size(); i++)//畫出矩形框
{
Rect r = foundFiltered[i];
rectangle(testImgNorm, r.tl(), r.br(), Scalar(0,255,0), 1);
}
imshow("test",testImgNorm);
waitKey();
}
system("pause");
}
結果如下:
總體效果還是不錯的,如果對hardexample,進行進一步訓練,以及樣本的資料進行clean,相信精度還可以進一步提高,並且現在維數也比價高,位了加快檢測還可以用PCA進一步降維,但必須自己重新寫detector了哦,一定要好好理解一下detector,其實hog + svm的程式碼很多,本質上都是差不多的。