查看全课大纲(第 24 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

行人检测的传统方法HOG+SVM

小象实战讲义 · 基于深度学习的计算机视觉

行人检测是计算机视觉中的一项基础任务,旨在从图像中定位出直立行走的人体。在深度学习兴起之前,HOG(方向梯度直方图)特征结合SVM(支持向量机)分类器是解决该问题的经典且有效的方法。本节我们将深入剖析HOG特征的构造原理,理解SVM如何利用这些特征进行“行人”与“非行人”的二分类,并回顾这一方法在计算机视觉发展史上的重要地位。

💡 核心导读

  • HOG特征:通过计算图像局部区域的梯度方向直方图,形成对物体边缘和形状的鲁棒描述。
  • 特征构造流程:从图像分块(Block和Cell)到梯度计算、直方图统计,再到对比度归一化,最终拼接成高维特征向量。
  • SVM分类器:在特征空间中寻找最优超平面,最大化正负样本间隔,实现对HOG特征的二分类。
  • 方法定位:理解HOG+SVM作为深度学习时代前的代表性方法,其核心思想对后续特征学习仍有启发意义。

图像梯度回顾与HOG特征思想

在介绍HOG之前,我们需要明确图像梯度的概念。图像中某一点的梯度是一个矢量,它指向该点灰度值变化最快的方向,其大小反映了变化的剧烈程度。

对于一个二维图像函数 f(x,y)f(x, y),其在点 (x,y)(x, y) 的梯度向量定义为: ∇f=[∂f∂x∂f∂y]=[GxGy]\nabla f = \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix} = \begin{bmatrix} G_x \\ G_y \end{bmatrix} 其中,GxG_x 和 GyG_y 分别是 xx 方向和 yy 方向上的偏导数(梯度分量)。

在实际的离散图像中,我们通常使用简单的差分模板来近似计算梯度:

  • 水平梯度 GxG_x: 用后一个像素值减去前一个像素值,即 f(x+1,y)−f(x−1,y)f(x+1, y) - f(x-1, y)。
  • 垂直梯度 GyG_y: 用下方像素值减去上方像素值,即 f(x,y+1)−f(x,y−1)f(x, y+1) - f(x, y-1)。

计算出两个方向的梯度分量后,我们可以进一步得到梯度的幅值(Magnitude) 和方向角(Orientation): 幅值:m(x,y)=Gx2+Gy2\text{幅值}: m(x, y) = \sqrt{G_x^2 + G_y^2} 方向角:θ(x,y)=arctan⁡(GyGx)\text{方向角}: \theta(x, y) = \arctan\left(\frac{G_y}{G_x}\right) 方向角的范围通常是 00 到 180180 度(无符号梯度),即忽略梯度的正负,只关心边缘的方向。

HOG特征的核心思想正是基于这种梯度信息:人体轮廓内外区域的梯度分布模式存在显著差异。通过统计图像局部区域内梯度方向的分布情况(即梯度方向直方图),可以构建出一个对光照变化和微小形变相对鲁棒的描述符,从而有效表征“行人”的视觉模式。

HOG特征描述符的构造流程

HOG特征的构造是一个系统化的过程,其目标是将一张图像转换成一个高维的特征向量。下面我们以经典的Dalal等人提出的用于行人检测的HOG参数为例,分步详解。

1. 预处理与梯度计算

首先,输入图像(通常是灰度图,彩色图可选取梯度幅值最大的通道或转为灰度)会进行伽马校正(Gamma Correction) 和颜色归一化,以部分消除光照影响。随后,计算图像中每个像素点的梯度幅值和方向角。注意:此步骤通常不进行平滑滤波,以避免模糊边缘、削弱梯度信息。

2. 划分细胞单元(Cell)与块(Block)

这是HOG特征构造的关键空间组织方式。

  • 细胞单元(Cell):将图像划分为小的连通区域,例如 8×8像素 为一个Cell。这是计算梯度直方图的基本单位。
  • 块(Block):由多个相邻的Cell组成一个更大的区域,例如 2×2个Cell 构成一个Block,即一个Block大小为 16×16像素。Block是进行对比度归一化的单位。
  • 重叠滑动:为了增强特征的鲁棒性,Block在图像上以步长(Stride)为8像素进行滑动。这意味着相邻的Block之间有50%的重叠区域。任何一个Cell都会被多个不同的Block所覆盖,其信息会多次贡献给最终的特征描述符,这有助于减轻局部光照和阴影的影响。
import numpy as np
import cv2

# 演示:手动计算一个小图像块的梯度(不使用cv2.HOGDescriptor)
# 1. 创建一个极小的5x5模拟灰度图像块
demo_cell = np.array([
    [10, 20, 30, 40, 50],
    [15, 25, 35, 45, 55],
    [20, 30, 40, 50, 60],
    [25, 35, 45, 55, 65],
    [30, 40, 50, 60, 70]
], dtype=np.float32)

print("模拟图像块 (5x5):")
print(demo_cell)
print()

# 2. 使用中心差分手动计算x和y方向的梯度
# 注意:这里为了演示清晰,使用最简单的中心差分;实际OpenCV的Sobel算子带有平滑效果。
# 计算内部点的梯度(忽略边界)
gx = np.zeros((3,3))
gy = np.zeros((3,3))
for i in range(1, 4):
    for j in range(1, 4):
        gx[i-1, j-1] = demo_cell[i, j+1] - demo_cell[i, j-1]  # 水平差分
        gy[i-1, j-1] = demo_cell[i+1, j] - demo_cell[i-1, j]  # 垂直差分

print("水平梯度 Gx (中心3x3区域):")
print(gx)
print()
print("垂直梯度 Gy (中心3x3区域):")
print(gy)
print()

# 3. 计算梯度幅值和方向角(角度转换为0-180度)
magnitude = np.sqrt(gx**2 + gy**2)
orientation = np.degrees(np.arctan2(gy, gx)) % 180  # arctan2范围(-180,180],%180转到[0,180)

print("梯度幅值 Magnitude:")
print(np.round(magnitude, 2))
print()
print("梯度方向角 Orientation (度):")
print(np.round(orientation, 2))
模拟图像块 (5x5):
[[10. 20. 30. 40. 50.]
 [15. 25. 35. 45. 55.]
 [20. 30. 40. 50. 60.]
 [25. 35. 45. 55. 65.]
 [30. 40. 50. 60. 70.]]

水平梯度 Gx (中心3x3区域):
[[20. 20. 20.]
 [20. 20. 20.]
 [20. 20. 20.]]

垂直梯度 Gy (中心3x3区域):
[[10. 10. 10.]
 [10. 10. 10.]
 [10. 10. 10.]]

梯度幅值 Magnitude:
[[22.36 22.36 22.36]
 [22.36 22.36 22.36]
 [22.36 22.36 22.36]]

梯度方向角 Orientation (度):
[[26.57 26.57 26.57]
 [26.57 26.57 26.57]
 [26.57 26.57 26.57]]

3. 计算Cell的梯度方向直方图

对于每个Cell(如8×8像素,共64个像素),我们为其构建一个梯度方向直方图:

  • 划分方向区间(Bins):将0-180度的方向范围均匀划分为9个区间(Bin),每个区间宽20度。
  • 投票统计:遍历Cell内的每个像素,根据其梯度方向角 θ\theta 和梯度幅值 mm 进行“投票”。
    • 幅值加权:该像素的投票权重是其梯度幅值 mm,而不仅仅是计数1。这使强边缘(大梯度)贡献更大。
    • 线性插值(沿方向角):为了减少量化误差,一个像素的梯度幅值会对其方向角相邻的两个Bin进行加权分配。例如,一个85度的梯度,离70度Bin差15度,离90度Bin差5度。那么,其幅值的 14\frac{1}{4} 累加到70度Bin,34\frac{3}{4} 累加到90度Bin。
  • 最终,每个Cell得到一个9维的直方图向量,描述了该局部区域梯度方向的分布。

4. Block内的对比度归一化

梯度幅值容易受到整体光照和前景-背景对比度的影响。为了提升特征的鲁棒性,需要对直方图进行对比度归一化。 归一化在Block级别进行。将一个Block内所有Cell的直方图向量(如2×2个Cell,共4×9=36维)拼接起来,形成一个大的向量 vv,然后对该向量进行归一化。常用的归一化方式有L2范数:v←v/∥v∥22+ϵv \leftarrow v / \sqrt{\Vert v\Vert _2^2 + \epsilon}。 由于Block是重叠滑动的,同一个Cell的直方图向量会参与多个不同Block的归一化过程,因此其信息被多次利用,这进一步增强了特征对光照变化的稳定性。

5. 拼接成最终的HOG特征向量

遍历整张图像中所有滑动位置的Block,将每个Block归一化后的直方图向量全部拼接起来,就得到了整张图像的HOG特征描述符。

以一个经典的128(高)×64(宽)像素的行人检测窗口为例:

  • 每个Block大小为16×16,步长为8。
  • 在高度方向:(128−16)/8+1=15(128-16)/8 + 1 = 15 个Block。
  • 在宽度方向:(64−16)/8+1=7(64-16)/8 + 1 = 7 个Block。
  • 总Block数:15×7=10515 \times 7 = 105 个。
  • 每个Block包含 2×2=42\times2=4 个Cell,每个Cell有9维直方图。
  • 最终HOG特征维度:105×4×9=3780105 \times 4 \times 9 = 3780 维。

这个3780维的向量就是用于描述该图像窗口是否包含行人的特征。

支持向量机(SVM)分类器原理

得到高维的HOG特征后,我们需要一个分类器来判断一个特征向量属于“行人”还是“非行人”。支持向量机(SVM)因其在解决小样本、非线性及高维模式识别问题中的优势,成为HOG特征的最佳搭档。

线性可分与最优超平面

SVM的基本模型是针对线性可分情况下的最优分类超平面提出的。假设我们有一组训练样本 {(xi,yi)}\lbrace (\mathbf{x}_i, y_i) \rbrace,其中 xi\mathbf{x}_i 是特征向量(如HOG特征),yi∈{−1,+1}y_i \in \lbrace -1, +1\rbrace 是类别标签(-1为非行人,+1为行人)。 线性可分的含义是,存在一个超平面 w⋅x+b=0\mathbf{w} \cdot \mathbf{x} + b = 0 能够将所有样本正确分类,即满足: yi(w⋅xi+b)≥1,∀iy_i (\mathbf{w} \cdot \mathbf{x}_i + b) \ge 1, \quad \forall i 其中,w\mathbf{w} 是超平面的法向量,bb 是偏置项。条件中等式成立的点(即 yi(w⋅xi+b)=1y_i (\mathbf{w} \cdot \mathbf{x}_i + b) = 1)被称为支持向量(Support Vectors),它们是与超平面距离最近的样本点。

这样的超平面可能有很多,SVM追求的是间隔(Margin)最大的那一个。间隔定义为两个平行于分类超平面且分别穿过正、负类支持向量的超平面 H1:w⋅x+b=1H_1: \mathbf{w} \cdot \mathbf{x} + b = 1 和 H2:w⋅x+b=−1H_2: \mathbf{w} \cdot \mathbf{x} + b = -1 之间的距离,其值为 2∥w∥\frac{2}{\Vert \mathbf{w}\Vert }。因此,最大化间隔等价于最小化 ∥w∥2\Vert \mathbf{w}\Vert ^2。

于是,线性SVM的优化问题可以表述为: min⁡w,b12∥w∥2s.t.yi(w⋅xi+b)≥1,i=1,…,n\begin{aligned} \min_{\mathbf{w}, b} \quad & \frac{1}{2} \Vert \mathbf{w}\Vert ^2 \\ \text{s.t.} \quad & y_i (\mathbf{w} \cdot \mathbf{x}_i + b) \ge 1, \quad i = 1, \dots, n \end{aligned} 这是一个凸二次规划问题,可以通过拉格朗日乘子法转化为对偶问题求解,从而高效地找到最优的 w\mathbf{w} 和 bb。

松弛变量与线性不可分

现实中的数据往往不是严格线性可分的,可能存在噪声或特异点。为此,SVM引入了松弛变量 ξi≥0\xi_i \ge 0,允许一些样本被错分或落在间隔之内。优化目标变为最小化 12∥w∥2+C∑i=1nξi\frac{1}{2} \Vert \mathbf{w}\Vert ^2 + C \sum_{i=1}^n \xi_i,约束条件变为 yi(w⋅xi+b)≥1−ξiy_i (\mathbf{w} \cdot \mathbf{x}_i + b) \ge 1 - \xi_i。其中,C>0C > 0 是一个惩罚参数,用于控制对错分样本的容忍度。CC 越大,模型越倾向于减少错分,间隔可能变小;CC 越小,则允许更多的错分以换取更大的间隔。

核函数与非线性映射

对于根本线性不可分的问题(例如,正负样本在原始特征空间中呈环形分布),SVM通过核技巧(Kernel Trick) 来处理。其核心思想是将原始特征 x\mathbf{x} 通过一个非线性映射 ϕ\phi 变换到一个更高维(甚至是无穷维)的特征空间 H\mathcal{H} 中,使得在 H\mathcal{H} 中样本变得线性可分。 我们不需要显式地计算高维空间中的 ϕ(x)\phi(\mathbf{x}),只需要定义原空间中的核函数(Kernel Function) K(xi,xj)=ϕ(xi)⋅ϕ(xj)K(\mathbf{x}_i, \mathbf{x}_j) = \phi(\mathbf{x}_i) \cdot \phi(\mathbf{x}_j)。常用的核函数包括多项式核、高斯径向基函数(RBF)核等。这样,SVM的决策函数可以写为: f(x)=sign(∑i=1nαiyiK(xi,x)+b)f(\mathbf{x}) = \text{sign}\left( \sum_{i=1}^n \alpha_i y_i K(\mathbf{x}_i, \mathbf{x}) + b \right) 其中 αi\alpha_i 是对偶问题的解。

在HOG行人检测的实践中,由于行人与非行人的HOG特征在特征空间中的分布通常具有良好的可分性,线性SVM往往就能取得很好的效果,这也避免了核函数选择与调参的复杂性。

HOG+SVM在行人检测中的应用

将HOG与SVM结合用于行人检测,是一个经典的“特征工程+机器学习”的管道(Pipeline):

  1. 训练阶段:

    • 收集大量正样本(包含行人的图像块,标准窗口为128×64,即高×宽)和负样本(不包含行人的背景图像块)。
    • 为每个样本计算其HOG特征,得到3780维的特征向量。
    • 使用这些带标签的HOG特征向量训练一个线性SVM分类器。SVM会学习到一个权重向量 w\mathbf{w} 和偏置 bb,在HOG特征空间中构造出最优分离超平面。
  2. 检测阶段:

    • 对于一张待检测图像,采用滑动窗口策略。用不同尺度的窗口在图像上滑动。
    • 对于每个窗口,提取其HOG特征。
    • 将HOG特征输入训练好的SVM分类器,计算决策函数值 f(x)=w⋅x+bf(\mathbf{x}) = \mathbf{w} \cdot \mathbf{x} + b。
    • 如果 f(x)f(\mathbf{x}) 大于某个阈值(通常为0),则判定该窗口包含行人。
    • 由于滑动窗口会产生大量重叠的检测框,最后需要应用非极大值抑制(NMS) 来合并重叠的检测结果,得到最终的行人位置。
import numpy as np

# 演示:模拟SVM对HOG特征的决策过程(概念性计算)
# 假设我们有一个简化版的3维HOG特征(实际为3780维),以及一个训练好的SVM权重。
np.random.seed(42)  # 固定随机种子,保证结果可复现

# 1. 模拟训练好的SVM权重向量 w 和偏置 b (随机生成用于演示)
w = np.array([0.5, -0.2, 0.8])  # 3维权重
b = -0.1

# 2. 模拟两个测试样本的HOG特征(3维简化版)
# 样本1:可能为行人
hog_feature_1 = np.array([1.2, 0.3, 0.9])
# 样本2:可能为非行人
hog_feature_2 = np.array([0.1, 1.5, -0.2])

print("SVM权重 w:", w)
print("偏置 b:", b)
print()

# 3. 计算决策函数值 f(x) = w·x + b
score_1 = np.dot(w, hog_feature_1) + b
score_2 = np.dot(w, hog_feature_2) + b

print("样本1 HOG特征:", hog_feature_1)
print("决策分数 score_1 =", round(score_1, 4))
print("分类结果 (阈值=0):", "行人" if score_1 > 0 else "非行人")
print()
print("样本2 HOG特征:", hog_feature_2)
print("决策分数 score_2 =", round(score_2, 4))
print("分类结果 (阈值=0):", "行人" if score_2 > 0 else "非行人")
SVM权重 w: [ 0.5 -0.2  0.8]
偏置 b: -0.1

样本1 HOG特征: [1.2 0.3 0.9]
决策分数 score_1 = 1.16
分类结果 (阈值=0): 行人

样本2 HOG特征: [ 0.1  1.5 -0.2]
决策分数 score_2 = -0.51
分类结果 (阈值=0): 非行人

📝 动手练一练

  1. 概念辨析:在HOG特征计算中,Block之间为什么要采用50%的重叠滑动?如果不重叠,可能会带来什么问题?
👉 点击查看参考答案

答案: 采用50%重叠滑动的主要原因是为了进行对比度归一化,以增强特征对光照和阴影的鲁棒性。

  • 归一化需要上下文:对一个Cell的梯度直方图进行归一化时,需要将其放在一个更大的局部上下文(即Block)中计算统计量。如果Block不重叠,那么每个Cell只属于一个Block,其归一化仅依赖于该Block内的局部对比度。若该Block恰好处于明暗交界处,其内部的对比度可能异常,导致归一化后的特征不稳定。
  • 信息多次利用与平滑:重叠滑动使得每个Cell被多个不同的Block所包含。因此,该Cell的梯度信息会参与多次(通常是4次)不同Block的归一化计算,并最终贡献到特征向量的不同位置。这相当于对每个Cell的特征进行了多次“投票”和平滑,有效降低了局部光照突变对最终特征描述符的影响。
  • 如果不重叠:特征对局部光照变化会更加敏感,模型泛化能力可能下降,在光照不均的场景下检测性能容易波动。
  1. 手算理解:假设一个Cell内只有一个像素,其梯度幅值为10,方向角为85度。HOG将0-180度分为9个bin(每20度一个)。请计算该像素的梯度幅值如何通过线性插值分配到相邻的bin中。
👉 点击查看参考答案

答案:

  • 0-180度被等分为9个宽度20度的bin,其边界为 0, 20, 40, 60, 80, 100, 120, 140, 160, 180 度;对应的中心为 10, 30, 50, 70, 90, 110, 130, 150, 170 度。两者不要混用。
  • 85度不在任何一个bin中心的整数刻度上,它落在中心为 70 度和 90 度两个相邻bin之间,距离 90 度中心更近(差 5 度),距离 70 度中心较远(差 15 度)。
  • 85度与70度bin中心相差15度,与90度bin中心相差5度。两中心间距为20度。
  • 因此,分配给90度bin的权重为 1−5/20=0.751 - 5/20 = 0.75,分配给70度bin的权重为 1−15/20=0.251 - 15/20 = 0.25。
  • 所以,幅值10的分配结果为:
    • 70度bin累加:10×0.25=2.510 \times 0.25 = 2.5
    • 90度bin累加:10×0.75=7.510 \times 0.75 = 7.5

通过这种插值方式,减少了因角度量化(硬分配到某一个bin)带来的信息损失,使直方图表示更加平滑和精确。

本章小结

本节我们深入探讨了深度学习时代之前行人检测的标杆性方法——HOG特征结合SVM分类器。我们首先从图像梯度这一基础概念出发,详细拆解了HOG描述符从细胞单元、块划分到梯度直方图统计与对比度归一化的完整构造流程。随后,我们阐述了支持向量机(SVM)的核心原理,包括最优超平面、间隔最大化、松弛变量处理线性不可分以及核函数映射至高维空间解决非线性问题的思想。最后,我们勾勒出HOG+SVM应用于行人检测的完整管道。这一方法体现了传统计算机视觉中“精心设计特征”+“高效统计学习”的经典范式,其关于局部梯度统计和特征归一化的思想,至今仍在某些特定场景和模型设计中有所体现。

需要客观说明的是:在今天的行人检测任务中,主流方案已是端到端的 CNN 检测器(如 YOLO、Faster R-CNN 系列),它们直接学习特征,精度和速度都远超 HOG+SVM。HOG+SVM 如今主要作为教学基线,以及 OpenCV HOGDescriptor 配合预训练线性 SVM 的轻量方案继续存在;它真正的价值在于把“梯度统计—局部归一化—线性分类”这条链路讲清楚,这正是理解深度特征的前置知识。

📋 行动清单

  • 尝试在脑海中复述HOG特征计算的五个关键步骤:梯度计算、划分Cell、统计Cell直方图、Block内归一化、拼接全图特征。
  • 理解SVM中“支持向量”的意义,并思考为何线性SVM就足以处理HOG特征分类问题。
  • 对比HOG+SVM与之前学过的Haar特征+级联分类器,思考两者在特征设计理念和应用场景上有何异同。

—— 小象教研组