HOG特征原理及代碼實現


HOG特征原理

HOG特征:

方向梯度直方圖(Histogram of Oriented Gradient, HOG)特征是一種在計算機視覺和圖像處理中用來進行物體檢測的特征描述子。

它通過計算和統計圖像局部區域的梯度方向直方圖來構成特征。Hog特征結合SVM分類器已經被廣泛應用於圖像識別中,尤其在行人檢測中獲得了極大的成功。

HOG+SVM進行行人檢測的方法是法國研究人員Dalal在2005的CVPR上提出的,而如今雖然有很多行人檢測算法不斷提出,但基本都是以HOG+SVM的思路為主。

(1)主要思想:

在一副圖像中,局部目標的表象和形狀(appearance and shape)能夠被梯度邊緣的方向密度分布很好地描述。(本質:梯度的統計信息,而梯度主要存在於邊緣的地方)。

(2)具體的實現方法是:

首先將圖像分成小的連通區域,我們把它叫細胞單元(cell or patch)。然后采集細胞單元中各像素點的梯度的或邊緣的方向直方圖。最后把這些直方圖組合起來就可以構成特征描述器。

(3)提高性能:

把這些局部直方圖在圖像的更大的范圍內(我們把它叫區間或block)進行對比度歸一化(contrast-normalized),所采用的方法是:先計算各直方圖在這個區間(block)中的密度,然后根據這個密度對區間中的各個細胞單元做歸一化。通過這個歸一化后,能對光照變化和陰影獲得更好的效果。

(4)優點:

與其他的特征描述方法相比,HOG有很多優點。首先,由於HOG是在圖像的局部方格單元上操作,所以它對圖像幾何的和光學的形變都能保持很好的不變性,這兩種形變只會出現在更大的空間領域上。其次,在粗的空域抽樣、精細的方向抽樣以及較強的局部光學歸一化等條件下,只要行人大體上能夠保持直立的姿勢,可以容許行人有一些細微的肢體動作,這些細微的動作可以被忽略而不影響檢測效果。因此HOG特征是特別適合於做圖像中的人體檢測的

HOG計算過程

原論文中的計算流程圖:

(1)圖像預處理

包括伽馬校正灰度化。這是可選的步驟,因為實驗證明做不做影響不大。伽馬校正是減少光度對實驗的影響。灰度化是將彩色圖片變成灰度圖。其實彩色圖片也可以直接處理。不過是分別對三通道的顏色值進行梯度計算,最后選擇梯度最大的那個。為簡單起見,假設輸入為灰度圖,同時大小是64*128(這個大小是上面論文的大小,也可以自己確定不同的大小,但是實驗效果就不能得到保證)。

(2)計算每一個像素的梯度(大小和原圖一樣)

A.分別計算水平梯度(horizontal gradient)和垂直梯度(vertical gradient)

使用下面的核(kernel)來計算

例:

對於像素點A,要計算水平梯度和豎直梯度,如上圖,水平梯度 =30-20=10,豎直梯度 =64-32=32.

// C++ gradient calculation. 
// Read image
Mat img = imread("bolt.png");
img.convertTo(img, CV_32F, 1/255.0);
 
// Calculate gradients gx, gy
Mat gx, gy; 
Sobel(img, gx, CV_32F, 1, 0, 1);
Sobel(img, gy, CV_32F, 0, 1, 1);
# Python gradient calculation 
 
# Read image
im = cv2.imread('bolt.png')
im = np.float32(im) / 255.0
 
# Calculate gradient 
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)

B.計算梯度大小(magnitude of gradient)和梯度方向(direction of gradient)

梯度大小和梯度方向將按照以下公式計算

梯度方向將會取絕對值,因此梯度方向的范圍是0-180度。取絕對值的原因是這樣效果更好。

(3)計算梯度直方圖

按照第二步的計算,每一個像素點都會有兩個值:梯度強度/梯度方向。

現在計算梯度直方圖,這是一個最關鍵的步驟,也是HOG能夠工作的原因。
梯度直方圖是在一個88的cell里面計算的。那么在88的cell里面就會有882=128個值,2是包括了梯度強度和梯度方向。通過統計形成梯度直方圖,128個值將會變成9個值,大大降低了計算量,同時又對光照等環境變化更加地robust。

首先,我將0-180度分成9個bins,分別是0,20,40...160。然后統計每一個像素點所在的bin。

梯度方向為80,梯度大小為2的點:80對應80的位置,值為2;
梯度方向為10,梯度大小為4的點:10在0-20正中間,所以0和2位置值均為2;
梯度方向為165,梯度大小為85的點:165在160-180之間,所以160位置值為855/20=21.5,0位置值為8515/20=63.5;
最終得到一個cell的梯度直方圖:

歸一化

將四個cell組成一個blocks,對16 * 16大小的blocks進行歸一化。
歸一化的目的是降低光照的影響。
歸一化的方法是向量的每一個值除以向量的模長。
比如對於一個(128,64,32)的三維向量來說,模長是
那么歸一化后的向量變成了(0.87,0.43,0.22)
那么16*16大小的block是怎么來的?
請看下圖
動圖戳這里

得到HOG特征向量

每一個16 * 16大小的block將會得到36大小的vector。那么對於一個64 * 128大小的圖像,按照上圖的方式提取block,將會有7個水平位置和15個豎直位可以取得,所以一共有7 * 15=105個block,所以我們整合所有block的vector,形成一個大的一維vector的大小將會是36 * 105=3780。

得到HOG特征向量,就可以用來可視化和分類了。對於這么大的HOG特征,SVM就派上用場了。
結果如圖:

代碼實現

# @hichens

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt


class Hog_descriptor():
    def __init__(self, img, cell_size=16, bin_size=8):
        self.img = img
        '''
        伽馬校正
        self.img = np.sqrt(img / np.max(img))
        self.img = img * 255
        '''
        self.cell_size = cell_size
        self.bin_size = bin_size
        self.angle_unit = 360 / self.bin_size

    def extract(self):
        height, width = self.img.shape
        #1.計算每個像素的梯度和方向
        gradient_magnitude, gradient_angle = self.global_gradient()
        gradient_magnitude = abs(gradient_magnitude)

        cell_gradient_vector = np.zeros((height // self.cell_size, width // self.cell_size, self.bin_size))
        for i in range(cell_gradient_vector.shape[0]):
            for j in range(cell_gradient_vector.shape[1]):
                #取一個cell中的梯度大小和方向
                cell_magnitude = gradient_magnitude[i * self.cell_size:(i + 1) * self.cell_size,
                                 j * self.cell_size:(j + 1) * self.cell_size]
                cell_angle = gradient_angle[i * self.cell_size:(i + 1) * self.cell_size,
                             j * self.cell_size:(j + 1) * self.cell_size]

                #得到每一個cell的梯度直方圖;
                cell_gradient_vector[i][j] = self.cell_gradient(cell_magnitude, cell_angle)

        #得到HOG特征可視化圖像
        hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)

        #HOG特征向量
        hog_vector = []
        #使用滑動窗口
        for i in range(cell_gradient_vector.shape[0] - 1):
            for j in range(cell_gradient_vector.shape[1] - 1):
                #4個cell得到一個block
                block_vector = cell_gradient_vector[i:i+1][j:j+1].reshape(-1, 1)
                #正則化
                block_vector = np.array([vector / np.linalg.norm(vector) for vector in block_vector])
                hog_vector.append(block_vector)
        return hog_vector, hog_image

    def global_gradient(self):
        #得到每個像素的梯度
        gradient_values_x = cv2.Sobel(self.img, cv2.CV_64F, 1, 0, ksize=5)#水平
        gradient_values_y = cv2.Sobel(self.img, cv2.CV_64F, 0, 1, ksize=5)#垂直
        gradient_magnitude = np.sqrt(gradient_values_x**2 + gradient_values_y**2)#總
        gradient_angle = cv2.phase(gradient_values_x, gradient_values_y, angleInDegrees=True)#方向
        return gradient_magnitude, gradient_angle

    def cell_gradient(self, cell_magnitude, cell_angle):
        #得到cell的梯度直方圖
        orientation_centers = [0] * self.bin_size
        for i in range(cell_magnitude.shape[0]):
            for j in range(cell_magnitude.shape[1]):
                gradient_strength = cell_magnitude[i][j]
                gradient_angle = cell_angle[i][j]
                min_angle, max_angle, mod = self.get_closest_bins(gradient_angle)
                orientation_centers[min_angle] += (gradient_strength * (1 - (mod / self.angle_unit)))
                orientation_centers[max_angle] += (gradient_strength * (mod / self.angle_unit))
        return orientation_centers

    def get_closest_bins(self, gradient_angle):
        idx = int(gradient_angle / self.angle_unit)
        mod = gradient_angle % self.angle_unit
        return idx, (idx + 1) % self.bin_size, mod

    def render_gradient(self, image, cell_gradient):
        #得到HOG特征圖
        cell_width = self.cell_size / 2
        max_mag = np.array(cell_gradient).max()
        for x in range(cell_gradient.shape[0]):
            for y in range(cell_gradient.shape[1]):
                cell_grad = cell_gradient[x][y]
                cell_grad /= max_mag
                angle = 0
                angle_gap = self.angle_unit
                for magnitude in cell_grad:
                    angle_radian = math.radians(angle)
                    x1 = int(x * self.cell_size + magnitude * cell_width * math.cos(angle_radian))
                    y1 = int(y * self.cell_size + magnitude * cell_width * math.sin(angle_radian))
                    x2 = int(x * self.cell_size - magnitude * cell_width * math.cos(angle_radian))
                    y2 = int(y * self.cell_size - magnitude * cell_width * math.sin(angle_radian))
                    cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
                    angle += angle_gap
        return image

img = cv2.imread('/home/hichens/Desktop/lena_full.jpg', cv2.IMREAD_GRAYSCALE) # 灰度圖片
hog = Hog_descriptor(img, cell_size=8, bin_size=9)
vector, image = hog.extract()
print (np.array(vector).shape)
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.subplot(122)
plt.imshow(image, cmap=plt.cm.gray)
plt.show()

參考文獻

1.目標檢測的圖像特征提取之(一)HOG特征
2.HOG特征
3.Histogram of Oriented Gradients
4.基於傳統圖像處理的目標檢測與識別(HOG+SVM附代碼)


免責聲明!

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



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