去年底接了一个小需求:给朋友的工作室做一套人脸签到系统。预算不高,设备就是一台普通办公电脑加USB摄像头,软件层面我第一时间想到的就是OpenCV和Python。说实话,这套组合在网上资料一抓一大把,但真正从零搭起来、跑到稳定,还是有不少文档里不会写的细节。这篇文章把整个实战过程掰开揉碎,从环境搭建、人脸检测、数据采集、模型训练到最后的识别优化和排错,完整走一遍。不管你是刚接触计算机视觉的入门者,还是准备把OpenCV接进实际项目的开发者,应该都能从里面捞到点能直接用的东西。
1. 装好OpenCV:第一步就卡住的概率其实很高
1.1 先确认Python环境再动手
很多人装OpenCV失败,不是OpenCV的问题,是Python环境本身就乱了。我见过太多新人在系统里同时装了好几个Python版本,然后pip装包时装到了一个版本,运行脚本时用的是另一个版本,最后冒出一句"我明明装了啊",然后到处找教程。所以第一步别急着敲pip命令,先做两件事:
打开命令行(Windows上按Win+R,输入cmd回车;macOS/Linux直接开终端),依次执行:
bash复制python --version
pip --version
这两条命令的输出要对应同一个Python版本。举个例子,如果python --version显示Python 3.11,而pip --version显示的是Python 3.8的pip路径,说明你的环境变量是乱的。这种情况我建议直接装Anaconda或Miniconda,用conda创建独立环境,每个项目的依赖互不干扰:
bash复制conda create -n face python=3.10
conda activate face
为什么用3.10而不是最新的3.12或3.13?主要不是OpenCV本身的兼容性问题,而是很多配套的深度学习推理库(比如后面要用的ONNX Runtime、PyTorch)在最新Python版本上的预编译包可能还没跟上。用3.10或3.11是个很稳的选择,生态兼容性最好。
1.2 opencv-python与opencv-contrib-python怎么选
管道清干净之后,装OpenCV就有讲究了。PyPI上opencv相关的包名有四个,经常有人搞混:
| 包名 | 包含内容 | 适用场景 |
|---|---|---|
| opencv-python | 核心模块,不含contrib | 只做基础图像处理、人脸检测 |
| opencv-contrib-python | 核心模块+contrib扩展 | 需要人脸识别(cv2.face)、特征匹配等 |
| opencv-python-headless | 核心模块,无GUI | 服务器端、Docker容器 |
| opencv-contrib-python-headless | 核心+contrib,无GUI | 服务器端做识别 |
人脸识别所依赖的cv2.face.LBPHFaceRecognizer就在contrib模块里,所以必须装opencv-contrib-python。如果你只装opencv-python,import cv2没问题,但用到cv2.face的时候会直接报AttributeError,这坑我帮你们踩过了。
实际安装命令:
bash复制pip install opencv-contrib-python
如果下载速度慢,可以临时换成国内镜像源:
bash复制pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple
1.3 安装完必须跑的验证命令
安装完成后别急着写代码,先验证一下:
bash复制python -c "import cv2; print(cv2.__version__)"
能打印出版本号才算装好。另外,很多人不知道cv2自带的人脸检测模型文件存放在哪里,这个路径后面写代码要用:
python复制import cv2
print(cv2.data.haarcascades)
打印出来的就是haar级联模型文件的目录,比如...\site-packages\cv2\data\。这里面有haarcascade_frontalface_default.xml、haarcascade_eye.xml等几十个预训练模型。下面实战部分就是基于这些模型文件来做的。
提示:如果你用的是conda环境,import cv2后版本号和上面pip安装的不一致,大概率是conda混用了pip的包,建议在conda环境里统一用pip安装,避免channel之间互相覆盖。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 人脸检测和人脸识别,是两件不同的事
2.1 检测解决"脸在哪",识别解决"你是谁"
很多刚接触OpenCV的朋友容易把"人脸检测"和"人脸识别"混为一谈,但实际上这是两个层面的问题:
人脸检测是找到图像中的人脸位置,输出一个矩形框(左上角坐标x、y和宽高w、h),回答的是"这张图里有没有人脸,脸在哪里"。人脸识别是判断框里的脸到底是谁,输出一个身份标签和置信度,回答的是"这个人是张三还是李四"。
实战项目里通常的流程是:先用检测器把脸从画面中框出来,裁出人脸区域,然后把人脸区域交给识别模型去对比身份。这有点像你先在一个房间里找人,找到人之后再看他是谁。所以这两个模块是串联关系,不是二选一的关系。
2.2 Haar级联分类器的工作原理
OpenCV里最经典的人脸检测方式是Haar Cascade,也就是Haar特征分类器。它的工作方式分两步:
第一步,计算图像中不同区域的Haar特征值。Haar特征其实就是一系列矩形的灰度差,比如"眼睛区域比脸颊区域暗"这样的规律。OpenCV内置了几千个Haar特征模板,通过滑动窗口不断计算,判断当前窗口是否符合人脸特征。
第二步,把几千个特征扔进一个级联分类器里逐级判断。每过一级,不符合人脸特征的窗口就会被淘汰,最后剩下的候选窗口就是人脸。这个"级联淘汰"的机制,使检测速度非常快,普通CPU也能达到实时帧率。
在OpenCV代码里,使用方式很简单:
python复制import cv2
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
加载这个XML文件后,调用detectMultiScale()方法开始检测,返回的就是人脸框列表。
2.3 LBPH为什么适合入门级识别
人脸识别算法领域非常广:从传统方法如Eigenfaces、Fisherfaces、LBPH,到深度学习如FaceNet、ArcFace、CosFace。入门阶段最高效的选择是LBPH,全称Local Binary Pattern Histograms,局部二值模式直方图。
LBPH的原理一句话讲就是:把一张人脸图像划分成多个区域,在每个区域计算局部二值模式(LBP)特征,然后统计成直方图,最后所有区域的直方图拼接成一张"人脸指纹",用来匹配两个人是不是同一个。
它最突出的优势是:不需要GPU,CPU上跑得很快;训练数据量小,每个人只要几十张图片就能工作;OpenCV contrib模块直接封装好了,代码量很少,适合快速落地。
当然它的识别精度天花板明显低于深度学习方法,在光照变化大、人脸角度偏转大的场景下容易翻车。但作为入门项目和中小型应用来说,LBPH足够用了。后文会讲怎么在LBPH不够用的时候切换到深度学习模型。
3. 手写人脸检测:从图片到摄像头实时画面
3.1 先用静态图片跑通检测流程
学检测不要一上来就搞摄像头,先在静态图片上把整个链路跑通,出了问题也好排查。找一张包含人脸的图片放到项目目录下,命名为test.jpg,写下面的代码:
python复制import cv2
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
img = cv2.imread('test.jpg')
if img is None:
print("图片读取失败,检查文件路径")
exit()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
print(f"检测到 {len(faces)} 张人脸")
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)
cv2.destroyAllWindows()
这里解释两个关键点。cvtColor转灰度是必须的,因为Haar特征是基于灰度图像计算的,直接传彩色图虽然程序不一定报错,但检测效果会变差。detectMultiScale是核心方法,它返回一个形状为(N, 4)的NumPy数组,每一行是(x, y, w, h)。
3.2 detectMultiScale三个关键参数怎么调
detectMultiScale的参数直接影响检测效果,这里展开细说:
scaleFactor,缩放因子。检测器会不断缩小图像再扫描,这个值控制每次缩小的比例。1.1表示每轮图像缩小10%,值越小计算量越大但检测越精细,一般取1.05到1.15之间。值太大(比如1.3以上)容易漏检小尺寸人脸。
minNeighbors,每个候选区域需要满足的最小邻居数。这个参数是抑制误检的关键。值越大,误检越少,但越容易漏掉真实的人脸;值越小,检出的框越多,但假阳性也越多。我用的人脸检测场景通常取5到8。
minSize,最小人脸尺寸。小于这个尺寸的窗口直接跳过,一方面能提高速度,另一方面可以过滤画面远处的小物体误判。在1080p画面里想检测3米外的人脸,至少设(60, 60)。
实操中遇到检测不到人脸的情况,优先小幅度调小minNeighbors,然后把scaleFactor往1.05方向调。如果误检太多,就反过来调大minNeighbors,并且调大minSize过滤小区域。
3.3 切换到摄像头实时检测
静态图片跑通以后,切换到摄像头就是加一个视频流循环的事:
python复制import cv2
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头")
exit()
while True:
ret, frame = cap.read()
if not ret:
print("读取视频帧失败")
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60)
)
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('Camera Face Detection', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这段代码有几个常见问题。
cap.read()偶尔返回ret=False,这可能是摄像头被其他程序占用或USB带宽问题,加一个重试机制比直接break更稳健。waitKey(1)如果传0会卡住画面不刷新,实时视频必须传1或者更大的毫秒数。在macOS上如果摄像头画面黑屏,检查终端是否已授权摄像头权限,这一步经常被忽略。
4. 从零训练一个自己的识别器
4.1 采集训练样本的目录组织与拍摄要点
人脸识别的前提是数据。训练集的组织方式直接影响后面代码的复杂度,我的做法是:
code复制dataset/
├── 1_zhangsan/
│ ├── 1.jpg
│ ├── 2.jpg
│ └── ...
├── 2_lisi/
│ ├── 1.jpg
│ ├── 2.jpg
│ └── ...
└── 3_wangwu/
└── ...
目录名以"ID_姓名"命名,ID是整数标签,训练时标签作为分类依据,姓名用来在后面展示中文名。目录建好后,写一个采集脚本,用摄像头拍下人脸区域,统一缩放后保存:
python复制import cv2
import os
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
user_id = input("请输入ID编号:")
name = input("请输入姓名:")
save_dir = f'dataset/{user_id}_{name}'
os.makedirs(save_dir, exist_ok=True)
cap = cv2.VideoCapture(0)
count = 0
while True:
ret, frame = cap.read()
if not ret:
continue
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:
count += 1
face = gray[y:y+h, x:x+w]
face = cv2.resize(face, (200, 200))
cv2.imwrite(f'{save_dir}/{count}.jpg', face)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(frame, f'captured: {count}', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Collect Faces', frame)
if cv2.waitKey(1) & 0xFF in [ord('q'), 27] or count >= 100:
break
cap.release()
cv2.destroyAllWindows()
print(f"采集完成,共保存 {count} 张")
采集的时候有几个非常影响最终效果的点,这里重点说。
第一,每张脸采集后先resize到统一尺寸,这里选200x200,LBPH对输入尺寸不敏感,但统一尺寸后训练数组形状可控。第二,尽量在光照均匀的环境下采集,让脸在画面里上下左右移动,拍下多个角度,但角度别偏太大,LBPH对大幅度侧脸基本无能为力。第三,每保存一张图,最好隔一小段时间再拍下一张,这样相邻帧之间差异性更大。如果想提高样本质量,可以检测两只眼睛是否都在框内,但这会增加代码复杂度,入门阶段直接保存也可以。
每人100张是比较合理的量,多了训练慢,少了识别精度不够。
4.2 训练LBPH识别器
样本采集完成后,写训练脚本。关键点是用cv2.face.LBPHFaceRecognizer_create()创建识别器,cv2.face来自opencv-contrib-python,没有这个模块就是装错包了:
python复制import cv2
import os
import numpy as np
recognizer = cv2.face.LBPHFaceRecognizer_create()
faces = []
labels = []
data_dir = 'dataset'
for dir_name in os.listdir(data_dir):
dir_path = os.path.join(data_dir, dir_name)
if not os.path.isdir(dir_path):
continue
label = int(dir_name.split('_')[0])
for file_name in os.listdir(dir_path):
if not file_name.endswith('.jpg'):
continue
img_path = os.path.join(dir_path, file_name)
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
faces.append(img)
labels.append(label)
faces = np.array(faces)
labels = np.array(labels)
recognizer.train(faces, labels)
recognizer.save('trainer.yml')
print(f"训练完成,共 {len(faces)} 张样本")
训练好之后会生成一个trainer.yml文件,这就是你的"人脸指纹库"。后续每次识别都基于这个文件,不需要重新训练。
recognizer.train(faces, labels)要求faces是一个灰度图列表或数组,labels是对应的整数标签。注意labels必须是整数,不能是字符串,字符串会直接报错。另外所有图片必须是相同尺寸,训练之前统一resize到200x200。
4.3 用predict完成人脸身份判定
训练好模型后,写最终的识别脚本。流程是:读取摄像头帧、检测人脸、对每个人脸区域调用predict、根据置信度判断身份:
python复制import cv2
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read('trainer.yml')
name_map = {1: '张三', 2: '李四', 3: '王五'}
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
continue
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=5, minSize=(80, 80)
)
for (x, y, w, h) in faces:
face = gray[y:y+h, x:x+w]
face = cv2.resize(face, (200, 200))
label, confidence = recognizer.predict(face)
name = name_map.get(label, "Unknown")
if confidence < 60:
text = f"{name} ({confidence:.1f})"
color = (0, 255, 0)
else:
text = f"Unknown ({confidence:.1f})"
color = (0, 0, 255)
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()
这里最需要理解的是confidence这个返回值。LBPH的predict返回两个值:label和confidence。confidence表示预测结果和训练样本之间的距离,数值越小越可靠。这和很多人的直觉相反,不是越大越可信。
实际经验值:confidence在0到50之间,基本可以确定是同一个人;50到70之间要小心,可能是同一个人但光照变化大,也可能是不同的人;超过70,大概率不是同一个人。这个阈值需要根据你的实际场景调整,我建议你跑完数据集后打印几组真实对比值,再定阈值。
5. 从实验室到真实环境:检测精度与运行速度的优化
5.1 Haar检测器的瓶颈在哪里
Haar级联检测器在正脸、光照充足、画面清晰的情况下表现不错,但一到真实场景就露馅了。我实际使用中遇到的问题主要有:
侧脸检测率断崖式下跌。Haar模型是基于正脸特征训练的,人脸旋转超过30度基本就检不到了。如果画面里的人低头看手机或侧身交谈,框就会时有时无。遮挡物影响大,戴口罩、戴墨镜、刘海遮住额头,都会导致检测失败或产生错误的框。光照变化剧烈时误检率上升,逆光环境下的人脸在灰度图上对比度极低,特征值计算出来和背景差不多。
如果你只是做个人脸签到、智能相册这类单人近距离场景,Haar还能扛住。但如果要做门禁、客流统计这类场景,建议直接换深度学习检测器。
5.2 换用YuNet或SSD做深度学习检测
OpenCV从4.x开始内置了DNN模块,可以直接加载深度学习模型做推理,不需要额外安装TensorFlow或PyTorch。
一个很省事的选择是YuNet,它专门做人脸检测,模型文件小,CPU上也能跑实时帧率,是OpenCV官方推荐的替代Haar的方案。使用前先从OpenCV的GitHub仓库下载face_detection_yunet_2023mar.onnx模型文件,然后:
python复制import cv2
detector = cv2.FaceDetectorYN.create(
'face_detection_yunet_2023mar.onnx',
'',
(320, 320),
score_threshold=0.9,
nms_threshold=0.3,
top_k=50
)
img = cv2.imread('test.jpg')
height, width = img.shape[:2]
detector.setInputSize((width, height))
retval, faces = detector.detect(img)
if faces is not None:
for face in faces:
x, y, w, h = face[:4].astype(int)
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
这段代码里face返回的是一个15维数组,前4个是坐标,后面还包含5个人脸关键点坐标和置信度,这些关键点后面做人脸对齐非常有用。与Haar相比,YuNet对侧脸、部分遮挡和暗光环境的鲁棒性提升明显。代价是模型文件约340KB,推理速度比Haar略慢,但现代CPU上仍然能跑30fps以上。
SSD(ResNet-10)是另一个选择,业界叫它OpenCV Face Detector。模型精度比YuNet略低一点,但速度稍快。如果设备的CPU很弱,可以试它。
5.3 识别模型升级方向与CUDA加速
LBPH在光照、角度、表情变化大的场景下识别率不够,这时需要升级到基于深度学习的特征提取模型。思路是:用FaceNet或ArcFace这类模型把裁好的人脸区域转换成一个128维或512维的特征向量,然后通过计算向量之间的余弦相似度来判定是不是同一个人。本质上,LBPH的"直方图"换成了"嵌入向量",比对方式从"距离阈值"升级成"相似度阈值"。
具体在OpenCV里,可以加载ONNX格式的FaceNet模型,用cv2.dnn.readNetFromONNX读取,然后做一次forward,得到特征向量。OpenCV的DNN模块虽然支持GPU加速,但是社区版OpenCV默认不带CUDA支持。想在OpenCV里用CUDA加速推理,通常要用网络上预编译的带CUDA的wheel包,或者自己从源码编译OpenCV。实测下来,YuNet这种轻量级模型在CPU上的推理时间约10到20毫秒,完全不需要上CUDA,CPU加速已经很从容。如果你是用FaceNet这种稍重的模型,普通CPU推理一帧可能在100毫秒以上,这时候才需要考虑CUDA优化。
需要注意,用pip安装的opencv-contrib-python默认不包含CUDA支持,如果你想在OpenCV里启用CUDA,需要装带CUDA的预编译版本或自己编译。对绝大多数入门项目,CPU计算已经完全够用,没必要在这上面投入太多时间。
5.4 边缘平台部署的现实路径
热词里出现了不少硬件平台方向,比如ESP32-S3 CAM人脸识别和基于RK3588的人脸识别门禁系统,说明很多人最终目标是端侧部署。
ESP32-S3 CAM这类MCU级别的平台,内存只有几百KB到几MB,跑不了完整的OpenCV,常见方案是用ESP-DL或TFLite Micro跑极轻量化的人脸检测模型,识别部分通常只做特征比对,不做复杂训练。如果不折腾优化,直接用OpenCV跑Haar检测在ESP32上帧率很低,体验不好。
RK3588是瑞芯微的AIoT芯片,自带6TOPS NPU,跑人脸检测和识别都比较轻松。在RK3588上一般不会直接用通用OpenCV,而是配合RKNN Toolkit把模型转换到NPU上跑,OpenCV只负责图像采集和预处理。这个开发闭环涉及模型转换、交叉编译、NPU驱动适配,复杂度比PC端高一个量级。建议先把PC端的OpenCV流程跑熟,再迁移到边缘平台,否则调试起来会非常痛苦。
6. 实战中的典型报错与排查链路
6.1 ModuleNotFoundError的完整排查思路
ModuleNotFoundError: No module named 'cv2'是出现频率最高的错误。我见过很多朋友装完opencv还报这个错,原因基本逃不出下面几类。
第一,环境错位。你pip install用的是conda的python,运行脚本时用的却是系统python,或者反之。排查方法非常直接:在命令行里执行where python(Windows)或which python(macOS/Linux),看看pip安装时用的是不是同一个解释器。
第二,包的安装名字错了。有人装的是opencv,而不是opencv-python或opencv-contrib-python。PyPI上确实有一个叫opencv的包,但它是另一个项目,不是OpenCV官方绑定。
第三,import时的拼写大小写问题。OpenCV的模块名是cv2,不是cv,不是OpenCV。从Python 3.3开始,大小写敏感,拼写不一致就会报错。
排查链路按这个顺序走,基本五分钟内能定位问题。定位完之后,别急着重装,先看清楚了再动手。
6.2 摄像头打不开或画面卡死
cap.isOpened()返回False的常见原因:
摄像头驱动没装好。Windows下打开设备管理器,确认成像设备里有没有你的摄像头;macOS和Linux下可以用系统自带的相机应用测试。
摄像头被其他程序占用。Zoom、腾讯会议、浏览器正在使用摄像头时,OpenCV打不开。这个报错提示很诡异,不会明确说"占用中",只会给你返回False。打开任务管理器,把所有可能调用摄像头的程序关掉再试。
笔记本没有摄像头或者摄像头硬件开关关闭。一部分笔记本有摄像头物理开关或快捷键(比如FN+F10),关闭状态下系统连摄像头设备都枚举不到,所以先确认自己这台设备有没有摄像头、开关有没有打开。确认没问题之后,再通过cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)和cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)降低分辨率来测试。
画面卡死还有一个容易被忽略的因素:没有及时释放资源。用了v2.VideoCapture(0)后如果没调用cap.release(),程序异常退出后摄像头资源可能没被释放,下次再打开就会失败。
6.3 中文路径保存图片乱码
Windows上使用cv2.imwrite保存到中文路径时,经常出现保存失败或文件名乱码,根本原因是OpenCV内部的编码方式对中文字符支持不好,imwrite遇到中文路径会返回False,甚至直接抛异常。
如果你在Windows上使用OpenCV且路径里有中文,强烈建议加一个前缀r的原始字符串,或者使用os.chdir()进入项目目录,再传相对路径。更稳妥的做法是全部使用英文路径,项目目录用face_recognition_project而不是人脸识别项目,避免编码问题消耗宝贵的调试时间。
python复制# 不推荐:中文路径可能失败
cv2.imwrite('D:/项目/采集/001.jpg', face)
# 推荐:英文路径
cv2.imwrite('dataset/1_zhangsan/001.jpg', face)
6.4 confidence虚高:训练集质量才是核心
训练完模型,跑到真实场景中识别率不高、confidence值普遍偏高,这个问题几乎每个人都遇到过。很多人的第一反应是调阈值,但我建议先从训练集质量下手,这是最根本的优化手段。
训练集质量的核心在多样性和样本数量。样本数量过少,比如每类只有20张,模型学不到足够的特征,预测时confidence自然高。光照单一,训练集里面全是同一个灯光下的照片,一换环境就崩。角度单一,全是大正脸,一低头或一侧脸就识别失败。背景杂乱,训练时提取了背景特征,模型没法聚焦在人脸上。
想提高质量,最直接的做法是增加样本多样性,在多个时间段拍摄,让自然光、室内灯光的照片都出现在训练集里。裁切人脸区域时,保留额头到下巴的完整区域,不要带入太多背景。如果样本采集时人脸框不稳,可以先做眼部检测,确保双眼可见再保存。
数据正确之后,再回到阈值和参数层面调试,否则调多少参数都是白搭。我在这个过程里最深的体会就是:模型精度不够,先怀疑数据和预处理,再怀疑模型选择,最后才去调阈值,这个顺序不要反。
最后分享一个实际项目里的小技巧:LBPH识别器训练完以后,多收集几条真实场景下的预测样本,把每个人的confidence分布打印一遍。你可能会发现,张三的confidence普遍在40左右,李四的可能要到60,这不是模型有毛病,而是他们的面部特征和光照条件不同导致的。所以合理做法是针对每个人设一个阈值,而不是一刀切用同一个阈值,这样的识别结果会比单阈值稳定不少。
