1. 程式人生 > 實用技巧 >學習日記_HOG特徵描述運算元-行人檢測

學習日記_HOG特徵描述運算元-行人檢測

HOG特徵內容

HOG特徵簡介

  HOG特徵是一種影象區域性特徵,其基本思路是對影象區域性的梯度幅值和方向進行投票統計,形成基於梯度特性的直方圖,然後將區域性特徵拼接起來作為總特徵。區域性特徵在這裡指的是將影象劃分為多個子塊(Block), 每個Block內的特徵進行聯合以形成最終的特徵。

HOG+SVM的工作流程如下:

  首先對輸入的圖片進行預處理,然後計算畫素點的梯度特特性,包括梯度幅值和梯度方向。然後投票統計形成梯度直方圖,然後對blocks進行normalize,最後收集到HOG feature(其實是一行多維的vector)放到SVM裡進行監督學習,從而實現行人的檢測。下面我們將對上述HOG的主要步驟進行學習。

HOG特徵的原理

影象預處理

  預處理包括灰度化和Gamma變換。

  灰度處理是可選操作,因為灰度影象和彩色影象都可以用於計算梯度圖。對於彩色影象,先對三通道顏色值分別計算梯度,然後取梯度值最大的那個作為該畫素的梯度。

然後進行伽馬矯正,調節影象對比度,減少光照對影象的影響(包括光照不均和區域性陰影),使過曝或者欠曝的影象恢復正常,更接近人眼看到的影象。

  伽馬矯正公式:

\(f(I)=I^γ\)

\(I\)表示影象,\(γ\)表示冪指數。

如圖,當\(γ\)取不同的值時對應的輸入輸出曲線(\(γ=1\)時輸入輸出保持一致) :

1) 當\(γ<1\)時,輸入影象的低灰度值區域動態範圍變大,進而影象低灰度值區域對比度得以增強;在高灰度值區域,動態範圍變小,進而影象高灰度值區域對比度得以降低。 最終,影象整體的灰度變亮。

2) 當\(γ>1\)時,輸入影象的高灰度值區域動態範圍變小,進而影象低灰度值區域對比度得以降低;在高灰度值區域,動態範圍變大,進而影象高灰度值區域對比度得以增強。 最終,影象整體的灰度變暗。

程式碼:

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('*.png', 0)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img2 = np.power(img/float(np.max(img)),1/2.2)
plt.imshow(img2)
plt.axis('off')
plt.show()

計算影象梯度

  為了得到梯度直方圖,那麼首先需要計算影象水平方向和垂直方向梯度。一般使用特定的卷積核對影象濾波實現,可選用的卷積模板有:soble運算元、Prewitt運算元、Roberts模板等等。

  一般採用soble運算元,OpenCV也是如此,利用soble水平和垂直運算元與輸入影象卷積計算\(dx\)\(dy\)

\[Sobel_X=\begin{bmatrix}1\\0\\-1\end{bmatrix}*\begin{bmatrix}1&2&1\end{bmatrix}=\begin{bmatrix}1&2&1\\0&0&0\\-1&-2&-1\end{bmatrix}\\Sobel_X=\begin{bmatrix}1\\2\\1\end{bmatrix}*\begin{bmatrix}1&0&-1\end{bmatrix}=\begin{bmatrix}1&0&-1\\2&0&-2\\1&0&-1\end{bmatrix} \]

\(d_x=f(x,y)*Sobel_X(x,y)\\ d_y=f(x,y)*Sobel_Y(x,y)\)

進一步可以得到影象梯度的幅值:

\(M(x,y)=\sqrt{d_x^2(x,y)+d_y^2(x,y)}\)

為了簡化計算,幅值也可以作如下近似:

\(M(x,y)=|d_x(x,y)|+|d_y(x,y)|\)

角度為:
\(\theta_M=arctan(d_y/d_y)\)

這裡需要注意的是:梯度方向和影象邊緣方向是互相正交的。

程式碼:

mport cv2
import numpy as np
​
# Read image
img = cv2.imread('*.jpg')
img = np.float32(img) / 255.0  # 歸一化
​
# 計算x和y方向的梯度
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)
​
# 計算合梯度的幅值和方向(角度)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

計算梯度直方圖

  經過上一步計算,每一個畫素點都會有兩個值:梯度幅值/梯度方向。

在這一步中,影象被分成若干個8×8的cell,例如我們將影象resize至64x128的大小,那麼這幅影象就被劃分為8x16個8x8的cell單元,併為每個8×8的cell計算梯度直方圖。當然,cell的劃分也可以是其他值:16x16,8x16等,根據具體的場景確定。

在計算梯度直方圖,讓我們先了解一下為什麼我們將影象分成若干個cell?

這是因為如果對一整張梯度圖逐畫素計算,其中的有效特徵是非常稀疏的,不但運算量大,而且會受到一些噪聲干擾。於是我們就使用區域性特徵描述符來表示一個更緊湊的特徵,計算這種區域性cell上的梯度直方圖更具魯棒性。

以8x8的cell為例,一個8x8的cell包含了8x8x2 = 128個值,因為每個畫素包括梯度的大小和方向。

在HOG中,每個8x8的cell的梯度直方圖本質是一個由9個數值組成的向量, 對應於0、20、40、60…160的梯度方向(角度)。那麼原本cell中8x8x2 = 128個值就由長度為9的向量來表示,用這種梯度直方圖的表示方法,大大降低了計算量,同時又對光照等環境變化更加地魯棒。

  首先,看下圖:

  左圖是衣服64x128的影象,被劃分為8x16個8x8的cell;中間的影象表示一個cell中的梯度向量,箭頭朝向代表梯度方向,箭頭長度代表梯度大小。

右圖是 8×8 的cell中表示梯度的原始數值,注意角度的範圍介於0到180度之間,而不是0到360度, 這被稱為“無符號”梯度,因為兩個完全相反的方向被認為是相同的。

  接下來,我們來計算cell中畫素的梯度直方圖,將0-180度分成9等份,稱為9個bins,分別是0,20,40...160。然後對每個bin中梯度的貢獻進行統計:

統計方法是一種加權投票統計, 如上圖所示,某畫素的梯度幅值為13.6,方向為36,36度兩側的角度bin分別為20度和40度,那麼就按一定加權比例分別在20度和40度對應的bin加上梯度值,加權公式為:

20度對應的bin:((40-36)/20) * 13.6,分母的20表示20等份,而不是20度;
40度對應的bin:((36-20)/20) * 13.6,分母的20表示20等份,而不是20度;

還有一個細節需要注意,如果某個畫素的梯度角度大於160度,也就是在160度到180度之間,那麼把這個畫素對應的梯度值按比例分給0度和160度對應的bin。如左下圖綠色圓圈中的角度為165度,幅值為85,則按照同樣的加權方式將85分別加到0度和160度對應的bin中。

對整個cell進行投票統計,正是在HOG特徵描述子中建立直方圖的方式,最終得到由9個數值組成的向量—梯度方向圖:

Block歸一化

  HOG特徵將8×8的一個區域性區域作為一個cell,再以2×2個cell作為一組,稱為一個block,也就是說一個block表示16x16的區域。

我們可能會想,為什麼又需要分block呢?

這是因為,雖然我們已經為影象的8×8單元建立了HOG特徵,但是影象的梯度對整體光照很敏感。這意味著對於特定的影象,影象的某些部分與其他部分相比會非常明亮。

我們不能從影象中完全消除這個。但是我們可以通過使用16×16個塊來對梯度進行歸一化來減少這種光照變化。

由於每個cell有9個值,一個block(2×2個cell)則有36個值,HOG是通過滑動視窗的方式來得到block的,如下圖所示:

  前面已經說明,歸一化的目的是為了降低光照的影響,因為梯度對整體光照非常敏感,比如通過將所有畫素值除以2來使影象變暗,那麼梯度幅值將減小一半,因此直方圖中的值也將減小一半,我們就需要將直方圖“歸一化”。

歸一化的方法有很多:L1-norm、L2-norm、max/min等等,一般選擇L2-norm。

例如對於一個[128,64,32]的三維向量來說,模長是\(\sqrt{128^2+64^2+32^2}=146.64\),這叫做向量的L2範數。將這個向量的每個元素除以146.64就得到了歸一化向量 [0.87, 0.43, 0.22]。

採用同樣的方法,一個cell有一個梯度方向直方圖,包含9個數值,一個block有4個cell,那麼一個block就有4個梯度方向直方圖,將這4個直方圖拼接成長度為36的向量,然後對這個向量進行歸一化。

而每一個block將按照上圖滑動的方式進行重複計算,直到整個影象的block都計算完成。

獲得HOG描述子

每一個16 * 16大小的block將會得到一個長度為36的特徵向量,並進行歸一化。 那會得到多少個特徵向量呢?

例如,對於上圖被劃分8 * 16個cell ,每個block有2x2個cell的話,那麼cell的個數為:(16-1)x(8-1)=105。即有7個水平block和15個豎直block。

每個block有36個值,整合所有block的特徵值,最終獲得由36 * 105=3780個特徵值組成的特徵描述符,而這個特徵描述符是一個一維的向量,長度為3780。

獲得HOG特徵向量,就可以用來視覺化和分類了。對於多維的HOG特徵,SVM就可以排上用場了。

基於OpenCV的實現

程式碼:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

if __name__ == '__main__':
    src = cv.imread("*.jpg")
    cv.imshow("input", src)
    
    hog = cv.HOGDescriptor()
    hog.setSVMDetector(cv.HOGDescriptor_getDefaultPeopleDetector())
    # Detect people in the image
    (rects, weights) = hog.detectMultiScale(src,
                                            winStride=(2,4),
                                            padding=(8, 8),
                                            scale=1.2,
                                            useMeanshiftGrouping=False)
    for (x, y, w, h) in rects:
        cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv.imshow("hog-detector", src)
    cv.imwrite("hog-detector.jpg",src)
    cv.waitKey(0)
    cv.destroyAllWindows()

視覺化:

from skimage import feature, exposure
from matplotlib import pyplot as plt
import cv2
image = cv2.imread('sp_g.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

fd, hog_image = feature.hog(image, orientations=9, pixels_per_cell=(8, 8),
                    cells_per_block=(2, 4), visualise=True)

# Rescale histogram for better display
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))

cv2.namedWindow("img",cv2.WINDOW_NORMAL)
cv2.imshow('img', image)
cv2.namedWindow("hog",cv2.WINDOW_NORMAL)
cv2.imshow('hog', hog_image_rescaled)
cv2.waitKey(0)==ord('q')

總結

HOG演算法具有以下優點:

  • HOG描述的是邊緣結構特徵,可以描述物體的結構資訊
  • 對光照影響不敏感
  • 分塊的處理可以使特徵得到更為緊湊的表示

HOG演算法具有以下缺點:

特徵描述子獲取過程複雜,維數較高,導致實時性差
遮擋問題很難處理
對噪聲比較敏感

轉載自