开头直接在结尾前不要把字数统计输出,我会注意。现在开始写这篇实战博文。
很多人第一次接触OpenCV人脸识别,是跟着教程抄了一段人脸检测的代码,结果一运行,期待的绿框没出现,反而砸过来一屏幕报错。这种情况十个人里有八个遇到过,剩下两个直接放弃了。人脸识别、OpenCV、Python这几个词看着门槛不高,但真正从零把它跑通,中间要踩的环境坑、版本坑、模型坑,一点不比写业务代码少。
我自己也经历过这个阶段。几年前第一次在笔记本上装OpenCV,以为一条pip install opencv-python就完事,结果实际做“人脸识别”(不是检测)的时候,发现cv2.face这个模块压根不存在,又去查资料、换包、重装,折腾到后半夜才跑出来第一组“识别成功”的绿字。这篇文章就把我从环境搭建到模型选型,再到实际写代码踩过的坑和最终沉淀下来的方案,完整讲一遍。适合刚接触Python和OpenCV的初学者,也适合已经能跑通检测、但对“识别”和工程项目落地还有困惑的开发者。
1. 环境搭建:把OpenCV装到真正能用的状态,而不是装完就报错
1.1 为什么你按网上教程装的OpenCV,总是用不了
很多教程会告诉你“安装OpenCV很简单,执行pip install opencv-python就行了”。这句话不算错,但只说了一半。opencv-python这个包是OpenCV的官方预编译版本,它包含的是主模块,也就是cv2下最常见的图像处理、视频读取、目标检测接口。但人脸识别里常用的face模块,并不在其中。
我第一次做LBPH人脸识别时,就是卡在from cv2 import face这一步。运行时报ImportError: cannot import name 'face' from 'cv2',我一度以为是OpenCV版本太老,跑到官网下载了最新版,结果一样。后来才意识到,cv2.face属于contrib扩展模块,预编译版本里不会默认带上。
这里顺便说一下几个容易混淆的包:
| 包名 | 包含内容 | 适用场景 |
|---|---|---|
opencv-python |
仅主模块 | 基础图像处理、视频处理、Haar检测 |
opencv-contrib-python |
主模块 + contrib扩展模块 | 人脸识别、特征匹配、文本检测等高级功能 |
opencv-python-headless |
仅主模块,不带GUI | 服务器、云函数里跑图像处理 |
opencv-contrib-python-headless |
主模块 + contrib,不带GUI | 服务器上做完整人脸识别管线 |
如果你想用OpenCV自带的人脸识别器(LBPH、EigenFace、FisherFace),直接装opencv-contrib-python是对的。新版OpenCV的深度学习人脸检测器(YuNet)和识别器(SFace)也是通过contrib的FaceDetectorYN、FaceRecognizerSF接口调用的,同样需要这个包。如果只是做Haar级联人脸检测,那opencv-python就够用了。但为了省事,我建议你直接装contrib版,反正绝大多数场景里多出来的功能都是有用的。
1.2 用venv把Python环境隔离好,别让依赖互相“串台”
新人踩得最多的第二个坑,是环境“串台”。电脑里装了好几个Python,有的在/usr/bin/python3,有的在/usr/local/bin/python3,还有Anaconda自带的、Visual Studio Code里虚拟环境激活的Python。你在终端里pip install opencv-python装到了一个Python,然后在Jupyter Notebook里import cv2时用的却是另一个Python,于是报ModuleNotFoundError: No module named 'cv2'。
这个问题好解决,从第一步就建一个干净的虚拟环境。
bash复制# 在项目目录下创建虚拟环境
python3 -m venv face_env
# 激活虚拟环境
# Windows:
face_env\Scripts\activate
# Linux / macOS:
source face_env/bin/activate
激活后,命令行前面会出现(face_env)字样,这时再执行安装:
bash复制pip install opencv-contrib-python numpy
如果你用的是Anaconda,也可以用conda建一个独立环境:
bash复制conda create -n face_rec python=3.10
conda activate face_rec
pip install opencv-contrib-python numpy
我个人的建议是:做OpenCV项目时,不要直接在base环境里装一堆包。因为OpenCV的依赖(numpy、matplotlib等)版本敏感,一旦跟其他项目的依赖冲突,处理起来非常头疼。一个项目一个环境,虽然磁盘多占一点,但能避免很多晚上睡不着觉的排错时间。
1.3 版本选择:别追新,也别恋旧
OpenCV的API在4.x上相对稳定,但某些子模块的接口还是会变。比如cv2.FaceDetectorYN在新版本里参数有变化,SFace的加载流程在不同版本上也有细微差别。我给新手的建议是:使用当前官方推荐的稳定版本,不要装4.8.0之前的版本,也不建议用刚发布的预览版。实测下来,4.9.0、4.10.0这些版本对人脸识别相关API支持稳定,网上参考资料也最多。
确认版本的命令:
bash复制python3 -c "import cv2; print(cv2.__version__)"
如果你看到版本号是4.x.x,说明装的是官方预编译版本。看到类似7.x.x的版本号,那可能是装成了opencv-python的错误包(有人从镜像站下载了不对应的包),这种情况直接卸载重装。
1.4 源码编译:折腾之前,先问自己真的需要吗
热词里有不少跟“CUDA OpenCV编译”“RK3588平台OpenCV”相关的问题。很多人一上来就想从源码编译一个带CUDA加速的OpenCV,理由是人脸识别实时性要求高。我先泼一盆冷水:用OpenCV做入门的人脸检测和识别,CPU完全够跑。Haar检测在普通笔记本上每帧20-50毫秒,LBPH识别更是微秒级,犯不上为了“加速”去编译一个两天都编不完的库。
什么时候才需要源码编译?当你部署到嵌入式平台(比如RK3588、Jetson),需要调用硬件加速单元(NPU、GPU)时;或者你要用OpenCV自带的OpenCL/CUDA后端处理高分辨率视频流时。这时候再去研究CMAKE_CUDA_ARCHITECTURES、-DWITH_CUDA=ON这些参数。作为入门,先老老实实用预编译包。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 别急着抄代码:先搞懂OpenCV做“人脸识别”的三条路线
2.1 “检测”和“识别”是两件事,很多人混在一起
我见过不少初学者把“人脸检测”当成“人脸识别”来搜,发现代码跑通了但没达到预期,然后一脸茫然。这两个概念必须分清:
- 人脸检测(Face Detection):在图像里找到人脸的位置,画一个框。它不关心这是谁,只回答“有没有人脸、在哪”。
- 人脸识别(Face Recognition):判断框里的人是谁,或者说是谁的概率最大。
对应到OpenCV里,检测部分通常用Haar级联分类器或者YuNet深度学习模型;识别部分可以用传统的LBPH/EigenFace,也可以用深度学习特征比对(SFace)。一篇完整的人脸识别项目,检测和识别是串在一起的管线。
2.2 Haar级联分类器:最老牌的“暴力匹配”
Haar级联分类器是一种基于特征的检测方法。它用一系列矩形特征(Haar特征)来判别某个区域是不是人脸。训练时会生成一个级联的强分类器:先用很少的简单特征快速排除大部分非人脸区域,只有通过前面若干层检测的区域,才进入更复杂的判断。所以它很快,特别适合在CPU上实时跑。
但它的局限性也很明显:对角度、光照、遮挡非常敏感。正脸稍微偏个十几度,可能就检测不到了。当年OpenCV自带的haarcascade_frontalface_default.xml我用了很久,后来发现戴眼镜、低头看手机时经常丢帧,就果断换掉了。
2.3 LBPH和EigenFace:传统的识别算法
LBPH(Local Binary Pattern Histogram,局部二值模式直方图)是OpenCV里最常用的传统人脸识别器。它的原理分三步:
- 对人脸区域做灰度化、缩放、直方图均衡化预处理。
- 对图像中的每个像素,和它周围的像素比较,生成一个局部二值模式(LBP)编码,然后统计整张图的直方图。
- 训练阶段把每个人的多张人脸样本编码成直方图,识别阶段比较新样本直方图和人脸库里的直方图,距离最近的判为匹配。
LBPH的好处是计算量小,样本需求量低(每个人5-10张就够了),无需GPU,适合小规模场景。100个人的班级门禁、公司部门的考勤打卡,用LBPH完全够用。缺点是光照变化大的时候鲁棒性一般,而且它做的是“人脸特征匹配”,不是真正的“深度学习特征提取”,所以跨年龄、跨姿态的识别效果会差一些。
2.4 YuNet + SFace:OpenCV官方推荐的深度学习方案
从OpenCV 4.x开始,官方在OpenCV Zoo项目里放出了两个直接能用的人脸模型:
YuNet:人脸检测模型,提取人脸的边界框、五个关键点(左眼、右眼、鼻尖、左右嘴角)。SFace:人脸识别模型,把人脸图像编码成一个128维特征向量,通过向量距离判断是否是同一个人。
这套组合最大的优势是精度比Haar高一个档次,同时又不需要自己训练深度学习模型,只需要下载权重文件调用API就行,对普通开发者非常友好。很多人脸识别门禁厂商的软件原型就是这么做的。
2.5 三条路线怎么选
如果你做的是简单的测试Demo、出教程、课程设计,用Haar检测+LBPH识别就足够了,代码短、依赖少。如果你要做实际可用的小项目,比如家里的小型人脸门禁、实验室的考勤系统,建议直接上YuNet+SFace,精度和稳定性会好很多。这也是我后面几章展开的重点。
3. 从摄像头到名单:一个最小可跑的人脸识别Demo
3.1 先做一个简单的人脸采集脚本
训练一个LBPH识别器,需要每个目标人员收集多张人脸图像。最简单的采集方式是从摄像头逐帧抓取,按下按键保存当前帧的人脸区域。下面这个脚本就是从摄像头实时读取画面,检测到人脸后自动裁剪并保存到以用户名命名的文件夹里。
python复制import cv2
import os
# 输入用户名,作为保存目录
name = input("请输入姓名: ").strip()
save_dir = f"./dataset/{name}"
os.makedirs(save_dir, exist_ok=True)
# 加载Haar级联检测器(注意路径,换成你的实际路径)
cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头,请检查设备号")
exit(1)
count = 0
print("当摄像头画面中显示人脸时,按下 s 保存,按下 q 退出")
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(100, 100),
)
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
face_roi = gray[y:y + h, x:x + w]
# 统一缩放到200x200,方便后续训练
face_resized = cv2.resize(face_roi, (200, 200))
key = cv2.waitKey(1) & 0xFF
if key == ord('s'):
count += 1
img_path = os.path.join(save_dir, f"{count}.jpg")
cv2.imwrite(img_path, face_resized)
print(f"已保存第 {count} 张人脸样本: {img_path}")
elif key == ord('q'):
cap.release()
cv2.destroyAllWindows()
exit(0)
cv2.imshow("Face Capture", frame)
cap.release()
cv2.destroyAllWindows()
print(f"采集完成,共保存 {count} 张样本")
注意几个细节。第一,cv2.data.haarcascades是OpenCV自带的级联文件路径,不用手动下载模型文件,这对初学者很方便。第二,保存前把roi统一缩放到固定大小,这样训练出来的模型不会因为原始人脸框尺寸差异而失效。第三,采集时尽量让人脸正面朝向摄像头,轻微转动、戴眼镜、光线不足这些情况都会降低识别效果,所以每一类样本最好覆盖几种不同的角度和光线。
3.2 训练LBPH识别器:让程序记住“谁是谁”
有了样本之后,训练模型就是几行代码的事。先把所有样本图片读进来,标注好标签。标签是从0开始的整数,分别对应不同的人。
python复制import cv2
import os
import numpy as np
dataset_dir = "./dataset"
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
images = []
labels = []
label_map = {} # 标签到人名的映射
current_label = 0
# 遍历dataset下的每个子文件夹,每个文件夹是一个人的样本
for person_name in os.listdir(dataset_dir):
person_dir = os.path.join(dataset_dir, person_name)
if not os.path.isdir(person_dir):
continue
label_map[current_label] = person_name
for img_name in os.listdir(person_dir):
img_path = os.path.join(person_dir, img_name)
# 用opencv直接读灰度图
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
if img is None:
continue
images.append(img)
labels.append(current_label)
print(f"样本: {img_path} -> label {current_label}")
current_label += 1
print("标签映射:", label_map)
# 创建LBPH识别器并训练
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.train(images, np.array(labels))
# 保存模型和映射表(映射表用yaml存,方便后面识别时读取)
recognizer.save("lbph_model.yml")
with open("label_map.yaml", "w", encoding="utf-8") as f:
import yaml
yaml.dump(label_map, f, allow_unicode=True)
print("训练完成,模型已保存")
训练过程的核心是recognizer.train(images, np.array(labels))。这里要求images是Python列表,列表里的每个元素是一张灰度图,维度必须一致(所以采集时我统一缩放到200×200)。如果图片尺寸不一致,训练时会报维度错误。labels必须转成np.array,标签是整数,不要用字符串。
保存模型后,lbph_model.yml就是一套人脸特征库。之后再次运行程序时,不需要重训,直接加载这个文件就行。
3.3 实时识别:给每一帧画面里的人脸打上名字
实时识别的逻辑也简单:从摄像头读帧 -> 检测人脸框 -> 对框内区域调用recognizer.predict -> 根据返回的标签和置信度判断是谁,并把名字画在框上。
python复制import cv2
import yaml
# 加载训练好的模型和标签映射
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read("lbph_model.yml")
with open("label_map.yaml", "r", encoding="utf-8") as f:
label_map = yaml.safe_load(f)
# yaml的key默认是字符串,转成int
label_map = {int(k): v for k, v in label_map.items()}
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("无法打开摄像头")
exit(1)
while True:
ret, frame = cap.read()
if not ret:
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_roi = gray[y:y + h, x:x + w]
face_resized = cv2.resize(face_roi, (200, 200))
# predict返回(label, confidence)
label, confidence = recognizer.predict(face_resized)
# confidence越小,表示距离越近,置信度越高
name = label_map.get(label, "unknown")
if confidence > 80:
name = "unknown"
# 画框和名字
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(
frame,
f"{name} ({confidence:.1f})",
(x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.8,
(0, 255, 0),
2,
)
cv2.imshow("Face Recognition", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这里有个很重要的参数:confidence的阈值,也就是代码里的if confidence > 80。LBPH的predict()返回的距离值,数值越小越像同一个人。不同光照、不同摄像头下,这个阈值需要自己调。如果设置得太小(比如50),会导致一个脸上框就被标记为“unknown”;设置得太大(比如200),又会出现张冠李戴的情况。我的经验是:先用80起步,然后对着摄像头多试几次,调整到“认识的人几乎都能认出,不认识的人大多数被拦下”的平衡点。
3.4 效果不理想时,先别急着换算法,先优化数据
很多人跑完Demo,发现打出来的名字时对时错,第一反应是“LBPH算法不行”。但实际大多数情况是样本问题。我踩过两个典型的坑:
第一个是样本数量太不均衡。一个人收集了50张,另一个人只有5张,训练出来的模型会偏向样本多的那个人,导致识别率分布不均。建议给每个人收集20到30张,角度、表情、光照都覆盖一下。
第二个是检测框尺寸和训练尺寸不一致。如果你训练时缩放到200×200,但识别时忘了resize,或者resize到了160×160,识别效果会明显下降。好在上面代码里训练和识别都统一到了200×200,这里不会出错。
还有一个隐藏问题:Haar检测出来的框有时候夹带了背景和脖子区域,导致训练样本里“人脸”之外的东西占比较多。可以适当缩小检测框的宽高,只取人脸的中央区域,减少背景噪声。
4. 我踩过的那些坑:从NotImplementedError到CPU满载
4.1 OpenCV Error: The function/feature is not implemented 的完整排查链路
这个报错我见了不知道多少次,网上一搜一大把。常见场景有两种:第一种是调用了cv2.face相关方法,但装的是opencv-python而非opencv-contrib-python。错误信息会明确提示the function/feature is not implemented,因为OpenCV编译时没把这个模块编进去。
第二种是视频读取相关,比如VideoWriter或VideoCapture在读取某些格式时,因为OpenCV本身没有带对应编解码器,也会报这个错。这种情况在Windows上比较少见,在Linux服务器上却很常见。
排查思路可以按下面几步走:
- 确认报错的函数是哪个模块。如果是
cv2.face,先换成opencv-contrib-python再卸载opencv-python。 - 确认当前Python环境到底是哪一个。用
python3 -c "import cv2; print(cv2.__file__)"打印出cv2的实际路径,看看是不是你预期的那个环境。 - 如果是视频相关报错,先尝试把视频转成MP4(H.264编码)再测试,或者安装FFmpeg相关的系统依赖。
用一个命令同时排查环境和模块:
bash复制python3 - <<'EOF'
import sys
import cv2
print("Python:", sys.executable)
print("OpenCV:", cv2.__version__)
print("cv2 path:", cv2.__file__)
print("has face module:", hasattr(cv2, "face"))
EOF
如果has face module输出False,就说明你确实装错了包。执行一次干净重装:
bash复制pip uninstall opencv-python opencv-contrib-python -y
pip install opencv-contrib-python
4.2 No module named 'cv2':环境“串台”的经典场景
ModuleNotFoundError: No module named 'cv2'是新手最常碰见的报错,大概率是当前的Python解释器和pip装包的解释器不是同一个。在命令行里运行python3 -m pip install opencv-contrib-python,用-m参数明确绑定到当前Python解释器,比直接输pip install稳妥得多。尤其在macOS和Linux上,系统自带的Python和用户安装的Python并存,直接输pip很可能会装到系统Python目录下,而用户代码跑在用Homebrew安装的Python上,自然找不到。
另外一个经典场景是:之前在conda环境里装好了OpenCV,后来打开终端忘了conda activate,在base环境里运行脚本,结果import cv2就报错了。解决办法:每次运行项目前,先确认环境是否激活。这看起来像废话,但真的能花掉一整晚。
4.3 中文路径和中文标签,一个容易翻车的细节
我在保存数据集时用了中文用户名做文件夹名,OpenCV读取中文路径的图片有时会直接返回None,然后训练时images列表里就少了样本,模型效果自然会差。另外,Windows下如果路径里有中文,cv2.imread偶尔会失败,这是OpenCV老旧的编码处理逻辑导致的。
一个稳妥的解决方案是:项目路径和数据集目录全部用英文,不出现中文字符。至于中文标签,把label_map用yaml存成UTF-8,识别时放到cv2.putText上显示,通常没问题。如果发现putText显示中文变成问号,那是因为OpenCV自带的字体不支持中文,需要改用PIL绘制中文文字,或者直接把中文名字替换成拼音/英文。
4.4 CPU满载和摄像头延迟,别把问题都归给OpenCV
用Haar检测+LBPH识别这个组合,在普通笔记本上CPU占用率会比较高。如果摄像头分辨率是1080p,每一帧都要做灰度转换、缩放、金字塔检测,CPU会一直处于高负载状态。很多人这时候第一反应是“OpenCV太吃性能了”,其实问题出在没有控制帧率和分辨率。
我实测过,把帧处理前先resize到640×480,再把检测框坐标映射回原图,CPU占用能下降40%以上。代码很简单:
python复制frame_small = cv2.resize(frame, (640, 480))
gray_small = cv2.cvtColor(frame_small, cv2.COLOR_BGR2GRAY)
faces_small = cascade.detectMultiScale(gray_small, 1.1, 5)
# 坐标放回原图
rate_x = frame.shape[1] / 640
rate_y = frame.shape[0] / 480
for (x, y, w, h) in faces_small:
x = int(x * rate_x)
y = int(y * rate_y)
w = int(w * rate_x)
h = int(h * rate_y)
# 继续处理...
另外,cv2.VideoCapture默认会读取摄像头支持的原始分辨率,手动设置小分辨率能省很多性能:
python复制cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
4.5 摄像头索引问题:cap = cv2.VideoCapture(0)打不开怎么办
笔记本内置摄像头通常用0,外接USB摄像头可能是1或2。如果设备号不对,cap.isOpened()会返回False。排查时可以把0到3都试一遍,也可以查看系统摄像头列表。在Linux服务器上,还有可能是权限问题,需要先把当前用户加入video组,或者用sudo跑一下试试(不过不建议长期用sudo跑项目)。这一步虽然基础,但很多人确实会卡在这里。
5. 做完Demo之后,往真实项目走的三步:换模型、提精度、做部署
5.1 把Haar级联换成YuNet,检测效果立刻上一个台阶
当你把LBPH的Demo跑通之后,下一个瓶颈往往就是Haar检测。Haar在侧脸、遮挡、暗光下漏检率高,稍微低点头,框就消失了。这时候可以用OpenCV的YuNet模型替换掉级联分类器,代码改动不大,但检测稳定性能改善很多。
YuNet的用法和Haar完全不同,它需要加载ONNX模型文件。模型文件可以去OpenCV Zoo的GitHub仓库下载,名为face_detection_yunet_2023mar.onnx。核心调用代码如下:
python复制import cv2
# 创建YuNet检测器
detector = cv2.FaceDetectorYN.create(
"face_detection_yunet_2023mar.onnx", # 模型路径
"", # 配置文件,可留空
(320, 320), # 输入尺寸,需要是32的倍数
0.9, # 置信度阈值,可调
0.3, # NMS阈值
5000, # 最多保留的检测框数量
)
# 读取一帧图像,转换为BGR格式
img = cv2.imread("test.jpg")
# YuNet接受输入尺寸,需要根据实际图像大小设置一次
detector.setInputSize((img.shape[1], img.shape[0]))
retval, faces = detector.detect(img)
# faces是一个N行15列的数组
# 0-3:x, y, w, h
# 4-13:五个关键点坐标
# 14:置信度分数
if retval > 0:
for face in faces:
x, y, w, h = face[:4].astype(int)
score = face[14]
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(img, f"{score:.2f}", (x, y - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow("YuNet", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
YuNet的输入尺寸很关键,不要直接传原始大图,而是先缩放到一个合理尺寸。模型内部会自己处理缩放,但如果你原始图是1920×1080,直接输入也是能跑的,只是开销更大。实际项目里我会把输入尺寸设置成原始图的0.5倍左右,检测速度会快不少,精度损失很小。
5.2 用SFace替代LBPH:从“看长相”到“算特征”
LBPH这种传统方法,本质上是把局部纹理统计成直方图再作比较,类似根据一个人的五官比例和肤质纹理来判断“长得像不像”。而SFace这类深度学习模型,是把人脸图像映射到高维特征空间,让同一个人的不同照片在特征空间里靠得很近,不同人离得很远。
在OpenCV里使用SFace和LBPH有一个核心区别:SFace提取的是一整个特征向量,需要你保存一个“特征库”,识别时把当前人脸特征和库里的特征逐个算距离。
python复制import cv2
import numpy as np
# 加载SFace识别器
recognizer = cv2.FaceRecognizerSF.create(
"face_recognition_sface_2021dec.onnx", # 模型路径
"", # 配置文件,可留空
)
# 假设你已经用YuNet检测到人脸框并裁剪出人脸对齐图
# 下面这段是提取特征并计算相似度的示例
def get_feature(face_aligned):
# SFace需要输入对齐后的人脸,一般是112x112的BGR图
feature = recognizer.feature(face_aligned)
# 归一化
cv2.normalize(feature, feature, 1.0, 0.0, cv2.NORM_L2)
return feature
# 计算两张人脸的余弦距离,分数越低表示越相似
score = recognizer.match(feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE)
# score < 0.363 通常认为属于同一个人,这是SFace官方建议的阈值
print("相似度分数:", score)
这里有一个提示:SFace需要输入对齐后的人脸,也就是要把眼睛、鼻子、嘴角对齐到标准位置,直接用检测框裁出来的图效果会打折。YuNet刚好输出了五个关键点,你可以用仿射变换把关键点映射到标准位置。整个流程比LBPH复杂,但识别精度也上了一个台阶。如果只是做小规模个人项目,LBPH够用;做门禁、考勤这种商业化原型,我建议直接用SFace。
5.3 嵌入式部署和相机对齐:RK3588、红外相机的优化思路
热词里有“人脸识别门禁系统RK3588”“OpenCV如何测定两个摄像头基线长度”“RGB与红外相机画面对齐”这些,说明很多人做完软件Demo后都在往硬件产品方向走。嵌入式平台和PC上跑OpenCV,最大的区别是算力受限和平台架构差异。
在RK3588这种平台上,OpenCV的预编译版本不一定能充分发挥NPU的算力。常见做法是用OpenCV读取画面,然后把图像数据交给RKNPU等硬件加速推理引擎,最后把结果画到画面上。也就是说,OpenCV在嵌入式平台上的角色从“算力提供者”变成了“数据读取和预处理工具”。如果你要做RGB与红外双摄活体检测,两个摄像头之间的配准是核心问题,需要在OpenCV里做相机标定,计算两个相机的内参、外参和基线长度,然后对两张图做重投影对齐。这部分涉及张正友标定法和极线校正,是一个完全独立的大主题,这里先不展开。
我的建议是:先把软件Demo在自己的电脑上跑通,理解检测、识别、模型这三个层次的概念,再考虑嵌入式平台上的算子和内存优化。一口吃不成胖子,但一步步走一定能到。
最后分享一个小技巧:在跑人脸识别项目时,把检测的置信度阈值和识别的距离阈值都写进一个配置文件里,用YAML存着。你调参时就不用改代码,改配置就行。这个习惯是我被多次“改一行代码跑一次”折磨之后养成的。项目规模变大时,你会发现这个配置文件比代码本身还宝贵。
