简介:面向人脸识别初学者与OpenCV/SVM算法研究者,这份实战资源围绕人脸识别完整流程展开,涵盖人脸检测、特征提取与模型训练,并以SVM分类器实现多人脸识别,可直接对照配套博客教程边看边做。压缩包共31个文件,主要包含4个Python训练与识别脚本、jpg格式的人脸数据集、pickle模型文件、深度学习人脸检测模型、测试视频和pdf说明文档,总计47.72MB,目录按数据集、模型、脚本、视频分区存放,便于快速定位所需内容。目前已有1456人学习下载。通过资源可掌握图像预处理、人脸对齐、SVM模型训练与评估、视频实时识别等关键技术,同时借助附带的深度网络模型,也能快速搭建OpenCV的SSD人脸检测器,非常适合课程设计、毕业设计或工程化实战参考。
1. 人脸识别实战:用 OpenCV 检测人脸、SVM 分类身份,这份工程包能跑到什么程度
做一套能离线跑的人脸识别,最难的不是算法有多深,而是把 OpenCV、特征提取、SVM 分类这三段串成一条不漏的流水线。这份工程包走通的就是这条路:OpenCV DNN 做检测,nn4.small2 提取 128 维特征,SVM 做最终的身份分类,训练和识别脚本齐全,还带了已分类好的数据集和预训练模型。
包里能看到 face_embeddings.py、train_face.py、recognize_face.py、recognize_video.py 四个脚本,配合 output 下的 pickle 模型文件和两个深度模型,按顺序跑就能完成从训练到图片/视频识别的闭环。整个过程不依赖显卡,CPU 也能跑,所以特别适合做人脸识别课程设计、毕设演示,或者搭一个原型级的人脸识别门禁机。
这篇会把为什么这么选型、每个文件干什么、训练参数怎么调、哪些地方最容易翻车整件事拆开,最后补一段接入门禁/考勤场景的进阶做法。有具体问题直接看第 5 章,那几条都是实际跑过的坑。
2. 先看选型:为什么是 OpenCV DNN + FaceNet + SVM,而不是直接上深度分类网络
2.1 三类人脸识别方案:传统特征、度量学习与端到端分类
人脸识别的技术路线大体可以分成三类。
第一类是传统特征路线,比如 LBP、HOG 配合 SVM 或余弦距离。优点是计算量小、可解释性强,缺点是对光照、角度、遮挡非常敏感,稍微换个环境准确率就掉得厉害,在真实场景里基本只能做低安全要求的演示。
第二类是度量学习路线,代表就是 FaceNet、ArcFace 这类网络。它们不直接输出身份标签,而是把人脸图片映射成一个固定维度的特征向量,训练目标让同一个人的向量距离近、不同人的向量距离远。识别阶段再用 SVM、余弦距离或最近邻去判断。这个方案对光照和姿态的鲁棒性比传统特征好很多,而且预训练模型可以直接拿来用,不用自己从零训练。
第三类是端到端分类网络,直接把身份当成分类任务来训练。效果最强,但需要较大的数据集、较长的训练时间和 GPU。如果只是做一个离线小工程,这条路显然不划算。
这份资源采用的是“OpenCV DNN 检测 + FaceNet 特征 + SVM 分类”的混合路线。检测和特征都直接用预训练模型,只需要训练一个轻量 SVM,正好避开深度模型重训练的算力门槛,又比 LBP + SVM 的旧方案稳。适合单机离线、样本量几十到几百的小规模场景。
| 方案 | 代表 | 优点 | 缺点 |
|---|---|---|---|
| 传统特征分类 | LBP/HOG + SVM | 快、易解释 | 光照/角度鲁棒性差 |
| 度量学习 | FaceNet + 分类头 | 特征泛化好、可用预训练 | 特征网络仍需深度学习知识 |
| 端到端分类 | ArcFace + Softmax | 精度上限高 | 数据、显卡、训练时长要求高 |
2.2 检测器选型:OpenCV DNN 读入 res10 SSD 的用法与参数
人脸检测这一步用的是 res10_300x300_ssd_iter_140000_fp16.caffemodel 配合 deploy.proto.txt。它是 OpenCV DNN 模块官方常用的 SSD 人脸检测模型,输入 300x300 的 RGB 图,输出一组检测结果。虽然模型本身是 Caffe 格式,但 OpenCV 读 Caffe 模型不需要安装 Caffe,一行 readNetFromCaffe 就能加载,这也是选它的主要原因。另一个常见检测器是 Haar Cascade,但它对侧脸和暗光非常不友好,框还经常乱跳,所以这个工程没有用。
import cv2 import numpy as np detector = cv2.dnn.readNetFromCaffe( "face_dete_model/deploy.proto.txt", "face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel" ) def detect_faces(frame, conf_threshold=0.5): h, w = frame.shape[:2] blob = cv2.dnn.blobFromImage( frame, 1.0, (300, 300), (104.0, 177.0, 123.0) ) detector.setInput(blob) detections = detector.forward() results = [] 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") results.append((confidence, (x1, y1, x2, y2))) return results说下逻辑:detector.forward() 返回的 shape 是 (1, 1, max_detections, 7),第二维固定是 0,我们只看 detections.shape[2] 上的每一行。每行第 2 列是置信度,第 3 到第 6 列是归一化坐标,乘上图片宽高就是真实像素坐标。代码里用 x1, y1, x2, y2 的方式把人脸区域从原图切出来,后面喂给特征提取器。
参数上,conf_threshold 我用 0.5。门禁这类误检代价高的场景可以拉到 0.7,人脸小或角度偏时再降回 0.4。blobFromImage 的均值必须按模型原始训练设置,写成 (104, 177, 123),不能随意改成 ImageNet 的均值,否则检测框会漂。
2.3 特征提取器选型:nn4.small2.v1.t7 和 128 维 embedding
检测只是定位,真正决定“是不是同一个人”的是特征向量。资源里带的 nn4.small2.v1.t7 是 FaceNet 系列中一个轻量模型,Torch 格式,OpenCV 的 readNetFromTorch 可以直接加载,这也是这个工程能跑在 CPU 上的关键。
embedding_net = cv2.dnn.readNetFromTorch("nn4.small2.v1.t7") def get_embedding(face_roi): face_blob = cv2.dnn.blobFromImage( face_roi, 1.0 / 255, (96, 96), (0, 0, 0), swapRB=True, crop=False ) embedding_net.setInput(face_blob) vec = embedding_net.forward().flatten() return vec输入是 96x96 的彩色图,scale 设为 1/255,把像素值从 0~255 归到 0~1。输出是一个 128 维的 float 向量,flatten 以后就可以直接交给 SVM。注意这里的 swapRB=True 表示 BGR 转 RGB,因为 Torch 预训练模型一般吃 RGB 顺序,而 OpenCV 默认读进来是 BGR。
为什么强调 128 维而不是把整张图拉平当特征?因为 128 维是 FaceNet 在三元损失下学出来的“语义距离”,同一个人在不同光照、角度下的向量很近,不同人的向量疏远。直接拉平数万个像素做分类,SVM 会被噪声带偏,鲁棒性完全不在一个级别。
2.4 分类器选型:SVM 的核函数、C 参数和 probability 开关
拿到了 128 维特征,最后加一个分类器。资源里用的是 scikit-learn 的 SVC,不是 OpenCV 自带的 SVM,因为 sklearn 的接口更顺手,还能直接输出 predict_proba,方便做“不认识的人”这个判断。
from sklearn.svm import SVC import joblib svm = SVC(kernel="rbf", C=10, probability=True, gamma="scale") svm.fit(data_embeddings, data_labels) joblib.dump(svm, "output/recognizer.pickle")核函数选 RBF,是因为人脸 embedding 的分布通常不是线性可分的,RBF 可以把样本映射到更高维空间,比线性核更贴合这个任务。C 控制对错误的惩罚,C 越大模型越容易把训练样本学得死,换人后泛化变差;C 太小又可能欠拟合。我一般从 C=10 开始调,样本多了再根据验证集准确率增减。gamma 用 "scale",sklearn 会按特征数量自动算一个合理初值,不用手写小数。
关键开关是 probability=True。如果不开,predict_proba 这个接口不可用,识别阶段就只能拿 decision_function 的分数去猜,没有概率可看,阈值也定不准。还有一个隐藏点:SVC 的 predict_proba 是基于 Platt scaling 的,它是在训练后用交叉验证拟合出来的概率,不是真概率,但做阈值筛选足够用了。
至此,选型逻辑已经完整:深度学习模型负责“看脸”,传统机器学习负责“分类”,两边取长补短。第 3 章看工程文件怎么落地的。
3. 压缩包文件解析与环境安装:先搞清楚每个文件是谁,再装依赖
3.1 文件清单与职责
拿到压缩包先别急着跑,按下面这张表过一遍文件,很多报错其实是对文件角色理解错了。
| 路径 | 作用 | 使用阶段 |
|---|---|---|
| face_dete_model/deploy.proto.txt | SSD 检测模型的网络结构 | 训练/识别时加载 |
| face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel | SSD 检测权重 | 训练/识别时加载 |
| nn4.small2.v1.t7 | FaceNet 轻量模型,输出 128 维向量 | 训练/识别时加载 |
| face_embeddings.py | 遍历 dataset,提取人脸向量并缓存 | 第一步 |
| train_face.py | 用缓存向量训练 SVM | 第二步 |
| recognize_face.py | 对图片做人脸识别 | 第三步 |
| recognize_video.py | 对视频做人脸识别 | 第四步 |
| dataset/ | 训练数据集,每个人一个子目录 | 第一步 |
| output/ | 保存 embeddings、标签编码、SVM 模型 | 输出 |
| 1.mp4 | 测试视频 | 第四步演示 |
从这张表能看出分层的设计思路:检测器和特征提取器是两个“不动”的预训练模型,只有 SVM 是需要我们用自己数据训练的部分。pickle 文件是中间产物,embeddings.pickle 和 le.pickle 都可以由 face_embeddings.py 重新生成,recognizer.pickle 可以重新训练。别人给你这份资源时,pickle 可能已经带了一份训练好的结果,但你不一定要用旧的,换成自己的数据重新跑一遍更踏实。
3.2 安装 OpenCV 和 scikit-learn
导出环境前先建虚拟环境,避免把系统 Python 搞乱。
python -m venv .venv source .venv/bin/activate pip install opencv-python==4.5.5.64 scikit-learn==1.0.2 numpy版本上我习惯锁 opencv-python 4.5.5.x。这个版本对 DNN 模块的 Caffe 和 Torch 模型读取都相对稳定。scikit-learn 1.0.2 和 SVC 接口兼容性没有大问题,如果装的是 1.3 之后的新版本,joblib 保存的 pickle 也基本能读。
如果只是在无显示器的服务器上处理图片,可以装 opencv-python-headless 代替 opencv-python,省掉 GUI 依赖,但 recognize_video.py 里如果用 imshow 显示窗口,headless 会直接报错。本地跑就用正常 opencv-python。
装完验证一下:
python -c "import cv2, sklearn, numpy; print(cv2.__version__)"能输出版本号就说明环境没问题。更保险的做法是把两个预训练模型也读一遍:
import cv2 detector = cv2.dnn.readNetFromCaffe( "face_dete_model/deploy.proto.txt", "face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel" ) embedding_net = cv2.dnn.readNetFromTorch("nn4.small2.v1.t7") print("models ok")如果这一步报错,先不要往下走。模型读不出来,后面所有脚本都跑不起来,具体原因在第 5 章排查。
3.3 数据集准备:按人建目录,图片怎么放
face_embeddings.py 和 train_face.py 都约定 dataset 下的目录结构:每个人一个文件夹,文件夹名就是这个人最终显示的名字。
tree dataset正常输出会是:
dataset/ ├── zhang3 │ ├── 001.jpg │ ├── 002.jpg │ └── 003.jpg └── li4 ├── 001.jpg └── 002.jpg文件夹名最终会变成识别框上显示的名字,所以不要用中文昵称之类的含糊词,直接用“姓名拼音”或“工号”最稳。图片格式 jpg/png 都行,建议统一转成 jpg,脚本遍历时匹配 *.jpg 即可,转格式用 PIL 或 OpenCV 批量处理都很方便。
训练样本量我一般按每人 20~30 张来收集,最少不要低于 15 张。这 20 张要覆盖正脸、左右侧脸、轻微仰头、不同光照,最好再包含几张戴眼镜和不戴眼镜的。只对着网页上下载的几张标准证件照糊弄,训练出的 SVM 基本只能在训练集上自嗨。
资源自带的 dataset 已经分好类,可以直接跑通。要训练自己的门禁/考勤数据,把 dataset 里的子目录换成自己的名字目录就行。不要动 nn4.small2.v1.t7 和检测模型,这两个是通用预训练模型,不需要针对某人重新训练。数据准备阶段多花十分钟清理坏图,能省掉后面一个晚上的排错时间。
第 4 章开始跑四个脚本。
4. 训练与识别实操:四个脚本,按顺序跑完一个闭环
4.1 第一步:face_embeddings.py 生成 embeddings.pickle 和 le.pickle
第一个脚本解决的问题是:把一堆人脸图片变成 SVM 能吃的 128 维向量。它内部做了检测、人脸区域截取、embedding 提取,然后统一缓存进 pickle。
python face_embeddings.py \ --dataset dataset \ --detector face_dete_model/deploy.proto.txt \ --detector-model face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel \ --embedding-model nn4.small2.v1.t7 \ --embeddings output/embeddings.pickle \ --confidence 0.5这个脚本的循环逻辑可以写成下面这样:
import os import glob import pickle import cv2 data = {"embeddings": [], "names": []} for person_name in os.listdir("dataset"): person_dir = os.path.join("dataset", person_name) if not os.path.isdir(person_dir): continue for image_path in glob.glob(os.path.join(person_dir, "*.jpg")): frame = cv2.imread(image_path) boxes = detect_faces(frame, conf_threshold=0.5) if len(boxes) == 0: continue # 这张图里没检测到人脸,跳过 x1, y1, x2, y2 = boxes[0][1] face_roi = frame[y1:y2, x1:x2] vec = get_embedding(face_roi) data["embeddings"].append(vec) data["names"].append(person_name) with open("output/embeddings.pickle", "wb") as f: pickle.dump(data, f)注意两个细节。一是如果一张图里同时有多张人脸,早期演示代码通常只取置信度最高的一张;如果你专门收集单人照,这不是问题。二是跳过无人脸图片这个行为会导致最终向量数量不等于图片总数,后面训练和识别阶段要能接受这个差异。
参数上,--confidence 直接传给人脸检测。0.5 是一个平衡值,背景复杂时可以往上调。这个脚本跑完,output 目录下会出现 embeddings.pickle,它是一个 dict,包含 embeddings 列表和 names 列表,两边顺序一一对应。
le.pickle 其实可以由 train_face.py 生成,但完整工程里一些版本会在 embeddings 阶段一并缓存标签编码器,方便后面直接复用。具体看资源里脚本注释,不影响流程。
4.2 第二步:train_face.py 训练 SVM,保存 recognizer.pickle 和 le.pickle
第二步不再碰图片,只吃刚才的向量文件。数据到这一步已经变成“数值 + 标签”,训练很快。
python train_face.py \ --embeddings output/embeddings.pickle \ --recognizer output/recognizer.pickle \ --le output/le.pickle内部大致是:
from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from sklearn.svm import SVC data = pickle.loads(open("output/embeddings.pickle", "rb").read()) le = LabelEncoder() labels = le.fit_transform(data["names"]) (train_emb, test_emb, train_lab, test_lab) = train_test_split( data["embeddings"], labels, test_size=0.2, stratify=labels, random_state=42 ) svm = SVC(kernel="rbf", C=10, probability=True, gamma="scale") svm.fit(train_emb, train_lab)这里有两处容易忽略。stratify=labels 是为了让训练集和测试集里每类比例一致,像这种小样本数据集一定要加,否则某个人可能恰好全部落在测试集里,结果看起来像没学。random_state=42 保证可复现,你跑出来的准确率和别人跑出来对得上,对调试很有用。
C 值可以从 1 到 100 之间试。样本每人 20 张时,C=10 通常就能得到不错的准确率;如果训练集准确率 100% 但测试集明显下降,那是过拟合,把 C 调小到 1 或 0.1。这些值都写在脚本顶部的常量里,不用改算法结构。
跑完 output 下会有 recognizer.pickle 和 le.pickle。recognizer.pickle 是被 joblib/pickle 序列化的 SVC 对象,le.pickle 是 LabelEncoder。识别时两者缺一不可:recognizer 负责给出概率和类别索引,le 负责把索引还原成人名。
4.3 第三步:recognize_face.py 对图片识别,输出人名和置信度
训练完成后的识别就变成了纯加载模型,不用再训练。对图片识别只关心一个输入输出:给一张图,返回每个人脸的名字和框。
python recognize_face.py \ --detector face_dete_model/deploy.proto.txt \ --detector-model face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel \ --embedding-model nn4.small2.v1.t7 \ --recognizer output/recognizer.pickle \ --le output/le.pickle \ --image 1.jpg \ --confidence 0.5关键代码在预测这一段:
svm = pickle.loads(open("output/recognizer.pickle", "rb").read()) le = pickle.loads(open("output/le.pickle", "rb").read()) vec = get_embedding(face_roi).reshape(1, -1) preds = svm.predict_proba(vec)[0] idx = np.argmax(preds) name = le.classes_[idx] prob = preds[idx] if prob < 0.6: name = "unknown"不少人在 vec.reshape(1, -1) 上翻车。SVC 在 sklearn 里接收的输入必须是二维矩阵,而 get_embedding 返回的是一维数组,不 reshape 会直接报维度错误,报错信息也会很抽象。reshape(1, -1) 的意思是变成一行 128 列,-1 让 numpy 自己算列数。
prob < 0.6 就是所谓的未知类判断。SVM 在哪都能给一个最高概率,哪怕这个人根本没见过,它也会硬挑一个最像的输出。所以必须设阈值,低于阈值的统一叫 unknown。阈值从 0.5 到 0.9 都有人用,具体值取决于你对漏判和误判的容忍度,第 6 章会讲怎么用验证集把它定下来。
4.4 第四步:recognize_video.py 跑视频识别
视频识别其实只是把图片识别放进 while 循环,逐帧读取、逐帧处理。
python recognize_video.py \ --video 1.mp4 \ --detector face_dete_model/deploy.proto.txt \ --detector-model face_dete_model/res10_300x300_ssd_iter_140000_fp16.caffemodel \ --embedding-model nn4.small2.v1.t7 \ --recognizer output/recognizer.pickle \ --le output/le.pickle流程上每一帧都做完整的人脸检测、embedding 提取、SVM 预测,然后把名字画在框上。这个写法逻辑最简单,但性能也很简单,CPU 上 30 帧的视频基本会掉到个位数帧率。
如果只是验证功能,掉帧问题无所谓,能跑就行。如果要做成门禁机/考勤机那种实时效果,至少要加两个优化:一是跳帧,不是每帧都跑深模型;二是把检测框结果缓存下来,用追踪器补中间帧。进阶章会展开。
第 4 章跑通了之后,第 5 章的坑才是真正决定这项目能不能落地的部分。
5. 踩坑与常见问题排查:五处最容易翻车的地方,按现象原因解决对照
人脸识别工程翻车往往不是算法看不懂,而是环境和数据的小问题。下面这五条是我实际跑这份资源时踩过的,按“现象 → 原因 → 解决”的顺序写,可以直接对着排。
5.1 ModuleNotFoundError: No module named 'cv2',或者 opencv 装好了但 import 还是失败
现象:运行 face_embeddings.py 时第一行 import cv2 就红,pip show opencv-python 又能看到包,但 python 里 import 就是报错。
原因:大部分情况是虚拟环境没激活或者激活错了环境。pip 装到了 A 环境的 site-packages,python 跑的是 B 解释器。还有一种是装了 opencv-python-headless 和 opencv-python 冲突,两个包装在同一环境里,import 结果被其中一个抢占,但 DLL 依赖不完整。这也就是最常见的“opencv 安装成功却找不到 cv2”的由来。
解决:先执行 which python 和 which pip,确认两者在同一个虚拟环境。然后统一卸载干净再重装,依次执行 pip uninstall opencv-python opencv-python-headless,再执行 pip install opencv-python==4.5.5.64。如果还报错,把报错最后的 DLL 信息贴出来查,多半是缺 VC++ 运行库。
5.2 cv2.error: OpenCV 读不了 caffemodel 或 torch 模型
现象:加载 face_dete_model 时抛 cv2.error,提示文件打不开或 prototxt 解析失败,有的版本还会带一段很长的 OpenCV 编译路径。加载 nn4.small2.v1.t7 也可能报“Unknown layer”之类的问题。
原因:一是脚本里的相对路径不对,比如在项目的上一级目录执行脚本,写成 face_dete_model 就找不到文件;二是 zip 解压时 caffemodel 不完整;三是 OpenCV 版本太老,对 Caffe 模型某层支持不全,比如在 3.x 上读 fp16 caffemodel 就容易失败。
解决:先用绝对路径定位文件是否存在。最简单的是写死项目绝对路径,或者用 os.path.join(os.path.dirname(file), ...) 拼路径,不要依赖当前所在目录。文件名确认是 deploy.proto.txt,注意有些解压软件会改名成 deploy.proto.txt.txt,扩展名错了也会报解析失败。版本上别用 3.x 的老 OpenCV,至少升到 4.x。
5.3 识别结果里训练集的人也被标成 unknown
现象:模型训练后训练准确率接近 98%,可 recognize_face.py 把训练图片里本人识别成 unknown,偶尔还标错成别人。
原因:概率阈值设太高只是表象,更深一层是样本量太少或样本过于单一,SVM 决策边界撑得太紧。predict_proba 对训练集外稍远一点的点给出偏低概率,一旦阈值到 0.9,真人也进不了门。
解决:先把阈值降到 0.5 看结果,如果恢复识别,再逐步回调。同时检查每人样本数,低于 15 张先去补数据。补数据时别只加正脸照,把侧脸、低头、戴眼镜、亮暗差异都加进去。最后一点经验:对 embedding 做 L2 归一化再进 SVM,有时比调阈值更有效,但资源默认不做,自己改要注意保持训练和识别一致。
5.4 训练时 labels 和 embeddings 数量对不上,或者 train_test_split 报错
现象:train_face.py 里 train_test_split 报错,提示样本数不足,或者 data["names"] 长度比 data["embeddings"] 少。
原因:face_embeddings.py 会跳过检测不到人脸的图片,导致图片总数和实际向量数不一致。有时候某个文件夹整组照片都没检测到人脸,那这一整类就完全没有向量进入训练,SVM 根本不认识这个人。
解决:在 face_embeddings.py 跑完后,打印每类成功提取的向量数量。我一般会顺手做一个检查脚本,遍历 embeddings.pickle 里的 names,统计每个类有多少条,少于 10 条的直接回去补图。补图时挑清晰、人脸占比大的,避免那种一张合影里人脸只占几十像素的图。
5.5 视频识别 FPS 太低,看起来像幻灯片
现象:recognize_video.py 用 CPU 跑 1080p 视频,帧率只有 3~5,人物一动就丢框。
原因:每一帧都跑 300x300 的 SSD 检测,再对每个检测框跑一次 96x96 的 FaceNet,等于每帧至少两次深度模型前向计算,CPU 完全顶不住。视频分辨率越大,每帧处理时间越长。
解决:最直接的是把帧缩小一半再送检测,检测框坐标按比例放大回原图。其次是跳帧,每隔 1~2 帧做一次完整识别,中间帧只画上一次结果。如果想让框跟手,加一个 OpenCV tracker,检测到人脸后交给追踪器,只有追踪丢了才重新跑检测。具体做法第 6 章再给伪代码。
这五条覆盖了从环境、文件、数据到性能的主要翻车点。如果都避开了,这份资源离可以日常使用就差进阶那一步。
6. 进阶用法:把识别器接进门禁/考勤场景,并验证模型真实上限
门禁/考勤场景与人脸识别 demo 最大的区别是实时性和误判成本。把这份资源接进去,要做三件事:性能优化、阈值调优、上线验证。
6.1 跳帧 + 追踪,避免每帧都跑深度模型
我的常见做法是把检测结果缓存到本地变量,用 OpenCV KCF tracker 负责帧间跟踪。注意 tracker 接口在 opencv-contrib-python 包里,普通 opencv-python 可能没有这个函数,没有就只做跳帧。
tracker = cv2.TrackerKCF_create() # 需要 opencv-contrib-python frame_skip = 2 while True: ok, frame = cap.read() if not ok: break frame_idx += 1 if frame_idx % frame_skip == 0: boxes = detect_faces(frame, conf_threshold=0.7) if len(boxes) > 0: x1, y1, x2, y2 = boxes[0][1] tracker.init(frame, (x1, y1, x2 - x1, y2 - y1)) # 此处只对 tracker 框做 embed + SVM 识别 else: ok, box = tracker.update(frame)跳帧和追踪配合,可以把 CPU 上的人脸识别成本从每帧检测降低到“每两帧一次检测 + 每帧一次跟踪”。注意 tracker 的初始框坐标要是整数,且宽高为正,否则 OpenCV 会报错。
6.2 用验证集找概率阈值
不要拍脑袋设 0.6。每类留出 3~5 张不参与训练,单独跑识别,统计不同阈值下的正确率和 unknown 率。简单做法是遍历阈值:
for threshold in [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]: acc = calc_accuracy(val_data, threshold) print(threshold, acc)选择“误识率尽量低、unknown 率可接受”的那个点。门禁机通常把 0.7 当一个不错起点,考勤机对漏识别容忍度低,可以放到 0.5。
6.3 新员工入库与模型更新
这份资源是离线批量训练,新增一个人最简单的方式是把新人的图片放进 dataset,重跑 face_embeddings.py 和 train_face.py。如果已经上线,识别端只需要重新加载新的 recognizer.pickle,不用改识别脚本。样本积累多了,可以试试把 C 调小一点,避免 SVM 对老员工过拟合。
在那之后,我每次拿到新数据集都会强制走一遍“先跑 embeddings 看每类向量数量,再训练”的流程,数量不够绝不进入下一步。这个小习惯帮我避开了至少一半的人脸识别项目翻车时间,希望帮到你。
本文还有配套的精品资源,点击获取