一、理論
locally normalised histogram of gradient orientation in dense overlapping grids,即局部歸一化的梯度方向直方圖,是一種對圖像局部重疊區域的密集型描述符, 它通過計算局部區域的梯度方向直方圖來構成特征。
2、本質:
Histogram of Oriented Gradient descriptors provide a dense overlapping description of image regions
,即
統計圖像局部區域的梯度方向信息來作為該局部圖像區域的表征。
3、OpenCV中的HOG算法來源:
(1)大小:
A、檢測窗口:WinSize=128*64像素,在圖像中滑動的步長是8像素(水平和垂直都是)
B、塊:BlockSize=16*16像素,在檢測窗口中滑動的步長是8像素(水平和垂直都是)
C、單元格:CellSize=8*8像素
D、梯度方向:一個Cell的梯度方向分為9個方向,在一個單元格內統計9個方向的梯度直方圖
(2)HOG描述子
OpenCV中一個Hog描述子是針對一個檢測窗口而言的,一個檢測窗口有((128-16)/8+1)*((64-16)/8+1)=105個Block,一個Block有4個Cell,一個Cell的Hog描述子向量的長度是9,所以一個檢測窗口的Hog描述子的向量長度是105*4*9=3780維。
HOG特征提取是統計梯度直方圖特征。具體來說就是將梯度方向(0->360°)划分為9個區間,將圖像化為16x16的若干個block,每個block再化為4個cell(8x8)。對每一個cell,算出每一像素點的梯度方向和模,按梯度方向增加對應bin的值,最終綜合N個cell的梯度直方圖形成一個高維描述子向量。實際實現的時候會有各種插值。
(1)灰度化
由於顏色信息作用不大,通常轉化為灰度圖。
(2)標准化gamma空間
為了減少光照因素的影響,首先需要將整個圖像進行規范化(歸一化),這種處理能夠有效地降低圖像局部的陰影和光照變化。
比如可以取Gamma=1/2;
(3)計算圖像每個像素的梯度(包括大小和方向)
計算圖像橫坐標和縱坐標方向的梯度,並據此計算每個像素位置的梯度方向值;求導操作不僅能夠捕獲輪廓,人影和一些紋理信息,還能進一步弱化光照的影響。
梯度算子:水平邊緣算子: [-1, 0, 1] ;垂直邊緣算子: [-1, 0, 1]T
圖像中像素點(x,y)的梯度為:
作者也嘗試了其他一些更復雜的模板,如3×3 Sobel 模板,或對角線模板(diagonal masks),但是在這個行人檢測的實驗中,這些復雜模板的表現都較差,所以作者的結論是:模板越簡單,效果反而越好。
作者也嘗試了在使用微分模板前加入 一個高斯平滑濾波,但是這個高斯平滑濾波的加入使得檢測效果更差,原因是:許多有用的圖像信息是來自變化劇烈的邊緣,而在計算梯度之前加入高斯濾波會把這些邊緣濾除掉。
(4)將圖像分割為小的Cell單元格
由於Cell單元格是HOG特征最小的結構單位,而且其塊Block和檢測窗口Win的滑動步長就是一個Cell的寬度或高度,所以,先把整個圖像分割為一個個的Cell單元格(8*8像素)。
(5)為每個單元格構建梯度方向直方圖【重點】
這步的目的是:統計局部圖像梯度信息並進行量化(或稱為編碼),得到局部圖像區域的特征描述向量。同時能夠保持對圖像中人體對象的姿勢和外觀的弱敏感性。
我們將圖像分成若干個“單元格cell”,例如每個cell為8*8個像素(可以是矩形的(rectangular),也可以是星形的(radial))。假設我們采用9個bin的直方圖來統計這8*8個像素的梯度信息。也就是將cell的梯度方向360度分成9個方向塊,如圖所示:例如:如果這個像素的梯度方向是20-40度,直方圖第2個bin的計數就加一,這樣,對cell內每個像素用梯度方向在直方圖中進行加權投影(映射到固定的角度范圍),就可以得到這個cell的梯度方向直方圖了,就是該cell對應的9維特征向量(因為有9個bin)。
像素梯度方向用到了,那么梯度大小呢?梯度大小就是作為投影的權值的。例如說:這個像素的梯度方向是20-40度,然后它的梯度大小是2(假設啊),那么直方圖第2個bin的計數就不是加一了,而是加二(假設啊)。
單元格Cell中的每一個像素點都為某個基於方向的直方圖通道(orientation-based histogram channel)
投票。投票是采取
加權投票(weighted voting)的方式,即每一票都是帶權值的,這個權值是根據該像素點的梯度幅度計算出來。可以采用幅值本身或者它的函數來表示這個權值,實際測試表明: 使用幅值來表示權值能獲得最佳的效果,當然,也可以選擇幅值的函數來表示,比如幅值的平方根(square root)、幅值的平方(square of the gradient magnitude)、幅值的截斷形式(clipped version of the magnitude)等。根據Dalal等人論文的測試結果,采用梯度幅值量級本身得到的檢測效果最佳,使用量級的平方根會輕微降低檢測結果,而使用二值的邊緣權值表示會嚴重降低效果。
其中,加權采用三線性插值(鏈接為詳細說明的博文)方法,即將當前像素的梯度方向大小、像素在cell中的x坐標與y坐標這三個值來作為插值權重,而被用來插入的值為像素的梯度幅值。采用三線性插值的好處在於:避免了梯度方向直方圖在cell邊界和梯度方向量化的bin邊界處的突然變化。

(6)把單元格組合成大的塊(block),塊內
歸一化
梯度直方圖【重點】
由於局部光照的變化以及前景-背景對比度的變化,使得梯度強度的變化范圍非常大。這就需要對梯度強度做歸一化。歸一化能夠進一步地對光照、陰影和邊緣進行壓縮。
方法:
(6-1)將多個臨近的cell組合成一個block塊,然后求其梯度方向直方圖向量;
(6-2)采用L2-Norm with Hysteresis threshold方式進行歸一化,即將直方圖向量中bin值的最大值限制為0.2以下,然后再重新歸一化一次;
注意:block之間的是“共享”的,也即是說,一個cell會被多個block“共享”。另外,每個“cell”在被歸一化時都是“block”independent的,也就是說每個cell在其所屬的block中都會被歸一化一次,得到一個vector。這就意味着:每一個單元格的特征會以不同的結果多次出現在最后的特征向量中。
(6-3)四種歸一化方法:
作者采用了四中不同的方法對區間進行歸一化,並對結果進行了比較。引入v表示一個還沒有被歸一 化的向量,它包含了給定區間(block)的所有直方圖信息。| | vk | |表示v的k階范數,這里的k去1、2。用e表示一個很小的常數。這時,歸一化因子可以表示如下:
L2-norm:
L1-norm:
L1-sqrt:
L2-Hys:它可以通過先進行L2-norm,對結果進行截短(clipping)(即值被限制為v - 0.2v之間),然后再重新歸一化得到。
作者發現:采用L2- Hys,L2-norm 和 L1-sqrt方式所取得的效果是一樣的,L1-norm稍微表現出一點點不可靠性。但是對於沒有被歸一化的數據來說,這四種方法都表現出來顯着的改進。
(6-4)區間(塊)有兩個主要的幾何形狀——矩形區間(R-HOG)和環形區間(C-HOG)。
A、R-HOG區間(blocks):大體上是一些方形的格子,它可以有三個參數來表征:每個區間中細胞單元的數目、每個細胞單元中像素點的數目、每個細胞的直方圖通道數目。例如:行人檢測的最佳參數設置是:3×3細胞/區間、6×6像素/細胞、9個直方圖通道。則一塊的特征數為:3*3*9;作者還發現,對於R-HOG,在對直方圖做處理之前,給每個區間(block)加一個高斯空域窗口(Gaussian spatial window)是非常必要的,因為這樣可以降低邊緣的周圍像素點(pixels around the edge)的權重。R-HOG是各區間被組合起來用於對空域信息進行編碼(are used in conjunction to encode spatial form information)。
B、C-HOG區間(blocks):有兩種不同的形式,它們的區別在於:一個的中心細胞是完整的,一個的中心細胞是被分割的。如右圖所示:
作者發現C-HOG的這兩種形式都能取得相同的效果。C-HOG區間(blocks)可以用四個參數來表征:角度盒子的個數(number of angular bins)、半徑盒子個數(number of radial bins)、中心盒子的半徑(radius of the center bin)、半徑的伸展因子(expansion factor for the radius)。通過實驗,對於行人檢測,最佳的參數設置為:4個角度盒子、2個半徑盒子、中心盒子半徑為4個像素、伸展因子為2。前面提到過,對於R-HOG,中間加一個高斯空域窗口是非常有必要的,但對於C-HOG,這顯得沒有必要。C-HOG看起來很像基於形狀上下文(Shape Contexts)的方法,但不同之處是:C-HOG的區間中包含的細胞單元有多個方向通道(orientation channels),而基於形狀上下文的方法僅僅只用到了一個單一的邊緣存在數(edge presence count)。
(6-5)HOG描述符(不同於OpenCV定義):我們將歸一化之后的塊描述符(向量)就稱之為HOG描述符。
(6-6)塊划分帶來的問題:塊與塊之間是相互獨立的嗎?
答:通常的將某個變量范圍固定划分為幾個區域,由於邊界變量與相鄰區域也有相關性,所以變量只對一個區域進行投影而對相鄰區域完全無關時會對其他區域產生混疊效應。
分塊之間的相關性問題的解決:
方案一:塊重疊,重復統計計算
在重疊方式中,塊與塊之間的邊緣點被重復根據權重投影到各自相鄰塊(block)中,從而一定模糊了塊與塊之間的邊界,處於塊邊緣部分的像素點也能夠給相鄰塊中的方向梯度直方圖提供一定貢獻,從而達到關聯塊與塊之間的關系的作用。Datal對於塊和塊之間相互重疊程度對人體目標檢測識別率影響也做了實驗分析。
方案二:線性插值權重分配
有些文獻采用的不是塊與塊重疊的方法,而是采用線性插值的方法來削弱混疊效應。這種方法的主要思想是每個Block都對臨近的Block都有影響,這種影響,我們可以以一種加權方式附加上去。
基於線性插值的基本思想,對於上圖四個方向(橫縱兩個45度斜角方向)個進行一次線性插值就可以達到權重分配目的。下面介紹一維線性插值。假設x1和x2是x塊相鄰兩塊的中心,且x1<x<x2。對w(即權重,一般可直接采用該block的直方圖值即h(x))進行線性插值的方法如下式:
其中b在橫縱方向取塊間隔,而在斜45度方向則可采用sqrt(2)倍的塊間隔。
將所有“block”的HOG描述符組合在一起,形成最終的feature vector,該feature vector就描述了detect window的圖像內容。
二、OpenCV中HOG的參數與函數說明(HOG鏈接為OpenCV英文,
這里
為網友翻譯)
注:HOG在OpenCV中的幾個模塊中都有,略有差別,可做參考,OpenCV的官方文檔中只有對GPU模塊的HOG,這里前面幾個函數說明是GPU中的,后面兩個objedetect模塊中。其實我們在使用時用的是objedetect模塊中的HOG。
0、HOGDescriptor結構體
HOGDescriptor結構體的注釋點擊
這里(里面包括hog.cpp的詳細注釋)。
1、構造函數
(1)作用:創造一個HOG描述子和檢測器
(2)函數原型:
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
)
(3)參數注釋
<1>
win_size:檢測窗口大小。
<2>
block_size:塊大小,目前只支持Size(16, 16)。
<3>
block_stride:塊的滑動步長,大小只支持是單元格cell_size大小的倍數。
<4>
cell_size:單元格的大小,目前只支持Size(8, 8)。
<5>
nbins:直方圖bin的數量(投票箱的個數),目前每個單元格Cell只支持9個。
<6>
win_sigma:高斯濾波窗口的參數。
<7>
threshold_L2hys:塊內直方圖歸一化類型L2-Hys的歸一化收縮率
<8>
gamma_correction:是否gamma校正
<9>
nlevels:檢測窗口的最大數量
2、getDescriptorSize函數
(1)作用:獲取一個檢測窗口的HOG特征向量的維數
(2)函數原型:
C++:
size_t gpu::HOGDescriptor::
getDescriptorSize() const
3、getBlockHistogramSize函數
C++:
size_t gpu::HOGDescriptor::
getBlockHistogramSize() const
C++:
void gpu::HOGDescriptor::
setSVMDetector(const vector<float>&
detector)
5、getDefaultPeopleDetector 函數
C++:
static
vector<float> gpu::HOGDescriptor::
getDefaultPeopleDetector()
6、getPeopleDetector48x96 函數
(1)作用:獲取行人分類器(
48*96檢測窗口大小)的系數
C++:
static
vector<float> gpu::HOGDescriptor::
getPeopleDetector48x96()
7、getPeopleDetector64x128 函數
(1)作用:獲取行人分類器(
64*128檢測窗口大小)的系數
C++:
static
vector<float> gpu::HOGDescriptor::
getPeopleDetector64x128()
C++:
void gpu::HOGDescriptor::
detect(const GpuMat&
img,
vector<Point>&
found_locations,
double
hit_threshold=0,
Size
win_stride=Size(),
Size
padding=Size()
)
(3)參數注釋
<1>
img:源圖像。只支持CV_8UC1和CV_8UC4數據類型。
<2>
found_locations:檢測出的物體的邊緣。
<3>
hit_threshold:特征向量和SVM划分超平面的閥值距離。通常它為0,並應由檢測器系數決定。但是,當系數被省略時,可以手動指定它。
<4>
win_stride:窗口步長,必須是塊步長的整數倍。
<5>
padding:模擬參數,使得CUP能兼容。目前必須是(0,0)。
9、detectMultiScale 函數(需有setSVMDetector)
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
)
(3)參數注釋
<1>
img:源圖像。只支持CV_8UC1和CV_8UC4數據類型。
<2>
found_locations:檢測出的物體的邊緣。
<3>
hit_threshold:特征向量和SVM划分超平面的閥值距離。通常它為0,並應由檢測器系數決定。但是,當系數被省略時,可以手動指定它。
<4>
win_stride:窗口步長,必須是塊步長的整數倍。
<5>
padding:模擬參數,使得CUP能兼容。目前必須是(0,0)。
<6>
scale0:檢測窗口增長參數。
<7>
group_threshold:調節相似性系數的閾值。檢測到時,某些對象可以由許多矩形覆蓋。 0表示不進行分組。
<1> 得到層數levels
某圖片(530,402)為例,lg(402/128)/lg1.05=23.4 則得到層數為24
<2>循環levels次,每次執行內容如下
HOGThreadData& tdata = threadData[getThreadNum()];
Mat smallerImg(sz, img.type(), tdata.smallerImgBuf.data);
<3>循環中調用以下核心函數
detect(smallerImg, tdata.locations, hitThreshold, winStride, padding);
其參數分別為,該比例下圖像、返回結果列表、門檻值、步長、margin
該函數內容如下:
(a)得到補齊圖像尺寸paddedImgSize
(b)創建類的對象HOGCache cache(this, img, padding, padding, nwindows == 0, cacheStride); 在創建過程中,首先初始化HOGCache::init,包括:計算梯度descriptor->computeGradient、得到塊的個數105、每塊參數個數36。
(c)獲得窗口個數nwindows,以第一層為例,其窗口數為(530+32*2-64)/8+ (402+32*2-128)/8+1 =67*43=2881,其中(32,32)為winStride參數, 也可用(24,16)
(d)在每個窗口執行循環,內容如下:
在105個塊中執行循環,每個塊內容為:通過getblock函數計算HOG特征並 歸一化,36個數分別與算子中對應數進行相應運算;判斷105個塊的總和 s >= hitThreshold 則認為檢測到目標
10、getDescriptors 函數
(1)作用:返回整個圖片的塊描述符
(主要用於分類學習)。
C++:
void gpu::HOGDescriptor::
getDescriptors(const GpuMat& img,
Size
win_stride,
GpuMat&
descriptors,
int
descr_format=DESCR_FORMAT_COL_BY_COL
)
(3)參數注釋
<1>
img:源圖像。只支持CV_8UC1和CV_8UC4數據類型。
<2>
win_stride:窗口步長,必須是塊步長的整數倍。
<3>
descriptors:描述符的2D數組。
<4>
descr_format:描述符存儲格式:
DESCR_FORMAT_ROW_BY_ROW - 行存儲。
DESCR_FORMAT_COL_BY_COL - 列存儲。
11、computeGradient 函數
(1)作用:計算img經擴張后的圖像中每個像素的梯度和角度
(2)函數原型:
void HOGDescriptor::
computeGradient(const Mat&
img,
Mat&
grad,
Mat&
qangle,
Size
paddingTL,
Size
paddingBR
) const
(3)參數注釋
<1>
img:源圖像。只支持CV_8UC1和CV_8UC4數據類型。
<2>
grad
:
輸出梯度(兩通道),
記錄每個像素所屬bin對應的權重的矩陣,為幅值乘以權值。
這個權值是關鍵,也很復雜:包括高斯權重,三次插值的權重,在本函數中先值考慮幅值和相鄰bin間的插值權重。
<3>
qangle:
輸入弧度(兩通道),
記錄每個像素角度所屬的bin序號的矩陣,均為2通道,為了線性插值。
<4>
paddingTL
:
Top和Left擴充像素數。
<5>
paddingBR
:
Bottom和Right擴充像素數。
12、compute 函數
(1)作用:
計算HOG特征向量
(2)函數原型:
void HOGDescriptor::
compute(const Mat&
img,
vector<float>&
descriptors,
Size
winStride,
Size
padding,
const vector<Point>&
locations
) const
(3)參數注釋
<1>
img:源圖像。只支持CV_8UC1和CV_8UC4數據類型。
<2>
descriptors:
返回的HOG特征向量,
descriptors.size是HOG特征的維數。
<3>
winStride:窗口移動步長。
<4>
padding
:擴充像素數。
<5>
locations
:對於正樣本可以直接取(0,0),負樣本為隨機產生合理坐標范圍內的點坐標。
三、
HOG算法OpenCV實現流程
四、OpenCV的簡單例子
1、HOG行人檢測
(1)代碼:
#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;
}
上述過程並沒有像人臉檢測Demo里有Load訓練好的模型的步驟,這個
getDefaultPeopleDetector是默認模型,這個模型數據在OpenCV源碼中是一堆常量數字,這些數字是通過原作者提供的行人樣本
INRIAPerson.tar訓練得到的。
這里只是用到了HOG的
識別模塊,OpenCV把HOG包的內容比較多,既有HOG的特征提取,也有結合SVM的識別,這里的識別只有檢測部分,OpenCV提供默認模型,如果使用新的模型,需要重新訓練。
(2)將樣本圖像的名稱寫到一個TXT文件,方便程序調用。
(3)依次提取每張圖像的HOG特征向量。
hog.compute(img, descriptors,Size(8,8), Size(0,0));
可以生成hog descriptors,把它保存到文件中
for(int j=0;j<3780;j++)
fprintf(f,"%f,",descriptors[j]);
(5)得到XML文件。
這里識別有兩種用法:
B、另一種采用
hog.setSVMDetector+訓練的模型和hog.detectMultiScale(參考
利用Hog特征和SVM分類器進行行人檢測 )
五、總結
1、HOG與SIFT的區別
HOG和SIFT都是描述子,以及由於在具體操作上有很多相似的步驟,所以致使很多人誤認為HOG是SIFT的一種,其實兩者在使用目的和具體處理細節上是有很大的區別的。HOG與SIFT的主要區別如下:
(1)SIFT是基於關鍵點特征向量的描述。
(2)HOG是將圖像均勻的分成相鄰的小塊,然后在所有的小塊內統計梯度直方圖。
(3)SIFT需要對圖像尺度空間下對像素求極值點,而HOG中不需要。
(4)SIFT一般有兩大步驟,第一個步驟對圖像提取特征點,而HOG不會對圖像提取特征點。
2、HOG的優缺點
優點:
(1)HOG表示的是邊緣(梯度)的結構特征,因此可以描述局部的形狀信息;
(2)位置和方向空間的量化一定程度上可以抑制平移和旋轉帶來的影響;
(3)采取在局部區域歸一化直方圖,可以部分抵消光照變化帶來的影響;
(4)由於一定程度忽略了光照顏色對圖像造成的影響,使得圖像所需要的表征數據的維度降低了;
(5)而且由於這種分塊分單元的處理方法,也使得圖像局部像素點之間的關系可以很好得到表征。
缺點:
(1)描述子生成過程冗長,導致速度慢,實時性差;
(2)很難處理遮擋問題;
(3)由於梯度的性質,該描述子對噪點相當敏感。