【翻譯】HOG, Histogram of Oriented Gradients / 方向梯度直方圖 介紹
本文翻譯自 Histogram of Oriented Gradients"
在這篇文章中,我們將會學習 HOG (Histogram of Oriented Gradients,方向梯度直方圖)特徵描述子 的詳細內容。
我們將學習 HOG 演算法是如何實現的,以及在 OpenCv / MATLAB 或者其他工具裡面如何計算特徵子。
這篇文章是我正在寫的,關於 Image Recognition / 影象識別 和 Object Detection / 目標檢測 系列文章中的一部分。
完整的系列文章如下所示:
1. 使用傳統計算機視覺技術來進行 Image recognition / 影象識別 :Part 1 ( https://www.learnopencv.com/image-recognition-and-object-detection-part1/ )
2. HOG 方向梯度直方圖:Part 2
3. 影象識別的例子:Part 3 ( https://www.learnopencv.com/handwritten-digits-classification-an-opencv-c-python-tutorial
4. 訓練一個更好的眼睛檢測器:Part 4a ( https://www.learnopencv.com/training-better-haar-lbp-cascade-eye-detector-opencv )
5. 使用傳統計算機視覺技術來進行 Object detection / 目標檢測:Part 4b
6. 如何訓練和測試你自己的 OpenCv 目標檢測器:Part 5
7. 使用 Deep learning / 深度學習來進行影象識別:Part 6
介紹 Neural Networks / 神經網路 ( https://www.learnopencv.com/neural-networks-a-30000-feet-view-for-beginners/
理解 Feedforward Neural Networks / 負反饋神經網路 ( https://www.learnopencv.com/understanding-feedforward-neural-networks/ )
使用 Convolutional Neural Networks / 卷積神經網路 來進行影象識別 ( https://www.learnopencv.com/image-classification-using-convolutional-neural-networks-in-keras/ )
8. 使用 Deep learning / 深度學習來進行目標檢測:Part 7
很多事情看起來困難又神祕,但是你一旦花時間去了解,揭開神祕面紗,你就會發現神奇之處。
如果你是一個初學者,覺得計算機視覺又難又神祕,請記住一句話:
問:如何吃掉一個大象?
答:一口一口吃
什麼是 Feature Desciptor / 特徵描述子
Feature Desciptor / 特徵描述子 從影象中提取有用資訊,剔除無關資訊;
典型的,特徵描述子將一副 寬度 * 高度 * 3 ( 通道數 ) 的影象,轉換為長度為 n 的特徵向量或者特徵矩陣。比如 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 特徵描述子準備工作。
步驟 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 方向代表的是水平方向的變化。
如果影象畫素變換迅速的話,可以在梯度途中明顯看出,而當區域內變化緩慢時,不會出現梯度幅值。
梯度影象去除了很多不必要的資訊,保留了關鍵資訊。換句話說,你可以看著梯度圖,然後輕鬆的辨別出來照片裡的人。
每一個畫素點,都有一個幅值和方向。對於彩色的影象,三種通道的梯度都會被評估計算,取的是最大的梯度。
步驟 3:在 8*8 網格中計算梯度直方圖
這一步,影象會被分割成 8*8 大小的單獨小格子,然後對於每個 8*8 的小格子,分別計算梯度直方圖。
我們先來了解下為什麼要把影象分割為 8*8 的小格子。
有一個重要的原因是使用特徵描述子來描述一幅影象的一個子影象的話,網格分割會提供了一個緊湊的表示方式。
一個 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 的特徵描述子的視覺化比較困難。