【翻譯】HOG, Histogram of Oriented Gradients / 方向梯度直方圖 介紹


 

本文翻譯自 Histogram of Oriented Gradients"

 

在這篇文章中,我們將會學習 HOG (Histogram of Oriented Gradients,方向梯度直方圖)特征描述子 的詳細內容。

我們將學習 HOG 算法是如何實現的,以及在 OpenCv / MATLAB 或者其他工具里面如何計算特征子。

這篇文章是我正在寫的,關於 Image Recognition / 圖像識別Object Detection / 目標檢測 系列文章中的一部分。

 

很多事情看起來困難又神秘,但是你一旦花時間去了解,揭開神秘面紗,你就會發現神奇之處。

如果你是一個初學者,覺得計算機視覺又難又神秘,請記住一句話:

問:如何吃掉一個大象?

答:一口一口吃

 

什么是 Feature Desciptor / 特征描述子

Feature Desciptor / 特征描述子 從圖像中提取有用信息,剔除無關信息;

典型的,特征描述子從將一張 寬度 * 高度 * 3 ( 通道數 ) 大小的圖像,提取出長度為 n 的 Feature Vector / 特征向量 或者 Feature Array / 特征矩陣

比如 HOG 特征描述子會從一張 64 * 128 * 3 的圖像中提取出長度為 3780 的特征向量;

請記住, HOG 的特征描述子也可以計算其他尺寸,但是這篇文章中,我使用上述尺寸,以便你能夠輕松的理解概念。

 

這些概念聽起來都挺不錯,但是哪些是“有用的信息”,有些又是“無用的信息"

定義“有用的信息”,我們需要知道有用的信息用來干什么的;

很明顯,通過特征向量用來瀏覽圖像是沒用的,但是在圖像識別或者目標檢測中,特征向量會變得很有用;

在一些圖像分類算法中比如 SVM,Support Vector Machine,支持向量機 中,用特征向量進行分類會達到很好的結果。

 

但是在分類任務中,哪些特征是有用的呢? 

我們借助下面的例子來討論,比如現在我們想通過一個目標檢測器,可以檢測襯衫和大衣的紐扣;

一個紐扣是一個圓形(圖片中也有可能看起來像是橢圓),一般來說有幾個孔,用於縫到衣服上面;

你可以在紐扣的圖像上使用一個 Edge detector / 邊緣檢測器,可以輕松通過檢測邊緣來辨別它是不是一個紐扣;

這個例子中,邊緣信息是“有用的”而顏色信息是 ”無用的“;

除此之外,特征也需要有足夠特殊的地方。比如一個好的特征,應該能夠讓你辨別出紐扣和其他圓形的物體,比如硬幣和汽車輪胎。

 

如何計算 Histogram of Oriented Gradients / 方向梯度直方圖?

在這一節,我們會繼續深入學習如何計算 HOG 特征描述子。

步驟1:預加工

之前提到用於行人檢測的 HOG 特征描述子,是基於 64×128 大小的圖像。當然,圖像可能是任何尺寸的;

對於這些之后用於分析的圖像,唯一需要進行的處理是調整縱橫比圖像大小;

在我們的例子中,需要調整縱橫比為1:2,比如圖像可以被調整為 100×200, 128×256, 或者 1000×2000,但是不能是 101×205;

原始圖像大小是 720×475,我們截切出來 100×200 大小圖像用來計算 HOG 特征描述子,然后重新調整大小到 64×128;

現在我們就做完了計算 HOG 特征描述子准備工作。

Dalal 和 Triggs 的論文也提到了 Gamma Correction / 伽馬校正 作為預處理步驟,但性能提升很小,因此我們選擇預處理中跳過這一步。

 

步驟 2 :計算梯度圖像

為了計算 HOG 特征描述子,我們第一步需要計算水平和垂直方向的梯度。我們通過下面的 Kernel / 核 來處理圖像,很容易計算出梯度的直方圖。

我們可以使用核大小為 1 的 OpenCv 的 Sobel 算子:

1    // C++ gradient calculation. 
2    // Read image
3    Mat img = imread("bolt.png"); 4    img.convertTo(img, CV_32F, 1/255.0); 5   6    // Calculate gradients gx, gy
7 Mat gx, gy; 8    Sobel(img, gx, CV_32F, 1, 0, 1); 9    Sobel(img, gy, CV_32F, 0, 1, 1);

 

1    # Python gradient calculation 
2   3    # Read image
4    im = cv2.imread('bolt.png') 5    im = np.float32(im) / 255.0
6   7    # Calculate gradient 
8    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1) 9    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)

 

接下來,我們通過下面的公式來計算梯度的幅值和方向:

在 OpenCv 中,我們可以使用 cartToPolar 函數來計算上述數值:

// C++ Calculate gradient magnitude and direction (in degrees)
Mat mag, angle; cartToPolar(gx, gy, mag, angle, 1); The same code in python looks like this.

 

# Python Calculate gradient magnitude and direction ( in degrees ) 
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

 

下圖展示了梯度計算結果:

左邊:x 方向梯度的絕對值
中間:y 方向梯度的絕對值
右邊:梯度的幅值

 

注意 x 方向的梯度代表垂直方向的變化趨勢,而 y 方向代表的是水平方向的變化;

如果圖像像素變換迅速的話,可以在梯度圖中明顯看出,而當區域內變化緩慢時,不會出現梯度幅值;

梯度圖像去除了很多不必要的信息,保留了關鍵信息。換句話說,你可以看着梯度圖,然后輕松的辨別出來照片里的人;

每一個像素點,都有一個 Magnitude / 幅值 Direction / 方向

對於彩色的圖像,三種通道的梯度都會被評估計算,取的是最大的梯度。

 

步驟 3:在 8*8 cells / 網格 中計算梯度直方圖

這一步,圖像會被分割成 8*8 大小的單獨 cells / 小格子,然后對於每個 8*8 的小格子,分別計算梯度直方圖;

 

我們先來了解下為什么要把圖像分割為 8*8 的小格子;

有一個重要的原因是使用特征描述子來描述一幅 image / 圖像 的一個 patch / 子圖像 的話,網格分割會提供了一個 compact / 緊湊 的表示方式;

一個 8*8 的子圖像包含 8*8*3 = 192 個像素值。每個像素梯度有兩個值( Magnitude / 幅值Direction / 方向 ),所以每個子圖像會有 8*8*2=128 個數值;

在這節結束之前,我們會看到這 128 個數值如何使用 9 位的數組來 存儲在 9 位的直方圖中。經過壓縮處理之后的數據具有更好的 抗噪性

 

但是為什么取得是 8*8 的子圖像而不是 32*32? 這是根據我們所要檢測的目標來決定的;

對於 HOG 行人檢測, 從 64*128 的行人圖像中提取出的 8*8 子圖像,已經足夠用來提取出有用的信息(比如臉部,頭的頂部等等)

 

直方圖有必要是 9 位的向量,與 0,20,40,60… 160 度對應;

讓我們來看看在一個 8*8 的子圖像中,梯度是什么樣的:

 

中間:用箭頭來代表顏色和梯度的變化;

右邊:用數字來代表子圖像中的梯度;

 

如果你是一個計算機視覺的初學者,中間的圖像會很有幫助很形象;

通過箭頭來表示圖像中梯度的變化,箭頭的方向表示着像素強度變化的方向,幅值表示變化的緩慢;

通過右邊的圖,我們可以看到 8*8 子圖像中提取出來的代表梯度的數值,這些角度從 0~180 度而不是 0~360 度,這些被稱之為 unsigned gradients / 無符號梯度,因為一個梯度和它取負之后得到的是同樣的數值;

換句話說,一個梯度箭頭旋轉180度之后被認為是一樣的;

但是為什么我們不使用 0-360 度呢?經驗告訴我們使用無符號的梯度,比使用有符號的梯度在行人檢測中性能更好。不過一些 HOG 的實現中也可以允許你使用有符號的梯度。

 

接下來就是為這些 8*8 的子圖像,建立一個梯度直方圖。直方圖有 9 位,來與 0, 20, 40…160 度相對應;

下面的圖像向我們展示了操作過程,我們關注從 8*8 子圖像中提取出來的幅值和方向;

 

* 根據梯度的方向來選擇使用填充到哪一位,然后根據梯度的幅值來填充數值

 

我們先來看看 藍圈的數值,角度為 80,幅值為2所以在直方圖第五位加 2;

再來看看 紅圈的數值,角度為 10,幅值為 4,角度 10 的話在 0 和 20 之間,所以將它的幅值 4 被一分為 2 ,分別在直方圖的 "0 位" 和 "20 位" 里面放 2 。

還需要注意的一點是,如果 角度比 160 大,在 160 和 180 之間。我們知道在這里 0 度和 180 度一樣,所以下面這個例子,角度 165 度被分到了 0 度和 160 度 兩個位里面。

8*8 子圖像提取出來的數值,經過處理,可以得到一個 9 位的直方圖,對於上面的子圖像,我們可以得到如下的直方圖:

 

在我們的表示中,y 軸默認為 0 度。你可以從直方圖中看到,在 0~180 度之間有很多分布,這也表明子圖像中的梯度方向要么朝上要么朝下。

 

步驟 4:16*16 塊歸一化

在之前的步驟中,我們根據圖像的梯度制作了直方圖。但是對於亮度不同的圖像,梯度很敏感。

如果你讓所有像素點的數值除 2 來讓圖像變暗,梯度幅值也會相應的減半,因此直方圖也會對應着減半。

理想情況下,我們希望我們的描述器是不隨着亮度變化而變化的,換句話說,我們想要歸一化直方圖,所以讓它不受亮度影響;

在我說明直方圖如何被歸一化之前,讓我們來看看,一個長度為 3 的向量是如何被歸一化的;

比如我們有個 RGB 顏色向量為 [ 128, 64, 32 ],計算出長度為:

這也被稱為這個向量的 L2 范數;

對向量的每個元素除以 146.64,得到歸一化之后的向量 [ 0.87, 0.43, 0.22 ]。

 

現在考慮另一個向量,它的數值是之前向量的兩倍,2 x [ 128, 64, 32 ] = [ 256, 128, 64 ];

通過同樣的計算方式,你可以得到同樣的歸一化向量 [ 0.87, 0.43, 0.22 ],這就可以解決之前提到的亮度的影響問題。


現在我們知道了如何去歸一化向量,也許你會認為,歸一化 9*1 的直方圖和上面介紹的 3*1 的向量歸一化一樣。這想法並沒有錯,但是更好的方式是用一個更大尺寸 16*16 的塊去歸一化;

也就是 36*1 的直方圖可以看成 4 個 9*1 的直方圖構成,然后窗口以 8 像素移動(見上圖),計算出歸一化的 36*1 大小的向量然后重復這個過程遍歷圖像。

 

可視化 HOG

通過在 8*8 子圖像里面進行 9*1 歸一化的直方圖,我們可以可視化子圖像的 HOG 的描述子。

在下圖中你會發現,直方圖的 Dominant direction / 主要方向 捕獲了這個人的外形,尤其在軀干和腿。

不幸的是,在 OpenCv 中進行 HOG 的特征描述子的可視化比較困難。

 

# 英文版權 @ 

# 翻譯中文版權 @ coneypo

# 轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM