--

用于目标检测的 HOG -- histogram of oriented gradients



HOG wiki
HOG(梯度方向直方图)可以用来检测物体的轮廓,对于我们识别一个目标物体是非常有用的。

HOG 的计算通过以下步骤实现:

1. (可选的) 对图像做全局的归一化 (normalisation)
2. 像素在计算 x 和 y 轴上的梯度
3. 计算在一个 cell 级别上的 梯度直方图 (gradient histograms)
4. 在 block 级别做归一化 (normalization)
5. 展开成为一个特征向量 (feature vector)


第一步的归一化,可以去除光照强度对图像的影响。

第二步用来计算像素的一阶导数(梯度),能够探测到物体的轮廓、纹理以及文字等信息。 也可以采用二阶的导数,这样就能够探测到一个条状的物体,比如自行车上面的辐条、人的四肢等。

第三步把局部的梯度方向信息投影到直方图的 bin 区间,图形被划分成一个个的 cell 大小,统计每个 cell 内做直方图分布。 我们还考虑梯度的大小用来产生直方图。

第四步利用周围的 block 级别的区域(每个 block 往往包含了多个 cell)进行归一化。
每一个cell 的归一化因子是它周围的cell 组成的block的值,所以会发现在每个cell 会归到不同的block 中去计算,多有重复。 不过 block 级别的normalization 有助于去除光照强度、阴影等对目标检测的影响。

第五步我们需要把生成的 HOG 数据展开成一个向量作为分类器的输入。


这也是 Dalal, N. and Triggs, B., 在 “Histograms of Oriented Gradients for Human Detection,” IEEE Computer Society Conference on Computer Vision and Pattern Recognition, 2005, San Diego, CA, USA. 当中的算法。

skimage 提供了 hog 的接口,让我们能够很容易的调用。 skimage hog

  
skimage.feature.hog(img, 
    orientations = 9, 
    pixels_per_cell = (8, 8), 
    cells_per_block = (3, 3), 
    block_norm = None, 
    visualize = False, 
    visualise = None, 
    transform_sqrt = False, 
    feature_vector = True, 
    multichannel = None)


这里需要关注的是:

orientations:
把 360° 的方向分成多少个 bin,一般来说取 6 - 12 之间。
这也是生成的方向的 histogram 的 bin 的大小。


pixels_per_cell:

表示我们统计一个 histogram 的 cell 大小,每个 cell 会生成一个自己的 hitogram,histogram 的 bin 的数量就是 orientations。
pixels_per_cell = (x, y),其中 x 和 y 可以有不同的选值,一般会选择相同的值。

cells_per_block:
每个 block 里面包含多少个 cell, block 是用来做normalization 的。

visualize:
若为 True,返回 feature_vector 和 hog_img
若为 False,返回 feature_vector

我们来计算一下,image 大小是 64 * 64,
pixels_per_cell = (8, 8)
cells_per_block = (2, 3)
最后展成的向量长度是多少?

很容易我们会认为是 8*8*9, 然而并不是。

我们应该这么计算:
block 按照 cell 进行滑动,在图上会产生 7 * 7 个 block
每个block 里面包含有 2 * 2 的cell,每个cell 的histogram 有9 个bin,
所以总的维度是 7 * 7 * 2 * 2 * 9



import matplotlib.pyplot as plt

from skimage.feature import hog
from skimage import data, exposure


image = data.astronaut()[:,:,0]
print('image shape = ', image.shape)

fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
                    cells_per_block=(2, 2), visualise =True)

print('fd shape = ', fd.shape)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True)

ax1.axis('off')
ax1.imshow(image, cmap=plt.cm.gray)
ax1.set_title('Input image')

# Rescale histogram for better display
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))

ax2.axis('off')
ax2.imshow(hog_image_rescaled, cmap=plt.cm.gray)
ax2.set_title('Histogram of Oriented Gradients')
plt.show()




下面再来一点代码上的。

我们用了一个分类器,比方说,用了一个 SVM,然后滑动窗口把图像上窗口的像素做好标注之后, 可以:

1. 设定一个阈值,把小于一定值的像素的点重设为 0

imgcpy = np.copy(img)
for window in windows:
    (x1, y1), (x2, y2) = window
    imgcpy[x1:x2][y1:y2] += 1
heatmap = np.copy(imgcpy)
heatmap[imgcpy < threshold] = 0


2. 找到那些有连续在一起的像素点,认为是找到的同一个object, 然后用框框把这些object 框起来。

from scipy.ndimage.measurements import label
labels = label(heatmap)
# labels[0] is the labeld object map
# labels[1] is the object count

obj_cnt = labels[1]
for obj_idx in range(1, obj_cnt +1):
    nonzero = (labels[0] == obj_idx).nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    bbox = ((np.min(nonzerox), np.min(nonzeroy)), (np.max(nonzerox), np.max(nonzeroy)))
    cv2.rectangle(img, bbox[0], bbox[1], (0,0,255), 6)