(OpenCV)HOG:從理論到OpenCV實踐

原文地址:http://blog.csdn.net/zhazhiqiang/article/details/21047207

一、理論
    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算法來源:

    Histograms of Oriented Gradients for Human Detection, CVPR 2005。詳細的算法可以參考這個文章。這裏是英文中文的介紹。
 
(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壓縮公式:
            
             比如可以取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)倍的塊間隔。

(7)生成HOG特徵描述向量
    將所有“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函數
(1)作用:獲取塊的直方圖大小
(2)函數原型:
C++: size_t gpu::HOGDescriptor::getBlockHistogramSize() const  
 
4、setSVMDetector 函數
(1)作用:設置線性SVM分類器的係數
(2)函數原型:
C++: void gpu::HOGDescriptor::setSVMDetector(const vector<float>& detector
 
5、getDefaultPeopleDetector 函數
(1)作用:獲取行人分類器(默認檢測窗口大小)的係數(獲得3780維檢測算子)
(2)函數原型:
C++: static vector<float> gpu::HOGDescriptor::getDefaultPeopleDetector() 
 
6、getPeopleDetector48x96 函數
(1)作用:獲取行人分類器(48*96檢測窗口大小)的係數
(2)函數原型:
C++: static vector<float> gpu::HOGDescriptor::getPeopleDetector48x96()
 
7、getPeopleDetector64x128 函數
(1)作用:獲取行人分類器(64*128檢測窗口大小)的係數
(2)函數原型:
C++: static vector<float> gpu::HOGDescriptor::getPeopleDetector64x128() 
 
8、detect 函數
(1)作用:用沒有多尺度的窗口進行物體檢測
(2)函數原型:
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)
(1)作用:用多尺度的窗口進行物體檢測
(2)函數原型:
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)作用:返回整個圖片的塊描述符 (主要用於分類學習)。
(2)函數原型:
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提供默認模型,如果使用新的模型,需要重新訓練。
 
(1)製作樣本,將其歸一化到一個的尺度。
(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]);
(4)利用SVM進行訓練。(參考SVM:從理論到OpenCV實踐)
(5)得到XML文件。
    這裏識別有兩種用法:
A、一種採用svm.predict來做(參考利用HOG+SVM訓練自己的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)由於梯度的性質,該描述子對噪點相當敏感
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章