先说个很多初学者都会经历的尴尬:按照教程把 OpenCV 装好,抄了一段 CascadeClassifier 检测代码,摄像头一开,绿色框稳稳当当地把人脸框住,于是你觉得自己已经会了人脸识别。换成另一个人站到镜头前,程序仍然只会画框,完全不告诉你站在面前的是谁。问题出在哪儿?出在把“检测”当成了“识别”。
我要写的这篇实战,就是围绕这个卡点展开的:用 Python 和 OpenCV 做一个人脸识别闭环,不只是框脸,而是让程序能判断“这张脸是谁”。内容包括环境安装、样本采集、检测器选型、LBPH 识别器训练、实时识别主循环,以及我在调试中踩过的光照、模糊和阈值问题。整套方案不依赖 GPU,不需要自己搭建深度学习训练流程,普通笔记本加一个 USB 摄像头就能跑起来。适合卡在毕设、课程设计或个人项目试水阶段,有一点 Python 基础但还没系统做过视觉项目的读者。下面直接说干的。
1. 检测和识别是两件事:先把项目边界定清楚
1.1 “绿框套住脸”只是第一步
人脸检测(Face Detection)的任务是回答“画面里有没有人脸,脸在哪”,输出通常是一组矩形坐标 (x, y, w, h)。你看到的绿框,本质上是检测器的可视化结果。而人脸识别(Face Recognition)要回答的是另一个问题:“这张脸对应数据库里的哪一个人”。它输出的是一个人名、ID,或者一个“不认识”的结论。
这个区别听起来像废话,但真做项目时很多人混在一起。比如有人把采集到的图片直接丢进识别器,不先做人脸裁剪,结果模型训练出来了,识别率低得离谱。还有人把检测到的区域直接保存,里面包含了大量背景、身体和噪点,导致同一个人的样本看起来“相差很大”,模型当然学不到稳定的特征。
所以,我在动笔之前必须先把这个边界讲清楚:本项目的完整链路是“检测 → 裁剪 → 灰度化 → 训练/提取特征 → 比对 → 返回姓名”。每一步单独看都不复杂,但顺序一旦错了,后面改起来很痛苦。
1.2 为什么用 OpenCV 而不是直接上 PyTorch
人脸识别在工业界的主流方案早就转向了深度学习,比如用 FaceNet、ArcFace 提取人脸特征向量,再用向量距离判断相似度。这种方案准确率高,但需要准备训练数据、安装深度学习框架、处理模型权重,对第一次接触视觉项目的朋友来说,学习曲线陡很多。
如果用 OpenCV 自带的传统识别器,比如 LBPH、EigenFaces、FisherFaces,逻辑会直白得多:训练时输入“人脸的灰度图 + 标签”,识别时输入一张脸,模型返回标签和置信度。整个过程几百行代码就能跑通,还不需要独立显卡。它的精度上限不如深度学习方案,但在光线可控、正脸为主的场景里,做门禁 demo、课堂签到、家庭小项目完全够用。
有的同学会担心:学这种“过时”方案是不是浪费时间?我的观点是,通过 OpenCV 把“检测到识别”完整串一遍后,你对特征提取、相似度比较、训练集划分这些通用概念会有直观理解。以后再切到深度学习方案,你会发现只是把“手写特征”换成了“网络学出来的特征”,项目骨架依然是这一套。
1.3 OpenCV 方案的能力边界
说完了优点,也要泼一盆冷水。OpenCV + LBPH 这套组合,扛不住以下几类场景:
- 大角度侧脸:检测器可能找不到脸,识别器更是容易混淆。
- 严重遮挡:口罩、墨镜、刘海盖住大半张脸,传统特征几乎失效。
- 跨年龄识别:拿十年前的证件照去匹配现在的脸,LBPH 基本无能为力。
- 大规模人脸库:几百人以上的场景,传统识别器的稳定性会明显下降。
我通常建议:单人验证、小规模家庭或办公室识别、教学演示,用 OpenCV 方案是最划算的。如果你确实要做百万人脸库级别的系统,那需要换 FaceNet、ArcFace 等深度特征方案,那又是另一套工程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建和样本采集:最容易劝退的两道坎
2.1 安装包别装错:cv2 和 cv2.face 是两个概念
先解决第一个劝退点:ModuleNotFoundError: No module named 'cv2'。这个报错最常见的原因就是你根本没装 OpenCV。Python 里安装 OpenCV 的常规命令是:
bash复制pip install opencv-python
但如果你只装了这个包,后面用到 cv2.face.LBPHFaceRecognizer_create() 时大概率会报“module ‘cv2’ has no attribute ‘face’”。因为经典人脸识别模块在 OpenCV 的扩展包 opencv-contrib-python 里,而不是主包里。
bash复制pip install opencv-contrib-python
如果你之前已经装了 opencv-python,我建议先卸载,再装扩展包,避免两个包在同一个环境里互相覆盖:
bash复制pip uninstall opencv-python opencv-contrib-python
pip install opencv-contrib-python
装完验证一下:
python复制import cv2
print(cv2.__version__)
print(hasattr(cv2, 'face'))
第二行输出 True,说明 face 子模块可用。如果你用的是 Anaconda,也可以:
bash复制conda install -c conda-forge opencv
不过 conda 源里的 OpenCV 不一定包含了 face 模块,我还是更推荐在干净的 Python 虚拟环境里用 pip 安装。
2.2 摄像头打开了,但又没完全打开
很多人的第二个坑是摄像头。代码写好了,运行不报错,但窗口一片黑,或者直接提示读取失败。先看最简单的排查链路:
python复制import cv2
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("摄像头打不开")
exit()
while True:
ok, frame = cap.read()
if not ok:
print("读取视频帧失败")
break
cv2.imshow("camera", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
如果 cap.isOpened() 返回 False,我建议按顺序检查:
- 摄像头是不是被别的软件占用了。比如钉钉会议、腾讯会议还在后台,把摄像头独占,OpenCV 就拿不到画面。
- 笔记本摄像头索引是不是 0,如果是外接 USB 摄像头,有时候索引是 1。试到 2 也不丢人。
- 系统相机权限有没有给到 Python。Windows 和 macOS 都可能在系统设置里拦截摄像头权限。
- 如果你在虚拟机上跑,还要注意虚拟机有没有把宿主机的摄像头设备映射进去。
还有个小技巧:如果视频流偶尔画面卡住,可以在 cap.read() 前加一帧 cap.grab() 做缓冲,或者直接把采集分辨率调低一点:
python复制cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
分辨率低了,帧率会稳很多。人脸识别不需要 1080P,640×480 足够。
2.3 采集人脸样本:样本不是越多越乱越好
在识别之前,你得先给每个人建一个数据集目录。建议目录结构:
text复制dataset/
├── zhangsan/
│ ├── 001.pgm
│ ├── 002.pgm
│ └── ...
└── lisi/
├── 001.pgm
└── ...
每个目录代表一个“已知身份”,目录名就是标签。采集过程我一般写成独立脚本,把摄像头画面中检测到的人脸裁剪出来,缩放到统一尺寸保存。下面这个脚本会为当前用户采集 50 张正脸样本:
python复制import cv2
import os
import time
person_name = "zhangsan" # 每次换一个人,手动改这里
save_dir = f"dataset/{person_name}"
os.makedirs(save_dir, exist_ok=True)
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
count = 0
while count < 50:
ok, frame = cap.read()
if not ok:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=6, minSize=(100, 100)
)
for (x, y, w, h) in faces:
# 稍微往外扩一点,让模型学到头部轮廓,而不是只看到脸上的一小块
x0 = max(0, x - 15)
y0 = max(0, y - 15)
x1 = min(frame.shape[1], x + w + 15)
y1 = min(frame.shape[0], y + h + 15)
face = gray[y0:y1, x0:x1]
face = cv2.resize(face, (200, 200))
fname = f"{save_dir}/{int(time.time() * 1000)}_{count}.pgm"
cv2.imwrite(fname, face)
count += 1
cv2.rectangle(frame, (x0, y0), (x1, y1), (0, 255, 0), 2)
cv2.imshow("collect", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print(f"采集完成:{count} 张")
这段代码有几个容易被忽略的细节:保存为 .pgm 格式比 .jpg 更省空间也能减少压缩噪声;存灰度图而不是彩色图,因为 LBPH 本来就用灰度信息,彩色图反而会引入无关干扰;每次保存前检测人脸并缩放,可以保证训练数据尺寸统一。
样本数量不是越多越好。我之前见过有人一口气采集几千张,结果里面一半是低头看手机、一半是模糊帧,模型反而训练得稀烂。一个人 30~50 张就够了,重点是覆盖不同角度和光线:正面、左右轻微偏头、顺光、逆光、距离稍近稍远各来几张。需要注意,我说的是“轻微偏头”,不是让你收集大侧脸,传统识别器处理不了夸张角度。
3. 人脸检测实操:Haar 老将和 DNN 选手的实测对比
3.1 Haar 级联检测器怎么调才不漏检
OpenCV 最传统的人脸检测器是 Haar 级联,它用的是 Haar-like 特征加 AdaBoost 分类器。这个方案最大的优点是轻量,CPU 上跑也能获得不错帧率;但缺点也很明显,对角度、光照和遮挡比较敏感,动不动就误检。
我的检测代码一般长这样:
python复制import cv2
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(80, 80)
)
重点说参数:
scaleFactor:控制图像缩放的步长。1.1表示每次缩小 10%,越小检测越精细,但速度越慢,也越容易把一些纹理误判成人脸。我一般不会调到1.05以下。minNeighbors:一个候选区域至少要被多少个邻近框确认才算人脸。调大可以降低误检,但也会把正脸漏掉。5对实时视频是一个比较实用的值。minSize:小于这个尺寸的区域直接忽略。远处的小脸检测不出来,但近处的大脸不受影响。如果发现人脸离得近却检测不到,往往是因为这个值设大了。
如果你发现检测框在人脸上跳来跳去,一个很有效的方法是不要直接对原始灰度图检测,而是先做一个直方图均衡化:
python复制gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray)
这个方法会拉伸像素分布,增强对比度,对光线不均匀的画面有帮助,但也要注意,过度均衡化可能让暗部噪点更明显,所以需要实测。
3.2 换用 DNN 检测器:稳定性提升肉眼可见
如果 Haar 级别的误检让你头大,我建议直接切换到 OpenCV DNN 模块提供的 SSD 人脸检测器。这个模型基于 ResNet + SSD,OpenCV 里可以直接用 Caffe 格式加载,运行在推理阶段时纯 CPU 也能跑,不需要训练。
你需要两个文件:一个 Caffe 模型文件 res10_300x300_ssd_iter_140000.caffemodel,一个网络结构文件 deploy.prototxt。我把加载和推理封装成函数:
python复制import cv2
import numpy as np
net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel")
def detect_faces_dnn(frame, conf_threshold=0.6):
h, w = frame.shape[:2]
blob = cv2.dnn.blobFromImage(
frame, 1.0, (300, 300), (104.0, 177.0, 123.0)
)
net.setInput(blob)
detections = net.forward()
boxes = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence < conf_threshold:
continue
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
x1, y1, x2, y2 = box.astype("int")
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(w - 1, x2)
y2 = min(h - 1, y2)
# 过滤明显过小的框
if x2 - x1 < 20 or y2 - y1 < 20:
continue
boxes.append((x1, y1, x2 - x1, y2 - y1))
return boxes
DNN 检测器输出的不是 Haar 的 (x, y, w, h),而是左上角和右下角坐标,所以我在返回前手动转换成了统一格式。这样后面切换检测器,只要保证函数签名一致,主程序代码不用改。
从我的实测看,DNN 模型在侧脸、低头、暗光场景里的表现明显好于 Haar。代价是 CPU 占用更高,在普通笔记本上帧率会下降。如果你的主程序不需要每帧都检测,可以每隔 2~3 帧检测一次,或者先缩小帧再检测,能省不少时间。
3.3 我对两套检测器的取舍建议
| 维度 | Haar 级联 | OpenCV DNN SSD |
|---|---|---|
| 加载速度 | 很快 | 需要加载模型,首次略慢 |
| 检测帧率 | 高 | 中 |
| 正脸稳定性 | 中 | 高 |
| 侧脸/暗光表现 | 弱 | 明显更好 |
| 误检率 | 相对高 | 可通过置信度阈值控制 |
| 依赖文件 | OpenCV 自带了 xml | 需要额外下载模型 |
| 适合阶段 | 样本采集、实时 demo | 对稳定性有要求的场景 |
我的习惯是:样本采集阶段用 Haar 就够了,速度快而且方便连续抓取;到最终识别阶段,我通常切到 DNN 检测器,因为识别器对检测框的位置太敏感了。检测框偏了哪怕几个像素,裁剪出来的脸都会变化很大,直接影响识别置信度。
说实话,如果真的想做人脸识别,检测框的质量比“检测速度”更重要,因为后续识别是建立在裁剪区域上的。框不稳定,后面所有环节都会不稳定。
4. LBPH 识别器:为什么入门项目绕不开它
4.1 LBPH 到底是什么思路
LBPH 的全称是 Local Binary Pattern Histograms,局部二值模式直方图。它的核心思想是:把一张灰度图按像素逐个比较,每个像素跟它周围的像素比大小,周围像素比中心像素大就记为 1,小就记为 0,最后得到一个二进制串,再转换成数值。这个过程保留了局部的纹理结构,比如皮肤纹路、眼睛轮廓、嘴唇边界。
做完这一步,图片会变成一张“特征图”,但直接拿特征图去匹配很容易受位置偏移影响。所以 LBPH 会把特征图切成一个个小格子,然后统计每个格子的直方图,最后把所有格子拼接起来形成整张脸的特征向量。
这个算法让我想起小时候玩的点连线:每个人画出来的线位置不完全一样,但局部形状相似,通过统计每个区域的笔画分布,就能判断是不是同一个人画的。LBPH 的鲁棒性就在于它不是逐像素比颜色,而是比局部纹理模式,因此对光线变化有一定容忍度。
4.2 训练脚本:从 dataset 目录到模型文件
训练时,OpenCV 的 face 模块需要两个数组:所有训练图片的列表 images,以及每张图片对应的人名标签列表 labels。OpenCV 不支持中文目录名,所以我建议目录名用拼音或英文,保存一个映射关系,比如 zhangsan 代表“张三”。
训练脚本写起来不长:
python复制import os
import cv2
import numpy as np
images = []
labels = []
label_map = {}
current_label = 0
for person_name in sorted(os.listdir("dataset")):
person_dir = os.path.join("dataset", person_name)
if not os.path.isdir(person_dir):
continue
if person_name not in label_map:
label_map[person_name] = current_label
current_label += 1
for file_name in os.listdir(person_dir):
if not file_name.lower().endswith((".pgm", ".jpg", ".png")):
continue
path = os.path.join(person_dir, file_name)
img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
if img is None:
continue
img = cv2.resize(img, (200, 200))
images.append(img)
labels.append(label_map[person_name])
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.train(images, np.array(labels))
recognizer.save("face_model.yml")
with open("label_map.txt", "w", encoding="utf-8") as f:
for name, label_id in label_map.items():
f.write(f"{label_id}:{name}\n")
print("训练完成,共", len(images), "张图片")
有几个细节值得注意:
- 图片尺寸必须统一。我这里统一缩放到 200×200,太大训练慢,太小丢纹理。
- 图片路径不要出现中文。
cv2.imread在处理含中文路径时经常读不到图,虽然新版有所改善,但最好别赌。 - 模型文件和标签映射文件都要保存。模型文件存的是特征,标签映射文件存的是“数字标签对应谁”,两个文件配合才能显示人名。
4.3 confidence 值越小越像
识别不是直接返回“这是张三”这么简单。recognizer.predict(face) 返回两个值:label 和 confidence。label 是预测的标签,confidence 是差异度。
LBPH 的 confidence 和常见分类器相反:数值越小,说明待识别图片和训练样本越接近;数值越大,说明越不靠谱。没有一个放之四海皆准的阈值,但根据我的实测经验:
confidence < 50:非常像,基本可以认定是同一个人。confidence 50~70:可能是同一个人,也可能是光线变化比较大的同一个人,需要结合现场判断。confidence > 70:大概率不是训练集里的人,建议当“陌生人”处理。
阈值设太高,容易把陌生人误认成熟人;设太低,又会拒绝太多熟人。这需要你在自己采集的数据上多测几轮再定,不要直接抄网上的数字。
5. 实时识别主循环:把检测、比对、UI 缝在一起
5.1 一段可跑的识别主程序
前面准备好了训练模型和标签映射,现在组合成实时识别程序。整体逻辑是:读摄像头画面 → 灰度化 → 检测人脸 → 裁剪人脸 → 送入 LBPH 识别器 → 渲染结果。
python复制import os
import cv2
# 加载人脸检测器,这里先用 Haar,方便快速跑通
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
# 加载 LBPH 识别器和标签映射
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read("face_model.yml")
label_map = {}
with open("label_map.txt", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
label_id, name = line.split(":", 1)
label_map[int(label_id)] = name
# 这里把真实中文姓名也做一份映射,为了界面显示更好看
display_names = {
"zhangsan": "张三",
"lisi": "李四",
}
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while True:
ok, frame = cap.read()
if not ok:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100)
)
for (x, y, w, h) in faces:
# 裁剪出人脸区域并统一尺寸
face = gray[y:y + h, x:x + w]
face = cv2.resize(face, (200, 200))
# LBPH 预测
label, confidence = recognizer.predict(face)
name_en = label_map.get(label, "unknown")
name = display_names.get(name_en, name_en)
# 处理不认识的人
if confidence > 70:
text = "unknown"
color = (0, 0, 255)
else:
text = f"{name} {confidence:.0f}"
color = (0, 255, 0)
cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
cv2.putText(
frame, text, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2
)
cv2.imshow("face recognition", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这段代码就是我建议先跑通的最小闭环。它已经把采集、训练之外的所有环节一次性串起来了,你不会再看到“只有框没有人名”的现象。
5.2 系统每执行一次“你是谁”,内部发生了什么
我在调试时习惯把处理流程拆成下面几个阶段,方便定位问题:
- 摄像头返回彩色帧
frame。 - 转成灰度图
gray,减少计算量。 - 检测器在
gray上找到若干人脸框。 - 遍历每个框,把人脸区域裁剪出来,缩放到和训练时相同的尺寸。
- 把这张人脸灰度图交给
recognizer.predict()。 predict()返回最接近的标签和差异度。- 根据差异度阈值判定是“某个已知人”还是“陌生人”。
如果识别结果不稳定,先不要急着调模型,按照这个链路一步一步加打印,定位是哪一步出了问题。是检测框偏了,是裁剪尺寸不对,还是阈值不合理。这种逐层排查的习惯,比直接换算法更管用。
5.3 我实测的一组表现
我用自己的笔记本摄像头,采集了两个不同身份各 40 张左右的样本,跑完上面的程序,大致观感如下:
| 测试条件 | confidence | 结果 |
|---|---|---|
| 正脸、顺光、距离 0.8m | 35~50 | 能正确识别 |
| 正脸、室内灯光、距离 1.2m | 45~65 | 大多数情况能识别 |
| 脸轻微左偏 15 度 | 55~80 | 时好时坏,偏大时直接被判 unknown |
| 逆光或额头阴影明显 | 70~100 | 容易被误判成陌生人 |
| 完全不认识的人 | 85~140 | 绝大多数情况识别为 unknown |
可以看到,在光线稳定、人脸正对镜头的条件下,这套方案完全能工作。但只要光照条件变差,或者脸的角度稍微大一点,置信度就会明显恶化。这不是代码 bug,而是传统识别器的固有边界。理解这个边界,你才不会在后续调参时瞎折腾。
6. 从 demo 到稍微能用:我在调试里踩过的优化点
6.1 模糊帧是隐藏杀手
一开始我的样本采集脚本没有做清晰度判断,导致连续抓了很多模糊帧。训练时模型把这些模糊帧也当成了某个人的真实纹理,结果预测时,真人站到镜头前反而不稳定。
后来我在采集和识别环节都加了清晰度过滤。最轻量的方法是用拉普拉斯算子的方差判断图片边缘强度,方差太小就是模糊:
python复制import cv2
import numpy as np
def is_blurry(face_gray, threshold=80):
laplacian_var = cv2.Laplacian(face_gray, cv2.CV_64F).var()
return laplacian_var < threshold
阈值需要自己调。我实测在 200×200 的人脸灰度图上,正常清晰的人脸方差一般超过 100,模糊帧往往只有二三十。加入这个判断后,采集样本的质量会明显提高。这个技巧网上讲得不多,但对识别率的提升非常直接。
6.2 注册策略要“少量多次”,别一次照到底
很多教程告诉你,采集数据时装作自然转头,让摄像头连续抓几十张就行。但如果你真的在摄像头前连续录制,会发现相邻帧几乎一样,相当于你用了几十张非常相似的图片做训练,模型的泛化能力很差。
我的做法是“少量多次”:注册一个人时,分几个时间段采集,每个时间段收集 5~10 张,然后隔一天再补几次。这样做的目的是让训练集覆盖不同时刻的光线、发型、表情和摄像头微小偏移。虽然样本总量差不多,但分布更接近真实使用场景,识别稳定性会更好。
如果项目允许,你可以把新样本持续追加到目录里,重新训练模型。对于几十个人的规模,LBPH 重新训练耗不了多少时间,没必要追求一次性把模型训到完美。
6.3 性能不行,先别急着换电脑
实时识别时如果帧率太低,先查三件事:
- 每帧是不是都在做人脸检测?
- 检测器是不是在原始大分辨率帧上跑?
- 有没有不必要的
cv2.imshow渲染操作拖慢主循环?
我最常用的小优化是隔帧检测。毕竟连续两帧画面差异很小,每隔 2 帧检测一次人脸,检测框稍微延迟只要不超过 0.1 秒,人眼根本感觉不出来。把人脸检测放到低频循环里后,主线程的 CPU 占用会明显下降。
另外,如果只是在控制台跑实验,不要把每一帧都显示出来。显示窗口也会占用不少时间,如果你只是需要测试识别逻辑,可以先关掉 imshow,直接打印预测结果,跑一轮再分析数据。
6.4 最后的个人体会
人脸识别这个项目,最折磨人的往往不是算法本身,而是“你以为模型已经会了,结果换个环境它就翻脸”。我第一次跑通时也以为大功告成,后来灯光稍微变暗,连自己都识别不出来。那之后我才意识到,样本采集和阚值调整才是这个项目的重头戏,检测器和识别器反而是固定的。后面再有人问我 OpenCV 人脸识别难不难,我会说:框架代码半天能写完,但你要耐心对待环境和数据,尤其是前 200 张训练样本。把这一步做扎实了,后面就会顺利很多。
这套方案当然不是最强的人脸识别,但它足够帮你把一个完整的视觉识别项目跑通,也能让你在以后接触深度学习方案时,更清楚每个模块存在的意义。做完这个 demo,你可以继续尝试用 DNN 替换 Haar、用向量距离替换 LBPH、加入人脸关键点对齐,这些都是顺手就能扩展的方向。
