第三章 人脸检测基础:Haar Cascade分类器原理、HOG特征与SVM检测器、OpenCV实现人脸检测
人脸检测,说白了就是让计算机在一张图里找到人脸的位置。别小看这一步,它是整个人脸识别系统的入口。如果检测都做不好,后面的识别、比对全是白搭。
我刚开始做这个方向时,踩过一个坑——用了一张分辨率极低的监控截图去跑检测,结果啥也没检测出来。后来才明白,不同场景下得选不同的检测方法。今天咱们就聊聊三种经典方案:Haar Cascade、HOG+SVM,以及OpenCV怎么落地。
3.1 Haar Cascade分类器:老祖宗的智慧
Haar Cascade是Viola和Jones在2001年提出的。你想想看,那会儿深度学习还没影呢,这方法就已经能实时检测人脸了。我个人习惯把它叫做「基于特征的级联分类器」。
3.1.1 Haar特征长什么样?
Haar特征其实就是一些黑白矩形模板。它们用来计算图像局部的明暗差异。比如眼睛区域通常比脸颊暗,鼻子区域比两侧亮——这些规律就是Haar特征要捕捉的。
常见的Haar特征有四种:
- 边缘特征:两个矩形,一黑一白,检测水平或垂直边缘
- 线特征:三个矩形,中间白两边黑,检测线条
- 中心特征:四个矩形,中心白周围黑,检测斑点
- 对角线特征:两个矩形对角排列
每个特征的计算方式很简单:白色区域像素和减去黑色区域像素和。嗯,这里要注意——一张24x24的图片,能生成超过16万个Haar特征。全算一遍?太慢了。
关键点:Haar Cascade用积分图加速特征计算。积分图能在常数时间内算出任意矩形区域的像素和。这是我见过最早的「空间换时间」经典案例。
3.1.2 级联结构:层层筛选
为什么叫「级联」?因为分类器是一层一层串起来的。每一层都是一个弱分类器,用AdaBoost训练得到。
检测流程是这样的:
- 图像窗口进入第一层,如果通过,进入第二层
- 如果某一层没通过,直接判定为非人脸,窗口丢弃
- 只有所有层都通过,才判定为人脸
我曾经在项目里遇到过一个问题:误检率太高。后来发现是级联层数设少了。默认的20层其实不够,我建议在资源允许的情况下,用30层以上。每多一层,误检率能降一个数量级。
避坑指南:我曾经把级联层数调到50层,结果检测速度慢得像蜗牛。后来发现前几层就能过滤掉90%的非人脸区域,后面层数太多反而浪费。经验值是20-30层最平衡。
3.2 HOG特征与SVM检测器:梯度里的秘密
Haar Cascade对光照变化比较敏感。如果你在户外或者光照不均的场景下做人脸检测,HOG+SVM会是更好的选择。
HOG的全称是Histogram of Oriented Gradients,方向梯度直方图。说白了,它统计图像局部区域的梯度方向分布。人脸边缘、轮廓这些信息,在梯度空间里特别明显。
3.2.1 HOG特征提取步骤
我习惯把HOG提取分成四步走:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 计算梯度 | 用Sobel算子算水平和垂直梯度 |
| 2 | 划分cell | 把图像分成8x8的小格子 |
| 3 | 统计直方图 | 每个cell统计9个方向的梯度幅值 |
| 4 | 块归一化 | 2x2个cell组成一个block,做L2归一化 |
你想想看,为什么要归一化?因为光照变化会影响梯度幅值。归一化之后,特征对光照就不那么敏感了。我在做安防项目时,白天黑夜的光照差异很大,HOG+SVM的表现明显优于Haar Cascade。
3.2.2 SVM分类器:找那条分界线
SVM(支持向量机)要做的事很简单:在特征空间里找一个超平面,把人脸和非人脸分开。而且它找的是「最大间隔」的那个超平面。
为什么最大间隔好?因为泛化能力强。我见过有人用线性SVM,也有人用RBF核的。说实话,人脸检测这个任务,线性SVM就够用了。RBF核虽然能拟合更复杂的边界,但容易过拟合。
注意:HOG特征维度很高。一张64x128的图片,HOG特征维度是3780。SVM训练时,数据量不够容易过拟合。我建议至少准备5000张正样本和10000张负样本。
3.3 OpenCV实现人脸检测:动手实战
理论说完了,咱们直接上代码。OpenCV把Haar Cascade和HOG+SVM都封装好了,用起来很方便。
3.3.1 Haar Cascade实战
OpenCV自带预训练的Haar Cascade模型文件。你只需要加载它,然后调用detectMultiScale方法。
import cv2
# 加载预训练模型
face_cascade = cv2.CascadeClassifier(
'haarcascade_frontalface_default.xml'
)
# 读取图像
img = cv2.imread('test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1, # 图像缩放比例
minNeighbors=5, # 最小邻居数
minSize=(30, 30) # 最小人脸尺寸
)
# 绘制检测结果
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow('Face Detection', img)
cv2.waitKey(0)
这里有几个参数我特别想强调:
- scaleFactor:每次缩放的比例。1.1表示每次缩小10%。值越小,检测越慢但越准。我一般用1.05到1.1之间。
- minNeighbors:每个候选框需要有多少个邻居框才能保留。值越大,误检越少,但可能漏检。5是个不错的起点。
- minSize:小于这个尺寸的框直接忽略。能过滤掉很多噪声。
个人经验:我曾经在视频流里做实时检测,发现scaleFactor设成1.05时,一帧要处理200ms。改成1.2后,速度提升到30ms,准确率只降了2%。实时场景下,速度优先。
3.3.2 HOG+SVM实战
OpenCV的HOGDescriptor类里内置了一个训练好的行人检测器。但人脸检测需要我们自己训练,或者用OpenCV提供的dnn模型。不过,OpenCV也提供了一个基于HOG的人脸检测器示例。
import cv2
# 初始化HOG描述符
hog = cv2.HOGDescriptor()
# 设置SVM检测器(使用默认的人脸检测器)
hog.setSVMDetector(
cv2.HOGDescriptor_getDefaultPeopleDetector()
)
# 读取图像
img = cv2.imread('test.jpg')
# 检测人脸
faces, weights = hog.detectMultiScale(
img,
winStride=(4, 4),
padding=(8, 8),
scale=1.05
)
# 绘制结果
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow('HOG Face Detection', img)
cv2.waitKey(0)
嗯,这里要注意——OpenCV自带的HOG检测器默认是行人检测器。如果你要做人脸检测,需要自己训练SVM模型。训练流程大致是:
- 收集正样本(人脸)和负样本(非人脸)
- 提取HOG特征
- 训练SVM分类器
- 保存模型并加载
核心对比:Haar Cascade速度快,适合正面人脸;HOG+SVM对光照鲁棒,适合侧面和复杂背景。我个人的选择标准是——移动端用Haar,服务器端用HOG+SVM。
3.4 两种方法的对比总结
最后,我用一张表把两种方法的核心差异列出来:
| 对比维度 | Haar Cascade | HOG+SVM |
|---|---|---|
| 特征类型 | 矩形明暗差异 | 梯度方向分布 |
| 分类器 | 级联AdaBoost | 线性SVM |
| 速度 | 快(实时) | 中等 |
| 光照鲁棒性 | 一般 | 较好 |
| 角度鲁棒性 | 正面为主 | 可处理侧面 |
| 训练难度 | 简单(有预训练模型) | 需要自己训练 |
说实话,这两种方法在深度学习时代已经不算最前沿了。但它们的价值在于——轻量、可解释、不需要GPU。很多嵌入式设备、边缘计算场景,至今还在用它们。我最近一个IoT项目,就是在树莓派上用Haar Cascade做检测,效果完全够用。
下一章,咱们会聊基于深度学习的人脸检测方法。到时候你会发现,深度学习虽然准,但代价也不小。怎么选?看场景。