HOG特征描述
首先我們來了解一下HOG特征描述子。
HOG特征描述子(HOG descriptors)是由Navneet Dalal和 Bill Triggs在2005年的一篇介紹行人檢測方法的論文提到的特征描述子(論文以及演講可參見參考資料1、2)。
其主要思想是計算局部圖像梯度的方向信息的統計值,來作為該圖像的局部特征值。
- 如上圖,歸一化圖像后,由於顏色數據對我們沒有幫助,所以將圖片轉為灰度圖。
- 然后將圖片分割成一定“塊”數,稱作細胞單元。
- 計算每個細胞單元的梯度大小方向。
- 得到每個單元的梯度方向組成一個圖片的特征向量。
- 將這個特征向量交給SVM來學習或辨認。
SVM的簡單介紹可以參考:OpenCV 2.4+ C++ SVM文字識別。
算法分析
- 梯度計算
OpenCV 2.4+ C++ 邊緣梯度計算中,介紹了Sobel算子和Scharr濾波器的梯度計算方法,但是論文作者使用這些復雜方法的效果並不好,最好的是使用簡單的一維梯度算子:[-1, 0, 1],進行卷積計算,分別算出x方向梯度Gx與y方向梯度Gy。然后利用下面兩個公式計算梯度大小和方向:
- 平滑處理
論文作者使用了高斯模糊進行平滑處理,結果效果變差了。其可能原因是:許多有用的圖像信息是來自變化劇烈的邊緣,而在計算梯度之前加入高斯濾波會把這些邊緣濾除掉。
- 細胞方向選擇
- 細胞的統計方向,由細胞中的每一個像素點投票取得。
- 票箱是0 - PI(無向)角度范圍平分為9個角度,即PI/9為每個票箱的角度范圍。
- 每個投票像素點的投票權重,由其梯度幅值計算出來,可采用幅值本身(實驗效果最佳),或者他的函數來表示這個權重。
- 塊
局部關照變化或者前景和背景的對比變化,可能使梯度強度產生劇烈變化,但我們關注的不是這些信息,所以需要對這些信息弱化處理。利用數個細胞單元組成空間上連通的塊。這樣,HOG描述就成了由各個塊所有細胞單元的直方圖組成的一個向量,這些區域是互相重疊的。這樣就減小了這些劇烈變化的影響。
每個細胞是8*8個像素點,以四個細胞組成一個塊。
再由塊組成檢測窗。
- 塊向量歸一化
最后是利用L2-norm或者L1-norm進行歸一化。
設v是未被歸一化的向量,| vk |是k階范數,ε為任意小常數,當k=2時,L2-norm為:
當k=1時,L1-norm為:
還有一種歸一化方式L2-Hys:
首先進行L2-norm,然后進行截短(即值被限制為v - 0.2v之間),然后再歸一化。
HOGDescriptor API
gpu::HOGDescriptor::HOGDescriptor
創建HOG描述符和檢測器。
- C++: gpu::HOGDescriptor:: HOGDescriptor (Size win_size=Size(64, 128), Size block_size=Size(16, 16), Size block_stride=Size(8, 8), Size cell_size=Size(8, 8), int nbins=9, double win_sigma=DEFAULT_WIN_SIGMA, double threshold_L2hys=0.2, bool gamma_correction=true, int nlevels=DEFAULT_NLEVELS )
參數
- win_size – 檢測窗大小。需要和塊的大小、步長匹配。
- block_size – 塊的大小。需要和細胞大小匹配。目前只支持(16,16)的大小。
- block_stride – 塊的步長,必須是細胞大小的整數倍。
- cell_size – 細胞大小。目前只支持(8, 8)的大小。
- nbins – 投票箱的個數。目前只支持每個細胞9個投票箱。
- win_sigma – 高斯平滑窗口參數。
- threshold_L2hys – L2-Hys歸一化收縮率。
- gamma_correction – 伽馬校正預處理標志,需要或不需要。
- nlevels – 檢測窗口的最大數目。
gpu::HOGDescriptor::getDescriptorSize
返回分類所需的系數的數目。
- C++: size_t gpu::HOGDescriptor:: getDescriptorSize ( ) const
gpu::HOGDescriptor::getBlockHistogramSize
返回塊直方圖的大小。
- C++: size_t gpu::HOGDescriptor:: getBlockHistogramSize ( ) const
gpu::HOGDescriptor::setSVMDetector
設置線性SVM分類器的系數。
- C++: void gpu::HOGDescriptor:: setSVMDetector (const vector<float>& detector )
gpu::HOGDescriptor::getDefaultPeopleDetector
返回人的分類訓練檢測(默認的窗口大小)的默認系數。
- C++: static vector<float> gpu::HOGDescriptor:: getDefaultPeopleDetector ( )
gpu::HOGDescriptor::getPeopleDetector48x96
返回人的分類訓練檢測(48*96窗口大小)的系數。
- C++: static vector<float> gpu::HOGDescriptor:: getPeopleDetector48x96 ( )
gpu::HOGDescriptor::getPeopleDetector64x128
返回人的分類訓練檢測(64*128窗口大小)的系數。
- C++: static vector<float> gpu::HOGDescriptor:: getPeopleDetector64x128 ( )
gpu::HOGDescriptor::detect
在沒有多尺度的窗口執行對象檢測
- C++: void gpu::HOGDescriptor:: detect (const GpuMat& img, vector<Point>& found_locations, double hit_threshold=0, Size win_stride=Size(), Size padding=Size() )
Parameters:
- img – 源圖像。只支持CV_8UC1和CV_8UC4數據類型。
- found_locations – 檢測出的物體的邊緣。
- hit_threshold – 特征向量和SVM划分超平面的閥值距離。通常它為0,並應由檢測器系數決定。但是,當系數被省略時,可以手動指定它。
- win_stride – 窗口步長,必須是塊步長的整數倍。
- padding – 模擬參數,使得CUP能兼容。目前必須是(0,0)。
gpu::HOGDescriptor::detectMultiScale
在多尺度窗口中執行對象檢測。
- C++: void gpu::HOGDescriptor:: detectMultiScale (const GpuMat& img, vector<Rect>& found_locations, double hit_threshold=0, Size win_stride=Size(), Size padding=Size(), double scale0=1.05, int group_threshold=2 )
參數
- img – 源圖像。只支持CV_8UC1和CV_8UC4數據類型。
- found_locations – 檢測出的物體的邊緣。
- hit_threshold – 特征向量和SVM划分超平面的閥值距離。通常它為0,並應由檢測器系數決定。但是,當系數被省略時,可以手動指定它。
- win_stride – 窗口步長,必須是塊步長的整數倍。
- padding – 模擬參數,使得CUP能兼容。目前必須是(0,0)。
- scale0 – 檢測窗口增長參數。
- group_threshold – 調節相似性系數的閾值。檢測到時,某些對象可以由許多矩形覆蓋。 0表示不進行分組。
gpu::HOGDescriptor::getDescriptors
返回整個圖片的塊描述符。
- C++: void gpu::HOGDescriptor:: getDescriptors (const GpuMat& img, Size win_stride, GpuMat& descriptors, int descr_format=DESCR_FORMAT_COL_BY_COL )
參數
- img – 源圖像。只支持CV_8UC1和CV_8UC4數據類型。
- win_stride – 窗口步長,必須是塊步長的整數倍。
- descriptors – 描述符的2D數組。
- descr_format –
描述符存儲格式:
- DESCR_FORMAT_ROW_BY_ROW - 行存儲。
- DESCR_FORMAT_COL_BY_COL - 列存儲。
這個函數主要用於分類學習。
檢測代碼
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/gpu/gpu.hpp> #include <stdio.h> using namespace cv; int main(int argc, char** argv){ Mat img; vector<Rect> found; img = imread(argv[1]); if(argc != 2 || !img.data){ printf("沒有圖片\n"); return -1; } HOGDescriptor defaultHog; defaultHog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); //進行檢測 defaultHog.detectMultiScale(img, found); //畫長方形,框出行人 for(int i = 0; i < found.size(); i++){ Rect r = found[i]; rectangle(img, r.tl(), r.br(), Scalar(0, 0, 255), 3); } namedWindow("檢測行人", CV_WINDOW_AUTOSIZE); imshow("檢測行人", img); waitKey(0); return 0; }
這段代碼雖然可以檢測出行人,但是可能會出現,一個人身上有幾個框框。例如下圖左邊的行人:
所以我們需要對found進行篩選,把那些被嵌套的長方形去除。
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/gpu/gpu.hpp> #include <stdio.h> using namespace cv; int main(int argc, char** argv){ Mat img; vector<Rect> found, foundRect; img = imread(argv[1]); if(argc != 2 || !img.data){ printf("沒有圖片\n"); return -1; } HOGDescriptor defaultHog; defaultHog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); //進行檢測 defaultHog.detectMultiScale(img, found); //遍歷found尋找沒有被嵌套的長方形 for(int i = 0; i < found.size(); i++){ Rect r = found[i]; int j = 0; for(; j < found.size(); j++){ //如果時嵌套的就推出循環 if( j != i && (r & found[j]) == r) break; } if(j == found.size()){ foundRect.push_back(r); } } //畫長方形,圈出行人 for(int i = 0; i < foundRect.size(); i++){ Rect r = foundRect[i]; rectangle(img, r.tl(), r.br(), Scalar(0, 0, 255), 3); } namedWindow("檢測行人", CV_WINDOW_AUTOSIZE); imshow("檢測行人", img); waitKey(0); return 0; }
PS:輸入圖片不能比檢測窗還小,這樣會拋出錯誤的。
參考資料
Histograms of Oriented Gradients for Human Detection . Navneet Dalal & Bill Triggs . 2005
Histograms of Oriented Gradients for Human Detection Talk . Navneet Dalal & Bill Triggs
OpenCV HOGDescriptor 參數圖解 . Excalibur . 2011-03-11