来源:Datawhale
支持向量机(Support Vector Machine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。
正是由于SVM具有很多独特的优势,基于SVM分类器在很多时候都具有较好的拟合作用。本文对SVM算法在行人检测项目实践中的应用进行详细讲解,同时给出调用OpenCV分类器及可视化的代码实现,便于大家学习实践。
目前的行人检测基本上都是基于法国研究人员Dalal在2005的CVPR发表的HOG+SVM的行人检测算法(Histograms of Oriented Gradients for Human Detection, Navneet Dalel,Bill Triggs, CVPR2005)。HOG+SVM作为经典算法也集成到OpenCV里面去了,可以直接调用实现行人检测。
1. HOG特征描述符
1.1. 主要思想
1.2. 微观
1.3. 宏观(硬核)
1.4. HOG算法优缺点
2. HOG特征的原理
2.1. 图形预处理
2.2. 计算图像梯度
2.3. 计算梯度直方图
2.4. Block归一化
2.5. 获得HOG描述子
2.6. 使用HOG特征数据
3. 基于OpenCV的简单实现
3.1. 行人检测
3.2. 可视化
一、HOG特征描述符
HOG(Histogram of Oriented Gradients)HOG特征在对象检测与模式匹配中是一种常见的特征提取技术(深度学习之前),是基于本地像素块进行特征直方图提取的一种算法,对像局部的变形与光照影响有很好的稳定性,最初是用HOG特征来识别人像,通过HOG特征提取+SVM训练,可以得到很好的效果,OpenCV已经有相应的接口。
方向梯度直方图(HOG)特征描述符常和线性支持向量机(SVM)配合使用,用于训练高精度的目标分类器。
首先对输入的图片进行预处理,然后计算像素点的梯度特性,包括梯度幅值和梯度方向。然后投票统计形成梯度直方图,然后对blocks进行normalize,最后收集到检测窗口的HOG feature(一行多维的vector)放入SVM里进行监督学习,实现行人的检测。接下来对上述HOG的主要步骤进行学习。
检测窗口在整个图像的所有位置和尺度进行扫描,并对输出的金字塔进行非极大值抑制来检测目标(检测窗口的大小一般为128x64)
核心思想是所检测的局部物体外形能够被梯度或边缘方向的分布所描述,HOG能较好地捕捉局部形状信息,对几何和光学变化都有很好的不变性;
HOG是在密集采样的图像块中求取的,在计算得到的HOG特征向量中隐含了该块与检测窗口之间的空间位置关系。
特征描述子获取过程复杂,维数较高,导致实时性差;
很难处理遮挡问题,人体姿势动作幅度过大或物体方向改变也不易检测(这个问题后来在DPM中采用可变形部件模型的方法得到了改善);
跟SIFT相比,HOG没有选取主方向,也没有旋转梯度方向直方图,因而本身不具有旋转不变性(较大的方向变化),其旋转不变性是通过采用不同旋转方向的训练样本来实现的;
跟SIFT相比,HOG本身不具有尺度不变性,其尺度不变性是通过缩放检测窗口图像的大小来实现的;
此外,由于梯度的性质,HOG对噪点相当敏感,在实际应用中,在block和Cell划分之后,对于得到各个区域,有时候还会做一次高斯平滑去除噪点。
2.1 图形预处理
代码:
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()
放图,左图是 ,中图是 ,右图是 :
为了得到梯度直方图,那么首先需要计算图像水平方向和垂直方向梯度。可以通过使用以下内核过滤图像实现,分别用于计算水平梯度和垂直梯度。
可以使用内核大小为1的sobel算子获取相同结果,OpenCV也是如此。
进一步得到图像梯度的幅值:
简化计算,幅值也可以做近似:
图像梯度的方向:
这里需要注意的是:梯度方向和图像边缘方向是互相正交的。
代码:
mport 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)
下图展示了梯度:
第一个图:x-梯度的绝对值,第二个图:y梯度的绝对值 ,第三个图:梯度的幅值,第四个图:角度。
注意到,x-梯度在垂直线触发,y-梯度在水平线触发。梯度的幅值在有密集的剧烈改变时触发。当区域很平缓时,梯度没有明显变化。梯度图除去了很多不必要的信息(例如有颜色的背景),强调凸显线条。当你看到梯度图像,很容易想到这张图片有一个人。
在每个像素点,梯度有一个幅值和方向。对于有颜色的图像,计算三通道的梯度(如上图所示)。一个像素点的梯度的幅值是三通道中梯度幅值最大的值,角度也是最大梯度对应的角度。
2.3 计算梯度直方图
在这一步中,图像被分成若干个8×8的Cell,如下图所示,例如我们将图像resize至64x128的大小,那么这幅图像就被划分为8x16个8x8的Cell单元,并为每个8×8的Cell计算梯度直方图。当然,Cell的划分也可以是其他值:16x16,8x16等,根据具体的场景确定。
如下图所示,左图是衣服64x128的图像,被划分为8x16个8x8的Cell;中间的图像表示一个Cell中的梯度矢量,箭头朝向代表梯度方向,箭头长度代表梯度大小。
再比如:(如上图所示)某像素的梯度幅值为13.6,方向为36,36度两侧的角度bin分别为20度和40度,那么按一定加权比例分别在20度和40度对应的bin加上梯度值,加权公式为:
20度对应的bin:((40-36)/20) x13.6,分母的20表示20等份,其中4份给20度对应的bin;
40度对应的bin:((36-20)/20) x13.6,分母的20表示20等份,其中16份给20度对应的bin;
对整个Cell进行投票统计,最终得到9-bin直方图:
可以看到直方图中,0度和160附近有很大的权重,说明了大多数像素的梯度向上或者向下,也就是这个Cell是个横向边缘。
HOG特征将8×8的一个局部区域作为一个Cell,再以2×2个Cell作为一组,称为一个block,也就是说一个block表示16x16的区域。
由于每个Cell有9个值,一个block(2×2个Cell)则有36个值,HOG是通过滑动窗口的方式来得到block的,如下图所示:
接下来对Block进行归一化。(再再再一次强调,归一化的目的是为了降低光照/迁移的影响):
归一化的方法有很多:L1-norm、L2-norm、max/min等等,一般选择L2-norm。
例如对于一个[128,64,32]的三维向量来说,模长是:
对于上图被划分8 x16个Cell ,每个block有2x2个Cell的话,那么Cell的个数为:(8-1)x(16-1)=105。
每个16x16 block由36x1维向量,合并所有105个block的特征,最终得到由36 x105=3780维向量表示的特征描述符。
获得HOG特征向量,就可以用来可视化和分类了。对于多维的HOG特征,SVM就可以排上用场了。
介绍以下Dalal等人的训练方法:
提取正负样本的HOG特征;
用正负样本训练一个初始的分类器,然后由分类器生产检测器;
然后用初始分类器在负样本原图上进行行人检测,检测出来的矩形区域自然都是分类错误的负样本,这就是所谓的难例(hard examples);
提取难例的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-不建议使用,速度太慢拉
代码:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
if __name__ == '__main__':
src = cv.imread("*.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.2,
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()
待检测图片:
检测图片(有点不完美,调参调不动了,先酱~):
3.2 可视化
feature.log函数:
image:可以是灰度图或者彩色图;
orientations:就是把180度分成几份,也就是bin的数量;
pixels_per_Cell:一个Cell里包含的像素个数;
Cells_per_block:一个block包含的Cell个数;
visualize:是否返回一个hog图像用于显示,下面会显示这张图;
from skimage import feature, exposure
import cv2
image = cv2.imread('hog.jpg')
fd, hog_image = feature.hog(image, orientations=9, pixels_per_Cell=(16, 16),
Cells_per_block=(2, 2), visualize=True)
# Rescale histogram for better display
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))
cv2.imshow('img', image)
cv2.imshow('hog', hog_image_rescaled)
hog_image_rescaled = 255.0 * hog_image_rescaled
cv2.imwrite('edge_hog.jpg', hog_image_rescaled)
cv2.waitKey(0)==ord('q')
——END——