第三章 人脸检测基础: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训练得到。

检测流程是这样的:

  1. 图像窗口进入第一层,如果通过,进入第二层
  2. 如果某一层没通过,直接判定为非人脸,窗口丢弃
  3. 只有所有层都通过,才判定为人脸

我曾经在项目里遇到过一个问题:误检率太高。后来发现是级联层数设少了。默认的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模型。训练流程大致是:

  1. 收集正样本(人脸)和负样本(非人脸)
  2. 提取HOG特征
  3. 训练SVM分类器
  4. 保存模型并加载

核心对比:Haar Cascade速度快,适合正面人脸;HOG+SVM对光照鲁棒,适合侧面和复杂背景。我个人的选择标准是——移动端用Haar,服务器端用HOG+SVM。

3.4 两种方法的对比总结

最后,我用一张表把两种方法的核心差异列出来:

对比维度 Haar Cascade HOG+SVM
特征类型 矩形明暗差异 梯度方向分布
分类器 级联AdaBoost 线性SVM
速度 快(实时) 中等
光照鲁棒性 一般 较好
角度鲁棒性 正面为主 可处理侧面
训练难度 简单(有预训练模型) 需要自己训练

说实话,这两种方法在深度学习时代已经不算最前沿了。但它们的价值在于——轻量、可解释、不需要GPU。很多嵌入式设备、边缘计算场景,至今还在用它们。我最近一个IoT项目,就是在树莓派上用Haar Cascade做检测,效果完全够用。

下一章,咱们会聊基于深度学习的人脸检测方法。到时候你会发现,深度学习虽然准,但代价也不小。怎么选?看场景。