用 Haar 级联找脸:先掰正语义——OpenCV 级联做的是【人脸检测】(这里有一张脸),不是【人脸识别】(这是谁的脸);十行代码让摄像头在画面里框出人脸。
11-3 的基本功(灰度、数组、画框用的坐标规矩)在这里合体:转灰度 → 级联检测 → 画框,三个动作各是学过的一把刀。11-1 的环境、10-4 的 picamera2 供帧继续当基建。这也是全课第一次【模型文件】登场——它在硬盘上、由 OpenCV 读进内存跑推理,思路与 11-5 的 YOLO 一脉相承。
一、先掰正语义:检测 ≠ 识别
教材标题说【人脸识别】,但 OpenCV 级联干的事严格说是人脸检测(face detection):
- 检测:回答【画面里有没有脸、在哪】——输出一个或几个框(本节内容)
- 识别:回答【这张脸是谁】——需要先拿很多人的照片训练出身份模型(另一套技术,LBP/深度学习人脸网络,超出本节)
为什么要掰正:所有报错排查、所有进阶资料的搜索词都依赖这个区分;拿【识别】的教程去解决【检测】的问题,方向就错了。同 8-4 掰正【用 Python 开发 Arduino】一样:语义不清,后面的坑全讲不通。
二、Haar 级联:二十岁的老算法怎么找脸
Haar 级联分类器(2001 年提出)的原理一句话:脸有稳定的亮暗模式(眼窝比脸颊暗、鼻梁比两侧亮),算法用几百个小【亮暗模板】在图片的各个位置、各个大小上滑动比对,命中足够多就报【这里有脸】。
三个要点:
- 训练成果是一个 XML 文件(如
haarcascade_frontalface_default.xml):里面是上百层模板特征与阈值——模型文件就是一张特征表,OpenCV 读它来比对 - 级联 = 层层筛人:前几层用最便宜的特征快速排除【肯定不是脸】的窗口,剩下少数候选再过后面更细的层——所以够快,树莓派跑得动
- 只认【正脸】:
frontalface模型对侧脸、大角度歪头效果差——检测不到不一定是 bug,可能是脸没【正】对镜头
三、模型文件在哪:路径的正确打开方式
OpenCV 自带十几个训练好的级联文件(人脸、眼睛、猫脸、车身……)。找它们的正确姿势:
import cv2
print(cv2.data.haarcascades)
# apt 安装版通常输出:/usr/share/opencv4/haarcascades/
# 终端确认文件真实存在
ls /usr/share/opencv4/haarcascades/ | grep frontal
坑在哪:老教程常硬编码一个绝对路径(/usr/share/opencv/haarcascades/...),不同系统、不同安装方式路径千差万别——一律改用 cv2.data.haarcascades 拼接文件名,OpenCV 自己知道数据在哪。加载后用 .empty() 判断是否加载成功(路径错时它不报异常,只安静地返回空分类器)。
四、核心 API:detectMultiScale 三参数
faces = face_cascade.detectMultiScale(
gray, # 输入:灰度图(11-3 的第一把刀)
scaleFactor=1.1, # 每轮把搜索窗口放大 10%(图里远近不同的脸各归各档)
minNeighbors=5, # 至少 5 个邻居窗口都喊【是脸】才算数——越大越严、误报越少
minSize=(30, 30) # 比 30×30 像素还小的目标不找——过滤远处噪声
)
# 返回 [[x, y, w, h], ...]:每个脸框的左上角坐标与宽高
返回值直接能画框:cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)——绿色(BGR 的 0,255,0!)粗 2 像素。
调参手感:乱报(没有脸也画框)→ 加大 minNeighbors;漏报(有脸不画)→ 减小它或降低 minSize。检测是概率游戏,参数在【严】与【松】之间找平衡。
五、动手 Demo:照片版与实时版
照片版(拿 10-3 拍的 snap.jpg 或自备一张带正脸的照片):
# face_photo.py
import cv2
cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
if cascade.empty():
raise SystemExit("级联文件加载失败,先查 cv2.data.haarcascades 路径")
img = cv2.imread("snap.jpg")
if img is None:
raise SystemExit("照片没读到,检查路径(imread 失败返回 None,11-3 的坑)")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=5, minSize=(30, 30))
print("检测到", len(faces), "张脸")
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imwrite("snap_faces.jpg", img)
实时版(picamera2 供帧,循环检测):
# face_live.py
import cv2
from picamera2 import Picamera2
cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())
picam2.start()
for i in range(20): # 无头环境:检测 20 帧打印结果
frame = picam2.capture_array()
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))
print("第", i, "帧:", len(faces), "张脸",
faces.tolist() if len(faces) else "")
picam2.stop()
看到什么算成功:照片版打印【检测到 N 张脸】(N ≥ 1,对着镜头正脸拍一般 N=1),snap_faces.jpg 里脸上多了绿框;实时版对着镜头跑,脸入画时每帧打印 1 组坐标,离开画面变 0。
读结果:坐标 faces 每帧会轻微跳动——级联逐帧独立判断,没有记忆;【框抖】是正常现象,要平滑得自己加滤波(进阶话题)。检测不到时按第 3、4 节排:正脸吗?光线够吗?minNeighbors 是不是调太严了?
六、老教程哪里容易翻车
- 硬编码级联文件路径 → 换
cv2.data.haarcascades拼接(本节第三节)。 - pip 装的 OpenCV 有时不带级联数据 → 用 apt 版(11-2 的正路自动配齐);
ls一眼确认。 CascadeClassifier路径错不报异常、只安静返回空分类器,直到detectMultiScale才炸——加载后立刻.empty()检查。- 喂彩色图给 detectMultiScale → 它要灰度图;不转换在新版会报错或行为异常。
- 把【检测不到脸】当故障 → 先确认正脸、光线、距离(minSize 过滤);再动参数。
七、30 秒实验:实时检测的帧率账
实测本节实时版在树莓派上单帧检测约 0.2 秒:
- 每秒能处理几帧?
- 想做【有人靠近就拍照】的门铃,够用吗?
参考答案:1. 1 ÷ 0.2 = 5 FPS——比摄像头 30 帧的产出慢得多,所以实时程序常【隔帧检测】(每 3~5 帧检一次,中间帧沿用上次结果)。2. 够用:门铃这类【人有没有在】的判断对延迟不敏感,5 FPS 绰绰有余;要【流畅画框】才需要提帧率(缩小分辨率是最快的提速手段)。
八、常见误区
- 【这叫人脸识别】 —— 严格说是人脸检测【这里有脸】;识别【这是谁】需要另外训练身份模型。
- 【检测不到=代码错了】 —— 级联只认正脸;侧脸、逆光、太远(小于 minSize)都检不出,先摆好再怀疑代码。
- 【路径随便写】 —— 硬编码路径是老教程毒瘤;永远
cv2.data.haarcascades拼接,并加载后.empty()检查。 - 【minNeighbors 越大越好】 —— 越大误报越少但漏报越多;门铃要【宁可错杀】调小、考勤要【绝不误报】调大,按场景调参。
- 【级联是最好的找脸算法】 —— 它是最快上手的老算法;深度学习方案(如 OpenCV 自带的 DNN 人脸模型)精度更高但更吃算力——入门用级联,产品级再升级。
自测三问(点开对答案)
1. 检测和识别的区别?本节做的是哪个? 2. `detectMultiScale` 返回什么结构?怎么用它画框? 3. 实时版画面里框为什么会抖? **参考答案** 1. 检测=【有没有、在哪】(框);识别=【是谁】(身份)。本节是检测。 2. 返回 NumPy 数组,每行一个 `[x, y, w, h]`;`cv2.rectangle(img, (x, y), (x+w, y+h), (0,255,0), 2)` 逐个画。 3. 级联**逐帧独立判断、没有记忆**,每帧的框位置各自算各自的最优,轻微跳动正常;平滑要自己加滤波。- ☐ 下一节(11-5 AI 实验范例:OpenCV+YOLO 物体识别)
相关笔记
- OpenCV 的基本使用 —— 上一节:灰度、坐标与 BGR 三把刀
- AI 实验范例:OpenCV+YOLO 物体识别 —— 下一节:从【找脸】升级到【认万物】
- 使用 Python 操作相机模块 —— picamera2 供帧的出处
- 使用 Python 开发 Arduino 程序 —— 【先掰正语义】的同类操作
- 🍓 树莓派 · 索引 —— 全课路线图