【CV】HOG特征描述算子-行人检测

理论部分

HOG特征,即方向梯度直方图(Histogram of Oriented Gradients),第一次发表于2005年CVPR的会议上,在行人检测上取得了极大的成功,在图像手工特征提取方面具有里程碑式的意义。
HOG是基于本地像素块进行特征直方图提取的一种算法,对象局部的变形与光照影响有很好的稳定性,通过HOG特征提取+SVM训练,可以得到很好的效果,OpenCV已经有相应的接口。
在这里插入图片描述

HOG特征简介

HOG特征是一种图像局部特征,其基本思路是对图像局部的梯度幅值和方向进行投票统计,形成基于梯度特性的直方图,然后将局部特征拼接起来作为总特征。局部特征在这里指的是将图像划分为多个子块(Block), 每个Block内的特征进行联合以形成最终的特征。
HOG+SVM的工作流程如下:
在这里插入图片描述
首先对输入的图片进行预处理,然后计算像素点的梯度特特性,包括梯度幅值和梯度方向。然后投票统计形成梯度直方图,然后对blocks进行normalize,最后收集到HOG feature(其实是一行多维的vector)放到SVM里进行监督学习,从而实现行人的检测。下面我们将对上述HOG的主要步骤进行学习。

图像预处理

预处理包括灰度化Gamma变换
由于颜色信息作用不大,通常转化为灰度图。 对于彩色图像,将RGB分量转化成灰度图像,其转化公式为:
g r a y = 0.3 ∗ R + 0.59 ∗ G + 0.11 ∗ B gray=0.3*R+0.59*G+0.11*B gray=0.3R+0.59G+0.11B灰度处理是可选操作,因为灰度图像和彩色图像都可以用于计算梯度图。对于彩色图像,先对三通道颜色值分别计算梯度,然后取梯度值最大的那个作为该像素的梯度。
然后进行伽马矫正,调节图像对比度,减少光照对图像的影响(包括光照不均和局部阴影),使过曝或者欠曝的图像恢复正常,更接近人眼看到的图像。
伽马矫正公式: f ( I ) = I γ f(I)=I^\gamma f(I)=Iγ
I I I表示图像, γ \gamma γ表示幂指数。

注意这个 I I I 的取值范围为0~1。

在这里插入图片描述
如上图,当 γ \gamma γ取不同的值时对应的输入输出曲线( γ = 1 \gamma=1 γ=1时输入输出保持一致) :

  1. γ < 1 \gamma<1 γ<1时,输入图像的低灰度值区域动态范围变大,进而图像低灰度值区域对比度得以增强;在高灰度值区域,动态范围变小,进而图像高灰度值区域对比度得以降低。 最终,图像整体的灰度变亮。
  2. γ > 1 \gamma>1 γ>1时,输入图像的高灰度值区域动态范围变小,进而图像低灰度值区域对比度得以降低;在高灰度值区域,动态范围变大,进而图像高灰度值区域对比度得以增强。 最终,图像整体的灰度变暗。

所以,当图像光照不均匀时,可以通过Gamma校正,将图像整体亮度提高或降低。
伽马矫正的实现代码如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('*.png', 0)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img2 = np.power(img/float(np.max(img)),1/2.2)
plt.imshow(img2)
plt.axis('off')
plt.show()

计算图像梯度

为了得到梯度直方图,那么首先需要计算图像水平方向和垂直方向梯度。
一般使用特定的卷积核对图像滤波实现,可选用的卷积模板有:soble算子、Prewitt算子、Roberts模板等等。
一般采用soble算子,OpenCV也是如此,利用soble水平和垂直算子与输入图像卷积计算 d x dx dx d y dy dy
S o b e l X = [ 1 0 − 1 ] [ 1 2 1 ] = [ 1 2 1 0 0 0 − 1 − 2 − 1 ] Sobel_X=\left[ \begin{matrix} 1\\ 0 \\ -1 \end{matrix} \right] \left[ \begin{matrix} 1 & 2 & 1 \end{matrix} \right] = \left[ \begin{matrix} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{matrix} \right] SobelX=101[121]=101202101 S o b e l Y = [ 1 2 1 ] [ 1 0 − 1 ] = [ 1 0 − 1 2 0 − 2 1 0 − 1 ] Sobel_Y=\left[ \begin{matrix} 1 \\ 2 \\ 1 \end{matrix} \right] \left[ \begin{matrix} 1& 0 & -1 \end{matrix} \right] = \left[ \begin{matrix} 1 & 0 & -1 \\ 2 & 0 & -2 \\ 1 & 0 & -1 \end{matrix} \right] SobelY=121[101]=121000121
在这里插入图片描述
进一步可以得到图像梯度的幅值:
在这里插入图片描述
为了简化计算,幅值也可以作如下近似:
在这里插入图片描述
角度为:
在这里插入图片描述
这里需要注意的是:梯度方向和图像边缘方向是互相正交的。
在这里插入图片描述
其实现代码如下:

import cv2
import numpy as np
​
# Read image
img = cv2.imread('*.jpg')
img = np.float32(img) / 255.0  # 归一化# 计算x和y方向的梯度
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)# 计算合梯度的幅值和方向(角度)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

计算梯度直方图

经过上一步计算,每一个像素点都会有两个值:梯度幅值/梯度方向
在这一步中,图像被分成若干个8×8的cell,例如我们将图像resize至64x128的大小,那么这幅图像就被划分为8x16个8x8的cell单元,并为每个8×8的cell计算梯度直方图。当然,cell的划分也可以是其他值:16x16,8x16等,根据具体的场景确定。

在计算梯度直方图,让我们先了解一下为什么我们将图像分成若干个cell?
这是因为如果对一整张梯度图逐像素计算,其中的有效特征是非常稀疏的,不但运算量大,而且会受到一些噪声干扰。于是我们就使用局部特征描述符来表示一个更紧凑的特征。一个8x8的图像块包含8x8x3=192个像素值。一个8x8的Cell包含了8x8x2 = 128个值(每个像素包括梯度的大小和方向)。128个值将由9-bin的直方图(存储9个值的向量,想想坐标应该就明白了)。同时,计算这种局部cell上的梯度直方图更具鲁棒性

在HOG中,每个8x8的cell的梯度直方图本质是一个由9个数值组成的向量, 对应于0、20、40、60…160的梯度方向(角度)。那么原本cell中8x8x2 = 128个值就由长度为9的向量来表示,用这种梯度直方图的表示方法,大大降低了计算量,同时又对光照等环境变化更加地鲁棒。

首先,看下图:
在这里插入图片描述
左图是衣服64x128的图像,被划分为8x16个8x8的cell;中间的图像表示一个cell中的梯度矢量,箭头朝向代表梯度方向,箭头长度代表梯度大小;右图是 8×8 的cell中表示梯度的原始数值,注意角度的范围介于0到180度之间,而不是0到360度, 这被称为“无符号”梯度,因为两个完全相反的方向被认为是相同的。

接下来,我们来计算cell中像素的梯度直方图,将0-180度分成9等份,称为9个bins,分别是0,20,40…160。然后对每个bin中梯度的贡献进行统计:
在这里插入图片描述
统计方法是一种加权投票统计, 如上图所示,某像素的梯度幅值为13.6,方向为36,36度两侧的角度bin分别为20度和40度,那么就按一定加权比例分别在20度和40度对应的bin加上梯度值,加权公式为:

  • 20度对应的bin:((40-36)/20) * 13.6,分母的20表示20等份,而不是20度;
  • 40度对应的bin:((36-20)/20) * 13.6,分母的20表示20等份,而不是20度;

还有一个细节需要注意,如果某个像素的梯度角度大于160度,也就是在160度到180度之间,那么把这个像素对应的梯度值按比例分给0度和160度对应的bin。如左下图绿色圆圈中的角度为165度,幅值为85,则按照同样的加权方式将85分别加到0度和160度对应的bin中。
在这里插入图片描述
对整个cell进行投票统计,正是在HOG特征描述子中创建直方图的方式,最终得到由9个数值组成的向量—梯度方向图:
在这里插入图片描述

Block归一化

HOG特征将8×8的一个局部区域作为一个cell,再以2×2个cell作为一组,称为一个block,也就是说一个block表示16x16的区域。

我们可能会想,为什么又需要分block呢?
这是因为,虽然我们已经为图像的8×8单元创建了HOG特征,但是图像的梯度对整体光照很敏感,比如通过将所有像素值除以2来使图像变暗,那么梯度幅值将减小一半,因此直方图中的值也将减小一半。而对于某些特定的图像,图像的某些部分与其他部分相比会非常明亮,我们不能从图像中完全消除这个,但是我们可以通过使用16×16的block来对梯度进行归一化减少这种光照变化的影响

由于每个cell有9个值,一个block(2×2个cell)则有36个值,HOG是通过滑动窗口的方式来得到block的。

归一化的方法有很多:L1-norm、L2-norm、max/min等等,一般选择L2-norm。
v ← v ∣ ∣ v ∣ ∣ 2 2 + ξ 2 ,    ξ 为 一 个 很 小 的 数 , 防 止 分 母 为 0 v \leftarrow \frac v {\sqrt{||v||_2^2+\xi ^2}}, \ \ \xi为一个很小的数,防止分母为0 vv22+ξ2 v,  ξ0例如对于一个[128,64,32]的三维向量来说,模长是 12 8 2 + 6 4 2 + 3 2 2 = 146.64 \sqrt{128^2+64^2+32^2}=146.64 1282+642+322 =146.64,这叫做向量的L2范数。将这个向量的每个元素除以146.64就得到了归一化向量 [0.87, 0.43, 0.22]。现在有一个新向量,是第一个向量的2倍,也就是 [256, 128, 64],我们将这个向量进行归一化,你可以看到归一化后的结果与第一个向量归一化后的结果相同。所以,对向量进行归一化可以消除整体光照的影响。
采用同样的方法,一个cell有一个梯度方向直方图,包含9个数值,一个block有4个cell,那么一个block就有4个梯度方向直方图,将这4个直方图拼接成长度为36的向量,然后对这个向量进行归一化。
而每一个block将按照上图滑动的方式进行重复计算,直到整个图像的block都计算完成。归一化的块描述符叫做HOG描述子feature descriptor。

获得HOG特征

每一个16*16大小的block将会得到一个长度为36的特征向量,并进行归一化。 那会得到多少个特征向量呢?
例如,对于上图被划分8*16个cell ,每个block有2x2个cell的话,那么cell的个数为:(16-1)x(8-1)=105。即有7个水平block和15个竖直block。
每个block有36个值,整合所有block的特征值,最终获得由36*105=3780个特征值组成的特征描述符,而这个特征描述符是一个一维的向量,长度为3780。

获得HOG特征向量,就可以用来可视化和分类了。对于多维的HOG特征,SVM就可以排上用场了。

介绍一下Dalal等人的训练方法:

  1. 提取正负样本的HOG特征;
  2. 用正负样本训练一个初始的分类器,然后由分类器生产检测器;
  3. 然后用初始分类器在负样本原图上进行行人检测,检测出来的矩形区域自然都是分类错误的负样本,这就是所谓的难例(hard examples);
  4. 提取难例的HOG特征并结合第一步中的特征,重新训练,生成最终的检测器 ;

这种二次训练的处理过程显著提高了每个检测器的表现,一般可以使得每个窗口的误报率(FPPW False Positives Per Window)下降5%。

使用HOG特征数据

HOG特征本身是不支持旋转不变性与多尺度检测的,但是通过构建高斯金字塔实现多尺度的开窗检测就会得到不同分辨率的多尺度检测支持,如下图所示。详细内容可参考尺度空间理论
在这里插入图片描述
OpenCV中HOG多尺度对象检测API如下:

virtual void cv::HOGDescriptor::detectMultiScale(
    InputArray  img,
    std::vector< Rect > &   foundLocations,
    double  hitThreshold = 0,
    Size    winStride = Size(),
    Size    padding = Size(),
    double  scale = 1.05,
    double  finalThreshold = 2.0,
    bool    useMeanshiftGrouping = false 
)
Img-表示输入图像
foundLocations-表示发现对象矩形框
hitThreshold-表示SVM距离度量,默认0表示,表示特征与SVM分类超平面之间
winStride-表示窗口步长
padding-表示填充
scale-表示尺度空间
finalThreshold-最终阈值,默认为2.0
useMeanshiftGrouping-不建议使用,速度太慢拉

Haar特征描述算子-人脸检测这一节我们利用haar特征和级联分类器Adaboost检测人脸时我们使用过detectMultiScale()函数,级联分类器对象尝试在输入图像的不同尺度下检测对象,该函数有一个比较重要的参数scaleFactor(一般设置为1.3),表示一个比率:即在每层金字塔中所获得的图像与上一层图像的比率,scaleFactor越小,金字塔的层数就越多,计算就越慢,计算量也会更大,但是计算结果相对更精确。

总结

HOG算法具有以下优点

  • HOG描述的是边缘结构特征,可以描述物体的结构信息
  • 对整体光照、亮度等影响不敏感
  • 分块的处理可以使特征得到更为紧凑的表示

HOG算法具有以下缺点

  • 特征描述子获取过程复杂,维数较高,导致实时性差
  • 遮挡问题很难处理,,人体姿势动作幅度过大或物体方向改变也不易检测(这个问题后来在DPM中采用可变形部件模型的方法得到了改善);
  • 跟SIFT相比,HOG没有选取主方向,也没有旋转梯度方向直方图,因而本身不具有旋转不变性(较大的方向变化),其旋转不变性是通过采用不同旋转方向的训练样本来实现的;
  • 跟SIFT相比,HOG本身不具有尺度不变性,其尺度不变性是通过缩放检测窗口图像的大小来实现的;
  • 由于梯度的性质,HOG对噪点相当敏感,在实际应用中,在block和Cell划分之后,对于得到各个区域,有时候还会做一次高斯平滑去除噪点。

实践部分

行人检测

基于OpenCV的实现代码如下:

import cv2 as cv

if __name__ == '__main__':
    src = cv.imread("Human Detection.jpg")
    cv.imshow("input", src)

    hog = cv.HOGDescriptor()
    hog.setSVMDetector(cv.HOGDescriptor_getDefaultPeopleDetector())
    # Detect people in the image
    (rects, weights) = hog.detectMultiScale(src,
                                            winStride=(2, 4),
                                            padding=(8, 8),
                                            scale=1.1,
                                            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.imwrite("hog-detector.jpg", src)
    cv.waitKey(0)
    cv.destroyAllWindows()

检测结果:
在这里插入图片描述

可视化

代码如下:

from skimage import feature, exposure
import cv2 as cv

image = cv.imread('Human Detection.jpg')
image = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

fd, hog_image = feature.hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), visualize=True)

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

cv.namedWindow("img", cv.WINDOW_NORMAL)
cv.imshow('img', image)
cv.namedWindow("hog", cv.WINDOW_NORMAL)
cv.imshow('hog', hog_image_rescaled)

# 在此之前得到的hog_image_rescaled在0.0到1.0之间,此处乘以255.0
hog_image_rescaled = 255.0 * hog_image_rescaled
cv.imwrite('edge_hog.jpg', hog_image_rescaled)
cv.waitKey(0)

feature.log函数:

  • image:可以是灰度图或者彩色图;
  • orientations:就是把180度分成几份,也就是bin的数量;
  • pixels_per_cell:一个Cell里包含的像素个数;
  • cells_per_block:一个block包含的Cell个数;
  • visualize:是否返回一个hog图像用于显示,下面会显示这张图;

可视化结果:
在这里插入图片描述

参考资料

  1. https://github.com/datawhalechina/team-learning/tree/master/03%20%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89
  2. https://sulimin-nb.github.io/article/760e3967.html
  3. https://blog.csdn.net/hzy459176895/article/details/107145116
  4. https://www.cnblogs.com/recoverableTi/p/13246058.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值