opencv學習筆記(七)SVM+HOG
一、簡介
方向梯度直方圖(Histogram of Oriented Gradient,HOG)特征是一種在計算機視覺和圖像處理中用來進行物體檢測的特征描述子。它通過計算和統計圖像局部區域的梯度直方圖來構成特征。Hog特征結合SVM分類器已經被廣泛用於圖像識別中,尤其在行人檢測中獲得了極大的成功。需要提醒的是,HOG+SVM進行行人檢測的方法是法國研究院Dalal在2005的CVPR上提出的。
最近在做車標識別相關的研究,用到了SVM+HOG的方法進行識別,下面的例子,使用的數據樣本是6類車標:本田、大眾、豐田、現代、馬自達和雪鐵龍。
二、SVM+HOG進行車標識別
批處理:
首先在訓練樣本和測試樣本的文件夾下,使用dos批處理命令:
dir /b > trainsamsFilenameDecribeTxt.txt
dir /b > testsamsFilenameDecribeTxt.txt
得到訓練樣本和測試樣本的文件名列表,如下所示:
注意將最后一行的“trainsamsFilenameDecribeTxt.txt”刪掉。
然后需要將訓練樣本和測試樣本文件夾所在的路徑加到上述文件名列表的前面,形成樣本的完整路徑。我使用如下代碼幫助我完成,
首先是訓練樣本:
1 bool ClogoRecognition::createTrainSamDescribeTxt() 2 { 3 string s; 4 ifstream in(DEFAULT_TRAINSAMPLES_FILESNAME_TXT_DECRIBE_PATH); 5 if (!in) 6 return FALSE; 7 ofstream out; 8 out.open(DEFAULT_TRAINSAMPLES_TXT_DECRIBE_PATH, ios::trunc); //ios::trunc表示在打開文件前將文件清空,由於是寫入,文件不存在則創建 9 while (getline(in, s))//逐行讀取數據並存於s中,直至數據全部讀取 10 { 11 out <<DEFAULT_TRAINSAMPLES_PATH<< s.c_str() << '\n';//路徑后面加上訓練樣本的filename 12 int n = s.c_str()[0] - '0';//每個訓練樣本文件都以數字開頭命令,數字即代表該文件的類別 13 out << n << '\n';//每個樣本的后面寫入其類別,用於SVM訓練時指定type 14 } 15 in.close(); 16 out.close(); 17 return TRUE; 18 }
運行結果:
對於訓練樣本,路徑之后緊接着該文件對應的標簽類別。便於之后的訓練步驟。
其次是測試樣本:
1 /*創建測試樣本描述文件*/ 2 bool ClogoRecognition::createTestSamDescribeTxt() 3 { 4 string s; 5 ifstream in(DEFAULT_TESTSAMPLES_FILESNAME_TXT_DESCRIBE_PATH); 6 if (!in) 7 return FALSE; 8 ofstream out; 9 out.open(DEFAULT_TESTSAMPLES_TXT_DECRIBE_PATH, ios::trunc); //ios::trunc表示在打開文件前將文件清空,由於是寫入,文件不存在則創建 10 while (getline(in, s))//逐行讀取數據並存於s中,直至數據全部讀取 11 { 12 out << DEFAULT_TESTSAMPLES_PATH << s.c_str() << '\n';//路徑后面加上測試樣本的filename 13 } 14 in.close(); 15 out.close(); 16 return TRUE; 17 }
運行結果:
樣本圖片和數量,第一行從左往右依次為:本田、大眾、豐田;第二行從左往右一次為:現代、馬自達、雪鐵龍;
SVM訓練:
1 bool ClogoRecognition::svmTrain() 2 { 3 vector<string> img_path;//圖像路徑容器 4 vector<int> img_catg;//圖像類別容器 5 int nLine = 0; 6 string buf; 7 ifstream svm_data(DEFAULT_TRAINSAMPLES_TXT_DECRIBE_PATH);//訓練樣本圖片的路徑都寫在這個txt文件中,使用bat批處理文件可以得到這個txt文件 8 if (!svm_data) 9 return FALSE; 10 unsigned long n; 11 while (svm_data)//將訓練樣本文件依次讀取進來 12 { 13 if (getline(svm_data, buf)) 14 { 15 nLine++; 16 if (nLine % 2 == 0)//注:奇數行是圖片全路徑,偶數行是標簽 17 { 18 img_catg.push_back(atoi(buf.c_str()));//atoi將字符串轉換成整型,標志(0,1,2,...,9),注意這里至少要有兩個類別,否則會出錯 19 } 20 else 21 { 22 img_path.push_back(buf);//圖像路徑 23 } 24 } 25 } 26 svm_data.close();//關閉文件 27 CvMat *data_mat, *res_mat; 28 int nImgNum = nLine / 2; //nImgNum是樣本數量,只有文本行數的一半,另一半是標簽 29 data_mat = cvCreateMat(nImgNum, 432, CV_32FC1); //第二個參數,即矩陣的列是由下面的descriptors的大小決定的,可以由descriptors.size()得到,且對於不同大小的輸入訓練圖片,這個值是不同的 30 cvSetZero(data_mat); 31 //類型矩陣,存儲每個樣本的類型標志 32 res_mat = cvCreateMat(nImgNum, 1, CV_32FC1); 33 cvSetZero(res_mat); 34 IplImage* src; 35 IplImage* trainImg = cvCreateImage(cvSize(40, 32), 8, 3);//需要分析的圖片,這里車標的尺寸歸一化至40*32,所以上面定義了432,如果要更改圖片大小,可以先用debug查看一下descriptors是多少,然后設定好再運行 36 37 //處理HOG特征 38 for (string::size_type i = 0; i != img_path.size(); i++) 39 { 40 src = cvLoadImage(img_path[i].c_str(), 1); 41 if (src == NULL) 42 { 43 cout << " can not load the image: " << img_path[i].c_str() << endl; 44 continue; 45 } 46 47 cout << " 處理: " << img_path[i].c_str() << endl; 48 49 cvResize(src, trainImg); 50 HOGDescriptor *hog = new HOGDescriptor(cvSize(40, 32), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);//圖片尺寸:40*32;block尺寸:16*16;cell尺寸:8*8;檢測窗口的滑動步長:8*8;一個單元格內統計9個方向的梯度直方圖 51 vector<float>descriptors;//存放結果 52 hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0)); //Hog特征計算 53 cout << "HOG dims: " << descriptors.size() << endl; 54 n = 0; 55 for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) 56 { 57 cvmSet(data_mat, i, n, *iter);//存儲HOG特征 58 n++; 59 } 60 cvmSet(res_mat, i, 0, img_catg[i]); 61 cout << " 處理完畢: " << img_path[i].c_str() << " " << img_catg[i] << endl; 62 } 63 64 65 // CvSVM svm = CvSVM();//新建一個SVM 66 CvSVM svm; 67 CvSVMParams param;//這里是SVM訓練相關參數 68 CvTermCriteria criteria; 69 criteria = cvTermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON); 70 param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria); 71 // param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.3, 1.0, 5, 0.5, 1.0, NULL, criteria); 72 svm.train(data_mat, res_mat, NULL, NULL, param);//訓練數據 73 //保存訓練好的分類器 74 svm.save(DEFAULT_SVMMODEL_PATH); 75 cvReleaseMat(&data_mat); 76 cvReleaseMat(&res_mat); 77 cvReleaseImage(&trainImg); 78 return TRUE; 79 }
等待幾分鍾即可得到訓練好的xml模型;
SVM測試:
1 bool ClogoRecognition::svmTest() 2 { 3 string buf; 4 CvSVM svm; 5 svm.load(DEFAULT_SVMMODEL_PATH);//加載訓練好的xml文件 6 //檢測樣本 7 IplImage *test; 8 char result[512]; 9 vector<string> img_tst_path; 10 ifstream img_tst(DEFAULT_TESTSAMPLES_TXT_DECRIBE_PATH); //加載需要預測的圖片集合,這個文本里存放的是圖片全路徑,不要標簽 11 if (!img_tst) 12 return FALSE; 13 while (img_tst) 14 { 15 if (getline(img_tst, buf)) 16 { 17 img_tst_path.push_back(buf); 18 } 19 } 20 img_tst.close(); 21 22 ofstream predict_txt(DEFAULT_TESTSAMPLES_RECOGNITION_RESULT_TXT_DECRIBE_PATH);//把預測結果存儲在這個文本中 23 for (string::size_type j = 0; j != img_tst_path.size(); j++)//依次遍歷所有的待檢測圖片 24 { 25 test = cvLoadImage(img_tst_path[j].c_str(), 1); 26 if (test == NULL) 27 { 28 cout << " can not load the image: " << img_tst_path[j].c_str() << endl; 29 continue;//結束本次循環 30 } 31 IplImage* trainTempImg = cvCreateImage(cvSize(40, 32), 8, 3); 32 cvZero(trainTempImg); 33 cvResize(test, trainTempImg); 34 HOGDescriptor *hog = new HOGDescriptor(cvSize(40, 32), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9); 35 vector<float>descriptors;//結果數組 36 hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0)); 37 cout << "HOG dims: " << descriptors.size() << endl; 38 CvMat* SVMtrainMat = cvCreateMat(1, descriptors.size(), CV_32FC1); 39 int n = 0; 40 for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++) 41 { 42 cvmSet(SVMtrainMat, 0, n, *iter); 43 n++; 44 } 45 46 int ret = svm.predict(SVMtrainMat);//檢測結果 47 sprintf(result, "%s %d\r\n", img_tst_path[j].c_str(), ret); 48 predict_txt << result; //輸出檢測結果到文本 49 } 50 predict_txt.close(); 51 cvReleaseImage(&test); 52 return TRUE; 53 }
運行SVM測試代碼后,運行結果寫入指定的txt中,每一行的最后一個數字代表該行路徑下的圖片的識別結果。如下圖所示:
圖片文件名中第一個數字代表其類別,當和該行中最后一個數字一致時,說明識別正確,否則識別錯誤。由圖中可以看到,識別結果還是挺不錯的。