HOG特徵原理主要參考http://blog.csdn.net/abcjennifer/article/details/7365651
HOG即histogram of oriented gradient, 是用於目標檢測的特徵描述子,該技術將圖像局部出現的方向梯度次數進行計數,該方法和邊緣方向直方圖、scale-invariant feature transform類似,不同的是hog的計算基於一致空間的密度矩陣來提高準確率。Navneet Dalal and Bill Triggs首先在05年的CVPR中提出HOG,用於靜態圖像or視頻的行人檢測。
HOG特徵原理:
HOG的核心思想是所檢測的局部物體外形能夠被光強梯度或邊緣方向的分佈所描述。通過將整幅圖像分割成小的連接區域(稱爲cells),每個cell生成一個方向梯度直方圖或者cell中pixel的邊緣方向,這些直方圖的組合可表示出(所檢測目標的目標)描述子。爲改善準確率,局部直方圖可以通過計算圖像中一個較大區域(稱爲block)的光強作爲measure被對比標準化,然後用這個值(measure)歸一化這個block中的所有cells.這個歸一化過程完成了更好的照射/陰影不變性。
與其他描述子相比,HOG得到的描述子保持了幾何和光學轉化不變性(除非物體方向改變)。因此HOG描述子尤其適合人的檢測。
通俗的講:
HOG特徵提取方法就是將一個image:
1. 灰度化(將圖像看做一個x,y,z(灰度)的三維圖像)
2. 劃分成小cells(2*2)
3. 計算每個cell中每個pixel的gradient(即orientation)
4. 統計每個cell的梯度直方圖(不同梯度的個數),即可形成每個cell的descriptor
再小談下Hog、SIFT與PCA-SIFT的應用與區別:
Hog沒有旋轉和尺度不變性,因此計算量小;而SIFT中每個feature需要用128維的向量來描述,因此計算量相對很大。
那麼行人檢測中怎麼應用HOG呢?
對於解決Scale-invariant 的問題:將圖片進行不同尺度的縮放,就相當於對模板進行不同尺度scale的縮放
對於解決Rotation-invariant 的問題:建立不同方向的模版(一般取15*7的)進行匹配
總的來說,就是在不同尺度上的圖像進行不同方向的模板(15*7)匹配,每個點形成一個8方向的梯度描述。
SIFT由於其龐大計算量不用與行人檢測,而PCA-SIFT的方法過濾掉很多維度的信息,只保留20個主分量,因此只適用於行爲變化不大的物體檢測。
method |
Time |
Scale |
Rotation |
Blur |
Illumination |
Affine |
Sift |
common |
best |
best |
common |
common |
good |
PCA-sift |
good |
good |
good |
best |
good |
best |
Surf |
best |
common |
common |
good |
best |
good |
關於sift的其他講解:
http://blog.csdn.net/abcjennifer/article/details/7639681
http://blog.csdn.net/abcjennifer/article/details/7372880
http://blog.csdn.net/abcjennifer/article/details/7365882
svm性別識別參考自:http://m.blog.csdn.net/article/details?id=50480518
支持向量機在解決二分類問題方面有着強大的威力(當然也可以解決多分類問題),性別識別是典型的二分類模式識別問題,因此很適合用SVM進行處理,同時OpenCv又對SVM進行了很好的封裝,調用非常方便,因此我們在這個性別識別程序中考慮加入SVM方法。
在這裏我們採用了HOG+SVM的模式來進行,即先提取圖像的HOG特徵,然後將這些HOG特徵輸入SVM中進行訓練。
一、SVM概述
SVM的數學原理十分複雜,我們不在這裏過多討論,有關OpenCv中SVM的用法,這裏爲大家提供兩篇博客以供參考:OpenCV的SVM用法以及OpenCV 2.4+ C++ SVM介紹。
二、HOG特徵概述
HOG特徵是圖像的梯度特徵,具體參見:目標檢測的圖像特徵提取之(一)HOG特徵
三、建立訓練集
這裏繼續沿用上一篇博文中提到的性別識別訓練集,400張男性人臉樣本400張女性人臉樣本,下載地址:性別識別數據集。
四、算法的訓練與測試
1、建立控制檯工程,配置OpenCv環境
這裏將工程命名爲:GenderSVM。
2、編寫批量讀取函數read_csv()
只要涉及到訓練,都需要批量讀取訓練樣本的操作,SVM也不例外,因此需要先編寫批量讀取函數read_csv()。考慮到之前的批量讀取函數必須一次性將所有訓練樣本讀入內存中,內存消耗較大,在這裏做一個小小的改進:
void read_csv(String& csvPath,Vector<String>& trainPath,Vector<int>& label,char separator = ';') { string line,path,classLabel; ifstream file(csvPath.c_str(),ifstream::in); while (getline(file,line)) { stringstream lines(line); getline(lines,path,separator); getline(lines,classLabel); if (!path.empty()&&!classLabel.empty()) { trainPath.push_back(path); label.push_back(atoi(classLabel.c_str())); } } }
可見這裏我們將輸入參數由vector<Mat>改爲vector<String>,然後返回裝有訓練樣本的所有路徑的容器,需要時在根據其中的路徑進行讀取,降低了內存佔用量。
3、讀入訓練樣本路徑
string trainCsvPath = "E:\\性別識別數據庫—CAS-PEAL\\at.txt"; vector<String> vecTrainPath; vector<int> vecTrainLabel; read_csv(trainCsvPath,vecTrainPath,vecTrainLabel);
順利批量讀入路徑:
4、訓練初始化
在提取HOG特徵之前,需要初始化訓練數據矩陣:
/**********初始化訓練數據矩陣**********/ int iNumTrain = 800; Mat trainDataHog; Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1);
需要強調的是SVM的訓練數據必須都是CV_32FC1格式,因此這裏顯式的將標籤矩陣trainLabel初始化爲CV_32FC1格式,trainDataHog稍後進行初始化。
5、提取圖像HOG特徵
接下來循環讀入所有的訓練樣本,提取HOG特徵,放在訓練數據矩陣中。考慮嵌套代碼的複雜性,這裏先給出整體代碼,稍後解釋:
/**********提取HOG特徵,放入訓練數據矩陣中**********/ Mat imageSrc; for (int i = 0; i < iNumTrain; i++) { imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0)); if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); } int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i]; }
接下來我們對這段代碼進行詳細解釋。
(1)循環讀入訓練樣本
從vecTrainPath容器中逐條取出訓練樣本路徑,然後讀取:
imageSrc = imread(vecTrainPath[i].c_str(),1);
(2)尺寸歸一化
我們這裏將圖像尺寸歸一化爲64*64,這是因爲當時在寫程序時參考了一篇關於HOG特徵的博客。這裏的尺寸大家可以隨意設定,當然也會影響最終的識別效率,64*64可能並不是一個最優的尺寸:
imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64));
(3)計算HOG特徵
OpenCv給出的HOG特徵計算接口非常簡潔,三句話即完成:
HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0));
提取的特徵以容器的數據 結構形式給出。至於計算時的參數設定,參見我之前提供的那兩篇博客即可。
(4)初始化數據矩陣trainDataHog
前面提到,SVM中用到的訓練數據矩陣必須是CV_32FLOAT形式的,因此需要對數據矩陣顯示的指定其尺寸和類型。然後由於trainDataHog行數爲訓練樣本個數,而列數爲圖片HOG特徵的維數,因此無法在進行HOG特徵提取之前確定其尺寸,因此這裏選擇在進行完第一張樣本的HOG特徵、得到對應維數之後,在進行初始化:
if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); }
(5)將得到的HOG特徵存入數據矩陣
得到的HOG特徵是浮點數容器的形式,我們需要將其轉換成矩陣的形式以便於訓練SVM,這就涉及到了vector和Mat兩個數據結構的遍歷。vector遍歷這裏推薦使用迭代器的方式,而Mat遍歷這裏則選擇了相對耗時但是最簡單的方式——直接使用at函數:
int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i];
訓練得到的HOG特徵如圖所示:
可見在當前的參數設定下,提取到的HOG特徵爲1764維,共800張訓練樣本,每一行代表一個圖片的HOG特徵向量。通過“ctrl+鼠標滾輪”放大觀察特徵向量的具體參數:
6、訓練SVM分類器
有關OpenCv中SVM分類器的使用可以參見以下博客:OpenCV 2.4+ C++ SVM介紹。
首先,初始化相關參數:
/**********初始化SVM分類器**********/ CvSVM 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**********/ svm.train(trainDataHog,trainLabel,Mat(),Mat(),param); svm.save("E:\\性別識別數據庫—CAS-PEAL\\SVM_SEX_Model.txt");
注意我們這裏選擇將分類器保存爲txt形式:
當然,我們可以打開這個txt文件,查看裏面的參數:
7、測試分類效果
測試過程和訓練過程基本相同,讀取圖片、尺寸歸一化、提取HOG特徵、預測:
/**********測試SVM分類性能**********/ Mat testImage = imread("E:\\性別識別數據庫—CAS-PEAL\\測試樣本\\女性測試樣本\\face_35.bmp"); resize(testImage,testImage,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(testImage,descriptor,Size(1,1),Size(0,0)); Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1); int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { testHog.at<float>(0,n) = *iter; n++; } int predictResult = svm.predict(testHog);
8、完整代碼
這裏給出HOG+SVM進行性別識別的完整代碼:
// GenderSVM.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include <opencv2\opencv.hpp> #include <iostream> #include <sstream> #include <fstream> using namespace std; using namespace cv; void read_csv(String& csvPath,vector<String>& trainPath,vector<int>& label,char separator = ';') { string line,path,classLabel; ifstream file(csvPath.c_str(),ifstream::in); while (getline(file,line)) { stringstream lines(line); getline(lines,path,separator); getline(lines,classLabel); if (!path.empty()&&!classLabel.empty()) { trainPath.push_back(path); label.push_back(atoi(classLabel.c_str())); } } } int _tmain(int argc, _TCHAR* argv[]) { /**********批量讀入訓練樣本路徑**********/ string trainCsvPath = "E:\\性別識別數據庫—CAS-PEAL\\at.txt"; vector<String> vecTrainPath; vector<int> vecTrainLabel; read_csv(trainCsvPath,vecTrainPath,vecTrainLabel); /**********初始化訓練數據矩陣**********/ int iNumTrain = 800; Mat trainDataHog; Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1); /**********提取HOG特徵,放入訓練數據矩陣中**********/ Mat imageSrc; for (int i = 0; i < iNumTrain; i++) { imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0)); if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); } int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i]; } /**********初始化SVM分類器**********/ CvSVM 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**********/ svm.train(trainDataHog,trainLabel,Mat(),Mat(),param); svm.save("E:\\性別識別數據庫—CAS-PEAL\\SVM_SEX_Model.txt"); /**********測試SVM分類性能**********/ Mat testImage = imread("E:\\性別識別數據庫—CAS-PEAL\\測試樣本\\女性測試樣本\\face_35.bmp"); resize(testImage,testImage,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(testImage,descriptor,Size(1,1),Size(0,0)); Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1); int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { testHog.at<float>(0,n) = *iter; n++; } int predictResult = svm.predict(testHog); return 0; }
五、總結
以上就是通過HOG特徵+SVM進行性別識別的完整代碼,在編寫代碼的過程中遇到了一些有趣的問題,這裏稍作總結。
1、變量命名格式
當代碼量很大的時候,變量的命名格式就顯得十分重要,相信大家早已不用那種a、b、m、n這種簡單的無意義的命名方法了。在C++中推薦大家使用匈牙利命名法,即“類型縮寫+變量名縮寫”的命名格式。例如vecTrainPath這個變量名,前綴“vec”表明這個變量是一個vector格式的變量,而“TrainPath”則表明這個容器中存放的是訓練樣本的路徑。這種命名方式在大型工程中非常重要,還有一點需要注意的是當變量名中出現多個縮略短語時,推薦第一個短語小寫,其他短語的首字母大寫。
2、爲何選擇HOG特徵
通過實驗發現,直接將圖像向量化後輸入SVM(不經過特徵提取)的方式的正確率將不理想。雖然本質上像素本身最能代表圖像的語義信息,但由於SVM並不具備特徵提取能力,因此效果不佳。確切的說,特徵提取是模式分類的必要過程,即便是深度學習也不例外,因爲深度學習(DeepLearning)本質上也是一種特徵提取的手段,只不過提取得到的特徵更深層,更抽象,表現力更強。爲此我之前曾專門寫過一篇博客進行闡述:淺談模式識別中的特徵提取
當然這裏大家可以嘗試提取其他特徵之後再進行分類,甚至可以考慮通過提起深度特徵來進行分類,這裏只是以HOG特徵爲例而已。
4、有關vector的一些使用(爲什麼不用int型數組)
在這段代碼中我們大量用到了vector結構,這是C++11的新特性。仔細觀察,其實vector結構的最明顯的一個優勢就是能夠動態分配大小,實時添加/刪除元素,這點是數組所不能實現的。雖然可以通過new操作符來實現數組的動態分配,但我們仍推薦大家在需要使用可動態變化的數組的場合,使用vector。
5、Vectot和vector
在編寫代碼是仔細留心編譯器給出的拼寫提示,會發現這樣一現象:
那麼vector和Vector有什麼區別呢?一句話,Vector是OpenCv中的vector,類似的還有String和string等。Vector和String這類結構是隸屬於OpenCv的: