CV之 HOG特徵描述算子-行人檢測

4.1 簡介

本次任務將學習一種在深度學習之前非常流行的圖像特徵提取技術——方向梯度直方圖(Histogram of Oriented Gradients),簡稱HOG特徵。HOG特徵是在2005年CVPR的會議發表,在圖像手工特徵提取方面具有里程碑式的意義,當時在行人檢測領域獲得了極大成功。

學習HOG特徵的思想也有助於我們很好地瞭解傳統圖像特徵描述和圖像識別方法,本次任務我們將學習到HOG背後的設計原理,和opencv的實現。

4.2 內容介紹

1. HOG特徵簡介

在這裏插入圖片描述
HOG特徵是一種圖像局部特徵,其基本思路是對圖像局部的梯度幅值和方向進行投票統計,形成基於梯度特性的直方圖,然後將局部特徵拼接起來作爲總特徵。局部特徵在這裏指的是將圖像劃分爲多個子塊(Block), 每個Block內的特徵進行聯合以形成最終的特徵。

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

2.HOG特徵的原理

圖像預處理

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

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

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

  • 伽馬矯正公式:

f(I)=Iγf(I)=I^\gammaII表示圖像,γ\gamma表示冪指數。

如圖,當γ\gamma取不同的值時對應的輸入輸出曲線( γ=1\gamma=1時輸入輸出保持一致) :
1) 當γ<1\gamma<1時,輸入圖像的低灰度值區域動態範圍變大,進而圖像低灰度值區域對比度得以增強;在高灰度值區域,動態範圍變小,進而圖像高灰度值區域對比度得以降低。 最終,圖像整體的灰度變亮。

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

在這裏插入圖片描述

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('E:/python-project/deep-learning/picture/test1.jpg', 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水平和垂直算子與輸入圖像卷積計算dxdxdydy

SobelX=[101][121]=[121000121]SobelY=[121][101]=[101202101]dx=f(x,y)Sobelx(x,y)dy=f(x,y)Sobely(x,y)\begin{array}{l} \text {Sobel}_{X}=\left[\begin{array}{c} 1 \\ 0 \\ -1 \end{array}\right] *\left[\begin{array}{ccc} 1 & 2 & 1 \end{array}\right]=\left[\begin{array}{ccc} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{array}\right] \\ \operatorname{Sobel}_{Y}=\left[\begin{array}{c} 1 \\ 2 \\ 1 \end{array}\right] *\left[\begin{array}{ccc} 1 & 0 & -1 \end{array}\right]=\left[\begin{array}{ccc} 1 & 0 & -1 \\ 2 & 0 & -2 \\ 1 & 0 & -1 \end{array}\right] \\ d_{x}=f(x, y)^{*} \operatorname{Sobel}_{x}(x, y) \\ d_{y}=f(x, y)^{*} \operatorname{Sobel}_{y}(x, y) \end{array}

進一步可以得到圖像梯度的幅值:
M(x,y)=dx2(x,y)+dy2(x,y)M(x, y)=\sqrt{d_{x}^{2}(x, y)+d_{y}^{2}(x, y)}
爲了簡化計算,幅值也可以作如下近似:
M(x,y)=dx(x,y)+dy(x,y)M(x, y)=\left|d_{x}(x, y)\right|+\left|d_{y}(x, y)\right|
角度爲:
θM=arctan(dy/dx)\theta_{M}=\arctan \left(d_{y} / d_{x}\right)

這裏需要注意的是:梯度方向和圖像邊緣方向是互相正交的。
在這裏插入圖片描述

import cv2
import numpy as np

# Read image
img = cv2.imread('E:/python-project/deep-learning/picture/test1.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加上梯度值,加權公式爲:

40度對應的bin:((40-36)/20) * 13.6,分母的20表示20等份,而不是20度;
20度對應的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]的三維向量來說,模長是1282+642+322=146.64\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就可以排上用場了。

4.3 基於OpenCV的實現

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

if __name__ == '__main__':
    src = cv.imread("E:/python-project/deep-learning/picture/test7.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('E:/python-project/deep-learning/picture/test8.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')

在這裏插入圖片描述

4.4 總結

HOG算法具有以下優點:

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

HOG算法具有以下缺點:

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

論文地址:Histograms of Oriented Gradients for Human Detection - 2005CVPR

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章