4、人脸检测基础:Haar Cascade分类器原理、OpenCV实现人脸检测、检测参数调优、多尺度检测
人脸检测,说白了就是让计算机在一张图里找到人脸的位置。它是整个人脸识别系统的第一步,也是最关键的一步。你想想看,如果连脸都找不到,后面还谈什么识别?
我最早接触人脸检测是在2015年,那时候深度学习还没像现在这么普及。当时用的最多的就是Haar Cascade。虽然现在有了MTCNN、RetinaFace这些更强大的方法,但Haar Cascade依然有它的用武之地——轻量、快速、不需要GPU。在一些嵌入式设备上,它还是首选。
4.1 Haar Cascade分类器原理
Haar Cascade的核心思想其实很简单:用一些简单的矩形特征来区分人脸和非人脸。这些特征就是Haar-like特征,也叫矩形特征。
常见的Haar特征有三种:
- 边缘特征: 检测明暗变化明显的区域,比如眼睛和脸颊的边界
- 线特征: 检测细长的明暗条纹,比如鼻梁
- 中心特征: 检测中心与周围对比明显的区域,比如眼睛比周围暗
为什么这些特征能检测人脸?因为人脸有一些固定的明暗规律。比如:
- 眼睛区域比脸颊暗
- 鼻梁比两侧亮
- 嘴巴区域比周围暗
Haar特征就是通过计算白色矩形和黑色矩形内像素和的差值,来捕捉这些规律。
但光有特征还不够。一张24x24的图片,Haar特征的数量能达到16万多个。如果每个特征都去计算,速度会慢得离谱。这时候就需要AdaBoost算法出场了。
AdaBoost的核心思想是:从海量特征中挑选出最有区分力的那些,组合成一个强分类器。具体来说:
- 给每个训练样本一个权重
- 训练一个弱分类器(其实就是选一个Haar特征+一个阈值)
- 计算这个弱分类器的错误率
- 增加被错分样本的权重,降低正确样本的权重
- 重复步骤2-4,直到达到预设的弱分类器数量
- 把这些弱分类器加权组合成一个强分类器
但即使这样,直接用一个强分类器去扫描整张图片,速度还是不够。Viola和Jones提出了一个巧妙的方法——级联结构。
级联结构就像是一个层层筛选的漏斗:
- 前几层用很少的特征快速排除明显不是人脸的区域
- 后几层用更多的特征精细判断
- 只有通过所有层的区域才被认为是人脸
这样做的好处是:大部分非人脸区域在前几层就被排除了,只有少数候选区域需要经过全部层的判断。速度提升了10倍以上。
4.2 OpenCV实现人脸检测
OpenCV里已经训练好了Haar Cascade模型,我们直接拿来用就行。模型文件在OpenCV的安装目录下,常见的几个:
| 模型文件 | 检测目标 | 特点 |
|---|---|---|
| haarcascade_frontalface_default.xml | 正面人脸 | 最常用,检测正面效果不错 |
| haarcascade_frontalface_alt.xml | 正面人脸 | 比default更严格,误检率低 |
| haarcascade_profileface.xml | 侧面人脸 | 检测侧脸,但效果一般 |
| haarcascade_eye.xml | 眼睛 | 可做人眼检测 |
基本的使用流程是这样的:
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)
这段代码看起来简单,但有几个关键点需要注意:
- 转灰度图: Haar特征是基于灰度计算的,所以要先转灰度
- detectMultiScale: 这个函数名里的MultiScale就是多尺度检测的意思
- 返回结果: 返回的是人脸矩形框的列表,每个框是(x, y, w, h)
gray = cv2.equalizeHist(gray)。效果很明显。
4.3 检测参数调优
detectMultiScale有四个关键参数,调好了效果天差地别。我一个个说。
| 参数 | 作用 | 推荐值 | 调大/调小的影响 |
|---|---|---|---|
| scaleFactor | 图像缩放比例 | 1.05 - 1.2 | 调大:速度快,但可能漏检;调小:检测更精细,但速度慢 |
| minNeighbors | 最小邻居数 | 3 - 6 | 调大:误检率低,但可能漏检;调小:检测更多,但误检多 |
| minSize | 最小人脸尺寸 | (30, 30) | 调大:忽略小脸,速度快;调小:能检测小脸,但误检增加 |
| maxSize | 最大人脸尺寸 | 不设置 | 一般不用设置,除非你知道人脸最大多大 |
scaleFactor 这个参数,我刚开始用的时候总搞不明白。它控制的是图像金字塔的缩放比例。每次缩放后,用固定大小的检测窗口去扫描。scaleFactor=1.1表示每次缩小10%。
为什么要有这个参数?因为人脸在图片里大小不一。通过缩放图片,让检测窗口能匹配到不同大小的人脸。
minNeighbors 这个参数也很关键。它控制的是:一个候选区域周围有多少个其他检测框,才认为它是真正的人脸。
你想想看,如果minNeighbors设成1,那只要有一个检测框就算人脸。这样误检会很多。设成5,就需要周围有5个框都检测到才算。误检少了,但可能漏掉一些真实的人脸。
我一般这样调:
- 如果误检多,增大minNeighbors
- 如果漏检多,减小minNeighbors
- 通常3-6之间比较合适
4.4 多尺度检测
多尺度检测,说白了就是解决一个问题:人脸在图片里大小不一样。
比如一张合影,站在前面的人脸大,后面的人脸小。如果用固定大小的检测窗口,大脸能检测到,小脸就漏了。
OpenCV的做法是构建图像金字塔:
- 把原图按scaleFactor逐级缩小
- 对每一级缩放后的图片,用固定大小的检测窗口扫描
- 把所有检测结果合并起来
这样做的好处是:不管人脸在图片里多大,总有一级金字塔能匹配上。
但代价也很明显——计算量大了。金字塔层数越多,需要处理的图片就越多。
- 如果知道人脸的大致大小,可以设置minSize和maxSize,减少金字塔层数
- scaleFactor不要太小,1.1-1.2之间比较合理
- 可以先缩小原图再检测,比如把1080p缩到720p
我做过一个测试:一张1920x1080的图片,scaleFactor=1.1时,检测耗时约200ms;scaleFactor=1.2时,耗时约120ms。速度提升了40%,但漏检率从2%升到了5%。
所以,调参就是一个权衡的过程。没有完美的参数,只有适合你场景的参数。
最后说一句,Haar Cascade虽然老了,但它的思想——用简单特征+级联结构实现快速检测——至今仍有借鉴意义。理解了它,你再去看YOLO、SSD这些现代检测方法,会发现很多相似之处。