文章目录
1 HOG特征简介
Hog 算法的工作原理是创建图像中梯度方向分布的柱状图,然后以一种非常特殊的方式对其进行归一化。这种特殊的归一化使得Hog 能够有效地检测物体的边缘,即使在对比度很低的情况下也是如此。这些标准化的柱状图被放在一个特征向量(称为 HOG 描述符)中,可以用来训练机器学习算法,例如支持向量机(SVM),以根据图像中的边界(边)检测对象。由于它的巨大成功和可靠性,HOG 已成为计算机视觉中应用最广泛的目标检测算法之一。
特征描述符是图像或图像补丁的表示形式,它通过提取有用信息并丢弃无关信息来简化图像。
- 通常,特征描述符将大小W x H x 3(通道)的图像转换为长度为n的特征向量/数组。对于 HOG 特征描述符,输入图像的大小为 64 x 128 x 3,输出特征向量的长度为 3780。
- 在HOG特征描述符中,梯度方向的分布(直方图)被用作特征。图像的渐变(x和y导数)很有用,因为边缘和角落(强度突然变化的区域)周围的梯度大小很大,我们知道边缘和角落比平面区域包含更多关于物体形状的信息。
- HOG(Histogram of Oriented Gridients的简写)特征检测算法,最早是由法国研究员Dalal等在CVPR-2005上提出来的,一种解决人体目标检测的图像描述子,是一种用于表征图像局部梯度方向和梯度强度分布特性的描述符。其主要思想是:在边缘具体位置未知的情况下,边缘方向的分布也可以很好的表示行人目标的外形轮廓。
2 HOG简单描述
2.1 加载图像和导入资源
构建HOG描述符的第一步是将所需的软件包加载到Python中并加载我们的图像。
我们首先使用OpenCV加载三角形图块的图像。 由于cv2.imread()
函数将图像加载为BGR,因此我们会将图像转换为RGB,以便可以使用正确的颜色进行显示。 与往常一样,我们会将BGR图像转换为灰度进行分析。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 设置默认图形的尺寸
plt.rcParams['figure.figsize'] = [17.0, 7.0]
# 载入图片
image = cv2.imread('./images/triangle_tile.jpeg')
# 将原始图像转换为RGB
original_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 将原始图像转换为灰度
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 输出原始图像和灰度图像的形状
print('The original image has shape: ', original_image.shape)
print('The gray scale image has shape: ', gray_image.shape)
# 输出图像
plt.subplot(121)
plt.imshow(original_image)
plt.title('Original Image')
plt.subplot(122)
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Scale Image')
plt.show()
The original image has shape: (250, 250, 3)
The gray scale image has shape: (250, 250)
2.3 创建 HOG 描述符
我们将使用 OpenCV 的HOGDescriptor
类来创建 HOG 描述符。HOG 描述符的参数是使用HOGDescriptor()
function. The parameters of the HOGDescriptor()
函数设置的。 HOGDescriptor() 函数的参数及其默认值如下:
cv2.HOGDescriptor(win_size = (64, 128), block_size = (16, 16), block_stride = (8, 8), cell_size = (8, 8), nbins = 9, win_sigma = DEFAULT_WIN_SIGMA, threshold_L2hys = 0.2, gamma_correction = true, nlevels = DEFAULT_NLEVELS)
官方参数解释如下:
-
win_size – Size
检测窗口的大小,以像素为单位(宽度,高度)。 定义感兴趣的区域。 必须是像素大小的整数倍。 -
block_size – Size
块大小,以像素为单位(宽度,高度)。 定义每个块中有多少个单元格。 必须是像素大小的整数倍,并且必须小于检测窗口。 方块越小,可以获得的细节越细。 -
block_stride – Size 块的步幅
块跨度以像素为单位(水平,垂直)。 它必须是像元大小的整数倍。block_stride
定义相邻块之间的距离,例如,水平8个像素,垂直8个像素。 较长的block_strides
使算法运行更快(因为评估的块更少),但是算法的效果可能不佳。 -
cell_size – Size
像元大小(以像素为单位)(宽度,高度)。 确定单元格的大小。 单元格越小,可以获得的细节越精细。 -
nbins – int 直方图的条数(bins)
直方图的箱数。确定用于制作直方图的角度仓的数量。使用更多箱柜,您可以捕获更多梯度方向。 HOG使用无符号的渐变,因此角度单元的值将介于0和180度之间。 -
win_sigma – double
高斯平滑窗口参数。 通过在计算直方图之前对每个像素应用高斯空间窗口,可以对块边缘附近的像素进行平滑处理,从而提高HOG算法的性能。 -
threshold_L2hys – double
L2-Hys(Lowe样式修剪的L2范数)归一化方法收缩。 L2-Hys方法用于对块进行规范化,它由L2范数,裁剪和重新规范化组成。 限幅将每个块的描述符向量的最大值限制为具有给定阈值的值(默认为0.2)。 裁剪后,描述符向量按照* IJCV *,60(2):91-110,2004中所述进行重新规范化。 -
gamma_correction – bool
用于指定是否需要伽马校正预处理的标志。 执行伽玛校正会稍微提高HOG算法的性能。 -
nlevels – int
最大检测窗口数增加量。
我们可以看到,cv2.HOGDescriptor()
函数支持各种参数。 前几个参数(block_size, block_stride, cell_size
, 和 nbins
) 可能是最常用的参数。其他参数一般可以保留其默认值,即可获得良好的结果。
在下面的代码中,我们将使用cv2.HOGDescriptor()
函数来设置单元格大小,块大小,块步幅以及HOG描述符直方图的 bin 数。 然后使用.compute(image)
方法计算给定image
的 HOG 描述符(特征向量)。
# 为HOG描述符指定参数
# 像素大小(以像素为单位)(宽度,高度)。 它必须小于检测窗口的大小,
# 并且必须进行选择,以使生成的块大小小于检测窗口的大小。
cell_size = (6, 6)
# 每个方向(x,y)上每个块的单元数。 必须选择为使结果
# 块大小小于检测窗口
num_cells_per_block = (2, 2)
# 块大小(以像素为单位)(宽度,高度)。必须是“单元格大小”的整数倍。
# 块大小必须小于检测窗口。
block_size = (num_cells_per_block[0] * cell_size[0],
num_cells_per_block[1] * cell_size[1])
# 计算在x和y方向上适合我们图像的像素数
x_cells = gray_image.shape[1] // cell_size[0]
y_cells = gray_image.shape[0] // cell_size[1]
# 块之间的水平距离,以像元大小为单位。 必须为整数,并且必须
# 将其设置为(x_cells-num_cells_per_block [0])/ h_stride =整数。
h_stride = 1
# 块之间的垂直距离,以像元大小为单位。 必须为整数,并且必须
# 将其设置为 (y_cells - num_cells_per_block[1]) / v_stride = integer.
v_stride = 1
# 块跨距(以像素为单位)(水平,垂直)。 必须是像素大小的整数倍。
block_stride = (cell_size[0] * h_stride, cell_size[1] * v_stride)
# 梯度定向箱的数量
num_bins = 9
# 指定检测窗口(感兴趣区域)的大小,以像素(宽度,高度)为单位。
# 它必须是“单元格大小”的整数倍,并且必须覆盖整个图像。
# 由于检测窗口必须是像元大小的整数倍,具体取决于您像元的大小,
# 因此生成的检测窗可能会比图像小一些。
# 完全可行
win_size = (x_cells * cell_size[0] , y_cells * cell_size[1])
# 输出灰度图像的形状以供参考
print('\nThe gray scale image has shape: ', gray_image.shape)
print()
# 输出HOG描述符的参数
print('HOG Descriptor Parameters:\n')
print('Window Size:', win_size)
print('Cell Size:', cell_size)
print('Block Size:', block_size)
print('Block Stride:', block_stride)
print('Number of Bins:', num_bins)
print()
# 使用上面定义的变量设置HOG描述符的参数
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, num_bins)
# 计算灰度图像的HOG描述符
hog_descriptor = hog.compute(gray_image)
The gray scale image has shape: (250, 250)
HOG Descriptor Parameters:
Window Size: (246, 246)
Cell Size: (6, 6)
Block Size: (12, 12)
Block Stride: (6, 6)
Number of Bins: 9
2.4 HOG 描述符中的元素数量
HOG 描述符(特征向量)是由检测窗口中的所有块的所有单元的归一化直方图 concat 起来的长向量。 因此,HOG特征向量的大小将由检测窗口中的块总数乘以每个块的单元数乘以定向箱(bin)的数量来给出:
\begin{equation} \mbox{total_elements} = (\mbox{total_number_of_blocks})\mbox{ } \times \mbox{ } (\mbox{number_cells_per_block})\mbox{ } \times \mbox{ } (\mbox{number_of_bins}) \end{equation}如果我们没有重叠块(即block_stride
等于block_size
的情况),则可以通过将检测窗口的大小除以块大小来容易地计算块的总数。 但是,在一般情况下,我们必须考虑到有重叠块的事实。 要查找一般情况下的块总数(即对于任何block_stride
和 block_size
),我们可以使用下面给出的公式:
其中 Total
x
_x
x,是沿检测窗口宽度的总块数, Total
y
_y
y, 是沿检测窗口高度的块总数。 Total
x
_x
x 和 Total
y
_y
y的公式考虑了重叠产生的额外块。 在计算 Total
x
_x
x 和 Total
y
_y
y之后,我们可以通过乘法得到检测窗口中的块总数Total
x
_x
x
×
\times
× Total
y
_y
y。 上面的公式可以大大简化,因为block_size
, block_stride
,和 window_size
都是根据cell_size
定义的。 通过进行所有适当的替换和取消,上述公式简化为:
其中 cells
x
_x
x 是沿检测窗口宽度的单元格总数,cells
y
_y
y是沿检测窗口高度的单元总数。
N
x
N_x
Nx 是以cell_size
为单位的水平块步幅,
N
y
N_y
Ny 是以 cell_size
为单位的垂直块步幅。
下面让我们计算 HOG 特征向量的元素数量,并检查它是否与上面计算的 HOG 描述符的形状相匹配
# 计算沿着检测窗口宽度的总块数
tot_bx = np.uint32(((x_cells - num_cells_per_block[0]) / h_stride) + 1)
# 计算沿着检测窗口高度的块总数
tot_by = np.uint32(((y_cells - num_cells_per_block[1]) / v_stride) + 1)
# 计算特征向量中的元素总数
tot_els = (tot_bx) * (tot_by) * num_cells_per_block[0] * num_cells_per_block[1] * num_bins
# 输出HOG特征向量应具有的元素总数
print('\nThe total number of elements in the HOG Feature Vector should be: ',
tot_bx, 'x',
tot_by, 'x',
num_cells_per_block[0], 'x',
num_cells_per_block[1], 'x',
num_bins, '=',
tot_els)
# 打印HOG描述符的形状,看它是否与上面的匹配
print('\nThe HOG Descriptor has shape:', hog_descriptor.shape)
print()
The total number of elements in the HOG Feature Vector should be: 40 x 40 x 2 x 2 x 9 = 57600
The HOG Descriptor has shape: (57600, 1)
2.5 可视化 HOG 描述符
OpenCV没有简单的方法来可视化HOG描述符,因此我们必须首先进行一些操作才能使其可视化。我们将从重塑HOG描述符开始,以使我们的计算更加容易。然后,我们将计算每个单元格的平均直方图,最后将直方图bin转换为矢量。一旦有了向量,就可以为图像中的每个单元绘制相应的向量。
下面的代码生成一个交互式绘图,因此您可以与该图进行交互。该图包含:
- 灰度图像,
- HOG描述符(功能向量),
- HOG描述符的放大部分,以及
- 所选单元格的直方图。
您可以在灰度图像或HOG描述符图像上的任意位置单击以选择特定的单元格。单击任一图像后,将出现一个洋红色矩形,显示您选择的单元格。缩放窗口将为您显示所选单元格周围HOG描述符的放大版本;直方图将为您显示所选单元格的相应直方图。交互式窗口的底部还有一些按钮,允许使用其他功能(例如平移),并且可以根据需要选择保存图形。主页按钮会将图形恢复为其默认值。
注意:如果在Udacity工作区中运行此笔记本,则在交互式绘图中大约有2秒的延迟。这意味着,如果单击图像进行放大,则刷新图大约需要2秒钟。
%matplotlib notebook
%matplotlib inline
import copy
import matplotlib.patches as patches
# 设置默认图形尺寸
plt.rcParams['figure.figsize'] = [9.8, 9]
# 将特征向量重塑为 [blocks_y, blocks_x, num_cells_per_block_x, num_cells_per_block_y, num_bins].
# blocks_x和blocks_y将被换位,以便第一个索引(blocks_y)引用行号
# 第二个索引引用列号。 稍后在绘制特征向量时这将很有用,
# 以便特征向量索引与图像索引匹配。
hog_descriptor_reshaped = hog_descriptor.reshape(tot_bx,
tot_by,
num_cells_per_block[0],
num_cells_per_block[1],
num_bins).transpose((1, 0, 2, 3, 4))
# 输出特征向量的形状以供参考
print('The feature vector has shape:', hog_descriptor.shape)
# 输出重塑的特征向量
print('The reshaped feature vector has shape:', hog_descriptor_reshaped.shape)
# 创建一个数组,该数组将保存每个单元的平均梯度
ave_grad = np.zeros((y_cells, x_cells, num_bins))
# 输出ave_grad数组的形状以供参考
print('The average gradient array has shape: ', ave_grad.shape)
# 创建一个数组,该数组将计算每个单元格的直方图数量
hist_counter = np.zeros((y_cells, x_cells, 1))
# 将每个单元格的所有直方图相加并计算每个单元格的直方图数
for i in range (num_cells_per_block[0]):
for j in range(num_cells_per_block[1]):
ave_grad[i:tot_by + i,
j:tot_bx + j] += hog_descriptor_reshaped[:, :, i, j, :]
hist_counter[i:tot_by + i,
j:tot_bx + j] += 1
# 计算每个单元的平均梯度
ave_grad /= hist_counter
# 计算在所有单元格中拥有的向量总数。
len_vecs = ave_grad.shape[0] * ave_grad.shape[1] * ave_grad.shape[2]
# 创建一个数组,该数组的num_bins的弧度在0至180度之间。
deg = np.linspace(0, np.pi, num_bins, endpoint = False)
# 每个单元格都有一个带有num_bins的直方图。 对于每个像元,将每个仓绘制为矢量
# (其大小等于直方图中仓的高度,其角度与直方图中的仓对应)。
# 为此,创建第1级数组,该数组将保留图像中所有单元格中所有向量的(x,y)坐标。
# 此外,创建等级1数组,该数组将保存图像中所有单元格中所有向量的所有(U,V)分量。
# 创建将包含所有向量位置和成分的数组。
U = np.zeros((len_vecs))
V = np.zeros((len_vecs))
X = np.zeros((len_vecs))
Y = np.zeros((len_vecs))
# 将计数器设置为零
counter = 0
# 使用余弦和正弦函数从其大小计算矢量分量(U,V)。
# 请记住,余弦和正弦函数采用弧度表示角度。
# 从平均梯度数组计算矢量位置和大小
for i in range(ave_grad.shape[0]):
for j in range(ave_grad.shape[1]):
for k in range(ave_grad.shape[2]):
U[counter] = ave_grad[i,j,k] * np.cos(deg[k])
V[counter] = ave_grad[i,j,k] * np.sin(deg[k])
X[counter] = (cell_size[0] / 2) + (cell_size[0] * i)
Y[counter] = (cell_size[1] / 2) + (cell_size[1] * j)
counter = counter + 1
# 创建以度为单位的箱,以绘制直方图。
angle_axis = np.linspace(0, 180, num_bins, endpoint = False)
angle_axis += ((angle_axis[1] - angle_axis[0]) / 2)
# 创建一个以2 x 2排列的4个子图的图形
fig, ((a,b),(c,d)) = plt.subplots(2,2)
# 设置每个子图的标题
a.set(title = 'Gray Scale Image\n(Click to Zoom)')
b.set(title = 'HOG Descriptor\n(Click to Zoom)')
c.set(title = 'Zoom Window', xlim = (0, 18), ylim = (0, 18), autoscale_on = False)
d.set(title = 'Histogram of Gradients')
# 绘制灰度图像
a.imshow(gray_image, cmap = 'gray')
a.set_aspect(aspect = 1)
# 绘制特征向量(HOG描述符)
b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
b.invert_yaxis()
b.set_aspect(aspect = 1)
b.set_facecolor('black')
# 定义交互式缩放函数
def onpress(event):
#除非按下鼠标左键,否则什么都不做
if event.button != 1:
return
# 仅接受子图a和b的点击
if event.inaxes in [a, b]:
# 获取鼠标点击坐标
x, y = event.xdata, event.ydata
# 选择最接近鼠标单击坐标的单元格
cell_num_x = np.uint32(x / cell_size[0])
cell_num_y = np.uint32(y / cell_size[1])
# 设置矩形面片的边缘坐标
edgex = x - (x % cell_size[0])
edgey = y - (y % cell_size[1])
# 创建一个与上面所选单元格匹配的矩形补丁
rect = patches.Rectangle((edgex, edgey),
cell_size[0], cell_size[1],
linewidth = 1,
edgecolor = 'magenta',
facecolor='none')
# 单个补丁只能在单个图中使用。
# 创建补丁副本以在其他子图中使用
rect2 = copy.copy(rect)
rect3 = copy.copy(rect)
# 更新所有子图
a.clear()
a.set(title = 'Gray Scale Image\n(Click to Zoom)')
a.imshow(gray_image, cmap = 'gray')
a.set_aspect(aspect = 1)
a.add_patch(rect)
b.clear()
b.set(title = 'HOG Descriptor\n(Click to Zoom)')
b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
b.invert_yaxis()
b.set_aspect(aspect = 1)
b.set_facecolor('black')
b.add_patch(rect2)
c.clear()
c.set(title = 'Zoom Window')
c.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 1)
c.set_xlim(edgex - cell_size[0], edgex + (2 * cell_size[0]))
c.set_ylim(edgey - cell_size[1], edgey + (2 * cell_size[1]))
c.invert_yaxis()
c.set_aspect(aspect = 1)
c.set_facecolor('black')
c.add_patch(rect3)
d.clear()
d.set(title = 'Histogram of Gradients')
d.grid()
d.set_xlim(0, 180)
d.set_xticks(angle_axis)
d.set_xlabel('Angle')
d.bar(angle_axis,
ave_grad[cell_num_y, cell_num_x, :],
180 // num_bins,
align = 'center',
alpha = 0.5,
linewidth = 1.2,
edgecolor = 'k')
fig.canvas.draw()
# 在图形和鼠标单击之间创建连接
fig.canvas.mpl_connect('button_press_event', onpress)
plt.show()
The feature vector has shape: (57600, 1)
The reshaped feature vector has shape: (40, 40, 2, 2, 9)
The average gradient array has shape: (41, 41, 9)
3 HOG算法实现
3.1 实现流程
HOG特征提取的大致流程
本文将通过hog特征来识别人像
通过HOG特征提取+SVM训练,可以得到很好的效果
2.2 实现代码
import cv2 as cv
# 主程序入口
if __name__ == '__main__':
# 读取图像
src = cv.imread("people.png")
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()
实现结果如下:
原图
结果
总结
HOG(Histogram of Oriented Gradients)特征算法是一种用于物体检测和识别的计算机视觉技术。HOG特征算法通过计算图像中局部区域的梯度方向直方图来描述图像的特征。这些直方图反映了图像的边缘和纹理信息,从而提供了对目标物体形状和结构的描述。
在OpenCV中,可以使用HOG特征算法来进行物体检测。通过调用相应的函数和参数设置,我们可以加载训练好的HOG模型,提取图像的HOG特征,并使用这些特征进行目标检测和识别。HOG特征算法在实际应用中具有广泛的应用领域,如行人检测、车辆检测等。
参考
https://blog.csdn.net/qq_37141382/article/details/89342081
https://zhuanlan.zhihu.com/p/75705284
https://www.cnblogs.com/alexme/p/11361563.html