Histogram of Oriented Gridients,缩写为HOG,是目前计算机视觉、模式识别领域很常用的一种描述图像局部纹理的特征。就是说先计算图片某一区域中不同方向上梯度的值,然后进行累积,得到直方图,这个直方图呢,就可以代表这块区域了,也就是作为特征,可以输入到分类器里面了。

 

计算每一个像素的梯度:

计算方式:如下图为A像素及其上下左右的四个像素值。

图像梯度直方图python实现_直方图

通过计算水平梯度gx和垂直梯度gy来获取梯度值g。

水平梯度gx:

图像梯度直方图python实现_归一化_02

垂直梯度:

图像梯度直方图python实现_直方图_03

梯度值g:

图像梯度直方图python实现_直方图_04

梯度方向:

图像梯度直方图python实现_python_05

梯度方向将会取绝对值,因此梯度方向的范围是0-180度。取绝对值的原因是这样效果更好。

经过上面过程,每一个像素点的梯度值和梯度方向都可以被求出来。

 

计算梯度方向直方图:

由于一个点有梯度值和梯度方向两个数值。一个cell有8*8个像素,所以一个cell有8*8*2=128个数值。每一个cell都会生成一个方向梯度直方图,通过统计形成梯度直方图,128个值将会变成9个值。怎样变呢,过程如下:

首先,把180°分成九分,即每份是20°。如下图所示,如蓝色部分的点,从左边的梯度方向图可知,此点的梯度方向为80度,所以在下面的角度为80的bin中,填入这点的梯度数值,即2。那如果某点的梯度方向不是刚刚好是20、40....160度怎么办呢?如红色部分的点所示,可以看出,红色点的梯度方向为10°,其梯度数值为4,由于10位于0到20中间,所以把梯度数值各分一半给0的bin和20的bin。循环这个过程,那么一个cell就可以得到一个梯度方向直方图。

图像梯度直方图python实现_直方图_06

统计完一个cell的64个点后,得到一个长度为9的一维vector(向量):

图像梯度直方图python实现_归一化_07

 

归一化

归一化的目的是降低光照的影响。

归一化是以block为单位,如下图所示,一个block=4个cell,一个cell是8*8像素,那么一个block即是16*16个像素。

(绿色是cell,蓝色是block)

图像梯度直方图python实现_直方图_08

我们已经知道一个cell是有一个直方图的,可以看成一个长度为9的一维向量。那么一个block就有4个这样的9*1的向量,把4个一维向量合成一个,则一个block会有一个36*1的一维向量。然后进行归一化。

归一化过程用的是L2归一化:

比如对于一个(128,64,32)的三维向量来说,模长是 :

图像梯度直方图python实现_图像梯度直方图python实现_09

那么归一化后的向量变成了(0.87,0.43,0.22)。

 

计算HOG特征向量及可视化:

现在就到了最后一步了。如上图(跑步选手),水平会有7个block(block每次移动一个格子),垂直会有15个。所以一张图一共会有15*7=105个block,每个block有一个36*1的一维向量,所以一张图就有一个长度为 105*36 = 3780 的一维向量。

既然一张图的HOG特征是一个一维向量,那怎么可视化出来呢。

HOG的可视化结果为下图:

图像梯度直方图python实现_图像梯度直方图python实现_10

(应用:每张图片的HOG特征是可以输入到分类器中实现分类的)

可以看到,每一个cell都有9条线(线的数目跟梯度方向直方图中的bin个数相同),9条线格子表示180°分成9份后不同的方向(例如20°,40°........)。我们用一个python 的sklearn库实现的HOG看看效果:

from skimage.feature import hog
from skimage import io

im = io.imread('D://timg.jpg',as_grey=True)
normalised_blocks, hog_image = hog(im, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), visualise=True)
io.imshow(im)
io.show()
io.imshow(hog_image)
io.show()

输入:                            

图像梯度直方图python实现_归一化_11

输出的HOG可视化:

图像梯度直方图python实现_图像梯度直方图python实现_12

上面是cell为8*8像素的,为了明显展示,我们把cell调到32*32看看输出的效果:

图像梯度直方图python实现_python_13

可以看到,以格子为单位,很多以一点为中心往外辐射的线条,这些就是一个cell中,9个bin对应的线条(包括梯度和梯度方向)。

非调用接口的python代码:

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt
img = cv2.imread("D://car.jpg", cv2.IMREAD_GRAYSCALE)
def compute_image_gradient(img):
    #print(img.shape) #(333, 500)
    x_values = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
    y_values = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
    magnitude = abs(cv2.addWeighted(x_values, 0.5, y_values, 0.5, 0))
    angle = cv2.phase(x_values, y_values, angleInDegrees=True)
    #print(angle.shape) #(333, 500)
    return magnitude, angle
def choose_bins(gradient_angle, unit, bin_size):
    idx = int(gradient_angle / unit) #9个bin的某一个
    mod = gradient_angle % unit
    return idx, (idx + 1) % bin_size, mod
def compute_cell_gradient(cell_magnitude, cell_angle, bin_size, unit):
    centers = [0] * bin_size
    #print(cell_magnitude.shape) (8,8)
    for i in range(cell_magnitude.shape[0]):
        for j in range(cell_magnitude.shape[1]):
            strength = cell_magnitude[i][j]
            gradient_angle = cell_angle[i][j]
            min_angle, max_angle, mod = choose_bins(gradient_angle, unit, bin_size)
            #print(gradient_angle, unit, min_angle, max_angle)
            centers[min_angle] += (strength * (1 - (mod / unit)))
            centers[max_angle] += (strength * (mod / unit))
    return centers
def normalized(cell_gradient_vector):
    hog_vector = []
    for i in range(cell_gradient_vector.shape[0] - 1):
        for j in range(cell_gradient_vector.shape[1] - 1):
            block_vector = []
            block_vector.extend(cell_gradient_vector[i][j])
            block_vector.extend(cell_gradient_vector[i][j + 1])
            block_vector.extend(cell_gradient_vector[i + 1][j])
            block_vector.extend(cell_gradient_vector[i + 1][j + 1])
            mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))
            magnitude = mag(block_vector)
            if magnitude != 0:
                normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
                block_vector = normalize(block_vector, magnitude)
            hog_vector.append(block_vector)
    return hog_vector
def visual(cell_gradient, height, width, cell_size, unit):
    #print(cell_gradient.shape) #(41, 62, 9)
    feature_image = np.zeros([height, width])
    cell_width = cell_size / 2
    #print(cell_width) #4
    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]
            #print(cell_grad.shape) #(9,)
            cell_grad /= max_mag
            angle = 0
            angle_gap = unit
            for magnitude in cell_grad:
                angle_radian = math.radians(angle)
                x1 = int(x * cell_size + magnitude * cell_width * math.cos(angle_radian))
                y1 = int(y * cell_size + magnitude * cell_width * math.sin(angle_radian))
                x2 = int(x * cell_size - magnitude * cell_width * math.cos(angle_radian))
                y2 = int(y * cell_size - magnitude * cell_width * math.sin(angle_radian))

                cv2.line(feature_image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
                angle += angle_gap
    return feature_image
def main(img):
    cell_size = 8
    bin_size = 9
    unit = 360 // bin_size
    height, width = img.shape
    magnitude, angle = compute_image_gradient(img)
    #print(magnitude.shape)#(333, 500)
    cell_gradient_vector = np.zeros((height // cell_size, width // cell_size, bin_size))
    #print(cell_gradient_vector.shape) #(41, 62, 9)
    for i in range(cell_gradient_vector.shape[0]):
        for j in range(cell_gradient_vector.shape[1]):
            cell_magnitude = magnitude[i * cell_size:(i + 1) * cell_size,
                             j * cell_size:(j + 1) * cell_size]
            #print(cell_magnitude.shape) #(8, 8)
            cell_angle = angle[i * cell_size:(i + 1) * cell_size,
                         j * cell_size:(j + 1) * cell_size]
            cell_gradient_vector[i][j] = compute_cell_gradient(cell_magnitude, cell_angle, bin_size, unit) #返回一个cell的直方图
    hog_vector = normalized(cell_gradient_vector)
    #print(cell_gradient_vector.shape) #(20, 31, 9)
    hog_image = visual(cell_gradient_vector, height, width, cell_size, unit)
    plt.imshow(hog_image, cmap=plt.cm.gray)
    plt.show()
if __name__ == '__main__':
    img = cv2.imread('D://timg.jpg', cv2.IMREAD_GRAYSCALE)
    cv2.imshow("origin", img)
    cv2.waitKey()
    main(img)

效果:

输入

图像梯度直方图python实现_直方图_14

输出

图像梯度直方图python实现_直方图_15

 

HOG的应用:

HOG+SVM实现行人检测:

大概流程:

图像梯度直方图python实现_python_16

其中SVM是用已有的opencv中的svm检测器

import cv2 as cv

# 主程序入口
if __name__ == '__main__':
    # 读取图像
    src = cv.imread("D:/p.jpg")
    cv.imshow("input", src)
    # HOG + SVM
    hog = cv.HOGDescriptor()
    hog.setSVMDetector(cv.HOGDescriptor_getDefaultPeopleDetector())
    # Detect people in the image
    (rects, weights) = hog.detectMultiScale(src,
                                            winStride=(4, 4),
                                            padding=(8, 8),
                                            scale=1.25,
                                            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.waitKey(0)
    cv.destroyAllWindows()

效果:

图像梯度直方图python实现_python_17

图像梯度直方图python实现_直方图_18

当行人比较稀疏时,检测效果还好,但当人体遮挡情况比较严重且北京复杂时,会有误检,错检的情况。