任务描述
上一个任务我们一起学习了HOG特征提取的步骤,并实现了其中的颜色空间归一化和计算梯度图,在本次任务中我们将继续学习并实践接下来的步骤。
相关知识
为了完成本关任务,你需要了解:
计算梯度方向直方图;
重叠块直方图归一化,计算HOG特征向量。
计算梯度方向直方图
在这一步,我们先把整个图像划分为若干个8x8的小单元,称为cell,并计算每个cell的梯度直方图。这个cell的尺寸也可以是其他值,根据我们想要的特征尺度来确定。HOG特征最早提出是用于行人检测的,64x128大小的图像使用8x8的cell已经足够捕捉到感兴趣的特征了。
现在我们要把这个8x8的小单元用长度为9的数组来表示,这个数组就是梯度直方图。这种表示方法不仅使得特征更加紧凑,而且对单个像素值的变化不敏感,也就是能够抗噪声干扰。
我们来看一下图片中的一个cell中的梯度:
中间那张图中的箭头表示梯度,箭头方向表示梯度方向,箭头长度表示梯度大小。
右图是 8×8 的cell中表示梯度的原始数字,注意角度的范围介于0到180度之间,而不是0到360度, 这被称为“无符号”梯度,因为两个完全相反的方向被认为是相同的。
现在我们来计算cell中像素的梯度直方图,先将角度范围分成9份,也就是9 bins,每20°为一个单元,也就是这些像素可以根据角度分为9组。将每一份中所有像素对应的梯度值进行累加,可以得到9个数值。直方图就是由这9个数值组成的数组,对应于角度0、20、40、60... 160。
比如上面方向图中蓝圈包围的像素,角度为80度,这个像素对应的幅值为2,所以在直方图80度对应的bin加上2。红圈包围的像素值,角度为10度,介于0度和20度之间,其幅值为4,那么这个梯度值就被按比例分给0度和20度对应的bin,也就是各加上2。
此外还需要注意的是,若某个像素的梯度角度大于160度,也就是在160度到180度之间,那么把这个像素对应的梯度值按比例分给0度和160度对应的bin。
将这 8x8 的cell中所有像素的梯度值加到各自角度对应的bin中,就形成了长度为9的直方图:
从上图可以看到,更多的点的梯度方向是倾向于0度和160度,也就是说这些点的梯度方向是向上或者向下,表明图像这个位置存在比较明显的横向边缘。因此HOG是对边角敏感的,由于这样的统计方法,也是对部分像素值变化不敏感的,所以能够适应不同的环境。
def compute_cell_gradient(cell_magnitude, cell_angle, bin_size, unit):
centers = [0] * bin_size
# 遍历`cell`,统计梯度方向
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]
# 将像素归到某一个bin
idx = int(gradient_angle / unit)
mod = gradient_angle % unit
min_angle = idx
max_angle = (idx + 1) % bin_size
# 根据角度的相近程度分别对邻近的两个区间进行加权
centers[min_angle] += (strength * (1 - (mod / unit)))
centers[max_angle] += (strength * (mod / unit))
return centers
重叠块直方图归一化,计算HOG特征向量
为了降低光照的影响,HOG将图像中8x8的一个区域作为一个cell,再将2x2个cell作为一个block,对每一个block进行了归一化操作。由于每个cell有9个值,2×2个cell则有36个值,通过步长为一个cell的滑动窗口来得到block的。所以窗口每滑动一次,得到一个block,一个block有四个cell,在前面的步骤中,我们基于图像的梯度对每个cell创建了一个直方图,即有四个直方图,将这4个直方图拼接成长度为36的向量,然后使用L2范数对这个向量进行归一化。
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
编程要求
本次任务主要使用OpenCV实现HOG特征提取的后面几个步骤:计算梯度方向直方图;重叠块直方图归一化,计算HOG特征向量。
根据提示,在右侧编辑器补充代码,针对给定的注释提示进行对应操作。
测试说明
平台会对你编写的代码进行测试:
预期输出:
(64, 64, 9)
3969
import cv2
import numpy as np
import math
from utils import *
def compute_image_gradient(img):
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)
return magnitude, angle
def compute_cell_gradient(cell_magnitude, cell_angle, bin_size, unit):
centers = [0] * bin_size
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]
######## Begin1 ########
# 将像素归到某一个bin
idx = int(gradient_angle / unit)
mod = gradient_angle % unit
min_angle = idx
max_angle = (idx + 1) % bin_size
# 根据角度的相近程度分别对邻近的两个区间进行加权
centers[min_angle] += (strength * (1 - (mod / unit)))
centers[max_angle] += (strength * (mod / unit))
######## En1 ########
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 = []
######## Begin2 ########
# 拼接四个cell
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:
# L2范数归一化
normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
block_vector = normalize(block_vector, magnitude)
# 统计HOG特征
hog_vector.append(block_vector)
######## End2 ########
return hog_vector
def main(img):
cell_size = 8
bin_size = 9
unit = 180
height, width = img.shape
magnitude, angle = compute_image_gradient(img)
cell_gradient_vector = np.zeros((height // cell_size, width // cell_size, bin_size))
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]
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)
hog_vector = normalized(cell_gradient_vector)
test_task5(cell_gradient_vector,hog_vector)
if __name__ == '__main__':
# 读取图像:/data/workspace/myshixun/src/data/Lena.jpg
img = cv2.imread("/data/workspace/myshixun/src/data/Lena.jpg", 0)
main(img)