首页 / 资讯中心 / 文章详情

OpenCV人脸识别实战:从Haar/DNN检测到LBPH训练与实时识别

OpenCV人脸识别实战:从Haar/DNN检测到LBPH训练与实时识别 ★ FEATURED ARTICLE
简介这份压缩包面向具备Python基础、希望动手掌握OpenCV人脸检测与识别流程的学习者围绕明星人脸图像展开实战演练帮助理解Haar级联分类器、预处理、特征提取与匹配等机器视觉关键环节。包内共120个文件约51.99MB以110张jpg和5张jpeg人脸样本图片为主另含2个xml级联分类器文件、1个py脚本、1个txt说明和1个png辅助图可用于加载模型、批量测试与参数对比。已有464人学习说明其在入门实战场景中具有一定参考价值。读者可借助脚本串联灰度化、直方图均衡化、detectMultiScale检测等步骤结合样本图片观察不同参数下的检测效果并进一步参考LBPH、EigenFace、FisherFace等特征提取与匹配思路完成从检测到识别的实践闭环。1. 从一张糊掉的抓拍说起opencv 人脸识别这套包到底解决什么问题上个月帮一个做园区考勤的朋友看现场摄像头装在门口斜上方逆光人走过去只留半张侧脸。他们拿网上一份 demo 改了两天检测框一会儿有一会儿没有识别结果随机跳号。问题不在于算法多高级而在于整条链路没人拆开看过检测用哪个模型、样本怎么采、标签怎么对、阈值定在哪一步。这份 opencv 人脸识别实战操作包价值就在于把这条链路摊平了——采集、训练、识别三个脚本各自独立Haar 级联和 DNN 两条检测路线都保留识别端用 LBPH纯 CPU 就能跑不用显卡。适合三类人要交课程设计或毕设的学生、需要给小场景快速搭离线原型的工程师、以及想搞明白机器视觉里人脸从像素走到 ID 这条线的初学者。python 环境装好就能动手但想跑出能用的结果后面几章的参数和坑得先过一遍。2. 环境与目录先理顺两条检测路线选错了后面全是白干拿到压缩包之后最忌讳的动作是双击解压、直接python xxx.py。人脸识别这套东西对环境比一般脚本敏感得多cv2装错一个包跑到训练那一步才报错前面采集的一百多张样本全白采。这一章先把依赖装对、把目录职责看清楚再决定检测走哪条路线后面所有操作都建立在这个基础上。2.1 装 opencv-contrib 而不是 opencv-python很多人第一反应是pip install opencv-python装完cv2.imshow能用就以为环境好了。但 LBPH 识别器在cv2.face这个子模块里它属于 contrib 扩展包主包根本没有。我见过最快的翻车方式是主包和 contrib 包同时装进同一个环境后装的把先装的覆盖掉一半cv2.__version__看着正常cv2.face时有时无。# 先清干净两个包不能共存 pip uninstall -y opencv-python opencv-python-headless opencv-contrib-python # 只装 contrib 版本它已经包含主包全部功能 pip install opencv-contrib-python # 顺手把科学计算和可视化依赖补齐 pip install numpy matplotlib装完必须验证别凭感觉。下面这段检查脚本我每次搭环境都会跑一遍它同时确认版本、cv2.face是否可用、Haar 模型目录是否存在三个条件缺一个都说明环境有问题。import cv2 import os print(cv2 版本:, cv2.__version__) print(cv2 安装路径:, cv2.__file__) # 关键检查LBPH 识别器所在模块 assert hasattr(cv2, face), 缺少 cv2.face说明装的不是 opencv-contrib-python recognizer cv2.face.LBPHFaceRecognizer_create() print(LBPH 创建成功:, recognizer) # Haar 级联模型随包分发路径由 cv2.data 提供 haar_dir cv2.data.haarcascades print(级联模型目录:, haar_dir) print(正面人脸模型存在:, os.path.exists(haar_dir haarcascade_frontalface_default.xml))cv2.__file__那一行是关键输出的路径里如果带contrib字样基本就对了如果指向一个叫cv2的普通目录而你又装过两个包那就得回退重装。cv2.data.haarcascades是 OpenCV 内置的模型目录常量它会跟着安装位置走比手写绝对路径靠谱得多换台机器也不用改代码。虚拟环境这一步别省。人脸识别项目往往要配numpy、matplotlib、可能还有flask做接口跟系统里其他项目的依赖打架是常事。python -m venv venv建一个激活之后再装上面那些包出问题直接删目录重来成本最低。2.2 目录职责拆开看三个脚本各管一段这份包的结构是典型的「三段式」采集负责把摄像头画面切成人脸小图落盘训练负责把小图读进来喂给 LBPH 生成模型文件识别负责加载模型做实时推理。三条链路之间只通过磁盘文件耦合这个设计看着土实际非常好用——采集中断了不用重训模型效果差可以单独换数据集重训识别端可以脱离摄像头用图片跑回归测试。脚本角色输入输出出错时的典型症状样本采集摄像头实时帧每人一个子目录内含灰度小图样本糊、人脸框偏、目录为空模型训练数据集目录树.yml模型文件 标签映射表训练报类型错、识别永远返回同一 ID实时识别模型文件 摄像头画面叠加姓名框框在但名字错、帧率掉到个位数单图测试模型文件 静态图片终端打印 ID 与置信度同一张图两次结果不同我要强调标签映射表这一项。很多人训练完只存.yml标签和姓名的对应关系靠代码里写死一个列表。下次数据集目录顺序变了模型认的人就全错位而且错得很安静——不报错只是把 A 认成 B。常见做法是训练时顺手把一个id - 姓名的 JSON 落盘识别时读它这是整套流程里最便宜的一颗后悔药。2.3 Haar 与 DNN 两条检测路线的取舍包里同时留了两套检测代码不是冗余。Haar 级联是 OpenCV 自带的经典方案靠积分图加速的矩形特征加 AdaBoost 级联分类器优点是模型小、CPU 上快、开箱即用缺点是侧脸和遮挡基本没戏逆光容易漏检。DNN 那套是 SSD 结构的人脸检测网络输入固定 300×300对角度和光照的鲁棒性明显好一截代价是要额外加载两个模型文件跑起来更吃 CPU。维度Haar 级联DNN SSD模型来源随 OpenCV 分发路径由cv2.data给出需要 prototxt 与 caffemodel 两个文件正面清晰人脸快且准准但慢 25 倍侧脸、低头漏检率高明显更好小脸小于 60 像素需调minSize容易漏缩放输入后仍可检出参数调试成本scaleFactor/minNeighbors影响大主要调置信度阈值适用场景门禁、考勤、固定机位近距离抓拍、监控、角度不固定的画面我的建议是先用 Haar 把全流程跑通确认数据集、训练、识别三段没问题再换 DNN 提升鲁棒性。反过来做一旦结果不准你分不清是检测框歪了还是 LBPH 样本不够排错会非常痛苦。DNN 那套的加载方式如下注意blobFromImage的第三个参数必须和模型训练时的输入尺寸一致均值减掉的三个数也不能改改了准确率会莫名其妙往下掉。import cv2 # 两个模型文件放在包内指定目录按实际路径改 net cv2.dnn.readNetFromCaffe( models/deploy.prototxt, models/res10_300x300_ssd_iter_140000.caffemodel ) def detect_dnn(frame, conf_thres0.6): h, w frame.shape[:2] # 1.0 是缩放系数不在这里做归一化(300,300) 是网络固定输入 # 后三个数是 BGR 三通道均值必须与训练时保持一致 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]): conf detections[0, 0, i, 2] if conf conf_thres: continue # 网络输出是 0~1 归一化坐标乘回原图尺寸 x1 int(detections[0, 0, i, 3] * w) y1 int(detections[0, 0, i, 4] * h) x2 int(detections[0, 0, i, 5] * w) y2 int(detections[0, 0, i, 6] * h) boxes.append((x1, y1, x2 - x1, y2 - y1)) return boxesconf_thres默认 0.6 是我在一个室内光照场景下调出来的逆光严重的画面可以降到 0.5 换召回率代价是误检变多。检测框坐标一定要裁剪到图像范围内SSD 偶尔会输出负数或越界的坐标直接拿去切 ROI 会抛异常。提示DNN 模型的输入尺寸写死在网络结构里改成 640×640 不会更快也不会更准只会报维度不匹配。3. 采集自己的样本从摄像头读帧到 200×200 灰度图落盘识别准不准八成取决于这一步。LBPH 本质上是拿局部纹理的直方图做比对样本里光照、角度、尺度越统一比对的分布越稳定。我见过有人拿公开人脸库混着自己的摄像头样本一起训结果现场一个都认不出来——不是算法不行是两种来源的图像分布差太远。3.1 读帧、检测、裁 ROI、落盘采集脚本的核心逻辑是读一帧检测人脸裁出 ROI缩放成固定尺寸按序号存盘。中间加一个时间间隔控制避免连续几帧几乎是同一张图浪费样本配额。import cv2 import os import time SAVE_DIR dataset PERSON_ID u001 # 每个人一个子目录名字就是标签来源 MAX_NUM 100 # 每人采集张数 INTERVAL 0.15 # 采样间隔秒避免连续重复帧 SIZE (200, 200) # 统一尺寸训练时不再缩放 os.makedirs(os.path.join(SAVE_DIR, PERSON_ID), exist_okTrue) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) count 0 last_t 0.0 while count MAX_NUM: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # scaleFactor 1.2 比 1.1 快很多minNeighbors 5 能压掉大部分误检 faces cascade.detectMultiScale( gray, scaleFactor1.2, minNeighbors5, minSize(80, 80) ) now time.time() for (x, y, w, h) in faces: if now - last_t INTERVAL: continue roi gray[y:y h, x:x w] roi cv2.resize(roi, SIZE, interpolationcv2.INTER_AREA) cv2.imwrite(os.path.join(SAVE_DIR, PERSON_ID, f{count:04d}.jpg), roi) count 1 last_t now cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, f{count}/{MAX_NUM}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(collect, frame) if cv2.waitKey(1) 0xFF 27: # ESC 提前结束 break cap.release() cv2.destroyAllWindows()几个参数值得掰开说。INTERVAL 0.15意味着每秒采 6 到 7 张100 张大概 15 秒这个节奏够你缓慢转头、改变表情又不至于采到一堆重复帧。minSize(80, 80)是过滤远处小脸低于这个尺寸裁出来的图糊得没法用训练进去只会污染直方图。scaleFactor从 1.1 提到 1.2检测耗时能降三成左右代价是极小的人脸会漏固定机位场景完全划算。cv2.resize用INTER_AREA而不是默认的双线性缩小图像时它的抗锯齿效果更好直方图特征更干净。这一行不影响速度但影响后面识别的稳定性属于典型的「改了没感觉、不改有差别」的细节。3.2 样本规格数量、光照、角度怎么配采集最容易犯的错是坐在同一个位置、同一个姿势连拍一百张然后抱怨模型换个角度就不认。LBPH 没有数据增强样本的多样性完全靠采集阶段补。维度建议原因单人张数60200 张太少直方图不稳太多训练变慢且边际收益低光照条件至少 3 种顺光、侧光、室内灯灰度直方图对亮度分布极敏感头部角度正脸、左右各偏 1530 度侧脸时 Haar 本身就容易漏样本里得有表情自然、微笑、闭嘴各来一些嘴部纹理变化会显著影响局部特征遮挡眼镜戴与不戴分开采镜框边缘是强纹理混采会让特征发散背景有变化更好裁出来的 ROI 边缘可能带一点背景纹理我的习惯是一个人采三轮第一轮正对镜头把基础样本打底第二轮左右转头各采二十张第三轮换个房间再补一批。这样训出来的模型在现场换个站位也不会立刻失效。命名规范也得提前定死。用子目录名 人员标识、文件名 序号.jpg这种结构训练脚本一读目录就能拿到标签不需要额外的标注文件。目录名直接用人名或工号都行但别用中文路径——某些环境下cv2.imread读中文路径会返回None排查起来很浪费时间。3.3 采集完先自检别急着训练一百张图里混进几张糊的、过曝的、只剩半张脸的训练时不会报错只会让识别在特定角度下莫名其妙地失败。采完先跑一遍体检脚本把异常样本挑出来删掉比事后调参数有效得多。import cv2 import os DATA_DIR dataset for person in sorted(os.listdir(DATA_DIR)): person_dir os.path.join(DATA_DIR, person) if not os.path.isdir(person_dir): continue sizes, brights, blurs set(), [], [] for fn in os.listdir(person_dir): img cv2.imread(os.path.join(person_dir, fn), cv2.IMREAD_GRAYSCALE) if img is None: print(f[读取失败] {person}/{fn}) continue sizes.add(img.shape) brights.append(float(img.mean())) # 平均亮度 blurs.append(cv2.Laplacian(img, cv2.CV_64F).var()) # 清晰度越大越清楚 print(f{person}: 数量{len(brights)} 尺寸集合{sizes}) print(f 亮度 min/max {min(brights):.1f} / {max(brights):.1f}) print(f 清晰度 min {min(blurs):.1f})判断标准很直接尺寸集合必须只有一个元素出现两个说明混进了没缩放的图亮度最低值低于 30 基本是欠曝超过 220 是过曝这两类图在识别时会表现为「同一个人换个位置就不认」清晰度低于 20 的图可以直接删那是严重散焦或者运动模糊。注意自检脚本不要写成自动删除。先打印出来人工过一眼我吃过一次亏——把整组低对比度的样本误删了而那恰好是现场真实光照条件。4. 训练 LBPH 识别器三个参数与一条拒识阈值数据集就位之后训练本身只有几行代码真正花时间的是想清楚三件事LBPH 的三个参数怎么定、标签怎么和姓名对上、置信度阈值卡在哪。这三件事任何一件没想明白模型跑起来都能出结果但结果没法用。4.1 LBPH 在算什么三个参数各管什么LBPH 全称局部二值模式直方图。它的流程是对每个像素取它周围一个半径为radius的圆环上的neighbors个采样点把采样点的灰度跟中心像素比大小得到一串 0/1 二进制数这就是一个 LBP 编码整幅图算完之后把图像划分成grid_x × grid_y个格子每个格子单独统计 LBP 编码的直方图最后把所有格子的直方图拼起来就是这张人脸的特征向量。识别时比较两张图的直方图距离距离越小越像。radius决定采样环的大小值越大捕捉的纹理尺度越粗。neighbors是环上的采样点数8 是标准配置。grid_x和grid_y决定分块粒度分得越细对局部结构越敏感但也越容易受姿态变化干扰。参数常用值调大的效果调小的效果radius1关注更大尺度纹理对噪声更宽容抓细节但对轻微模糊敏感neighbors8编码更丰富计算变慢超过 16 边际收益很低特征变粗区分度下降grid_x/grid_y8 / 8局部区分力强姿态一变容易崩全局性强不同人之间容易混图像尺寸200×200细节多训练慢样本少时过拟合特征不足认不准我一般从radius1, neighbors8, grid_x8, grid_y8起步这个组合在 200×200 的输入上是个比较稳的平衡点。如果你的人脸样本尺寸改成了 100×100grid别超过 6否则每个格子太小直方图统计没有意义。4.2 训练脚本标签映射必须落盘训练这段代码不长但有两个地方特别容易写错labels的类型和目录遍历的顺序。import cv2 import os import json import numpy as np DATA_DIR dataset MODEL_PATH trainer.yml LABEL_MAP labels.json SIZE (200, 200) faces, labels [], [] name_map {} # sorted 保证每次遍历顺序一致否则标签会错位 for idx, person in enumerate(sorted(os.listdir(DATA_DIR))): person_dir os.path.join(DATA_DIR, person) if not os.path.isdir(person_dir): continue name_map[idx] person # 整数 ID - 人员标识 for fn in sorted(os.listdir(person_dir)): img cv2.imread(os.path.join(person_dir, fn), cv2.IMREAD_GRAYSCALE) if img is None: continue if img.shape ! SIZE: img cv2.resize(img, SIZE) faces.append(img) labels.append(idx) # labels 必须是 int32 的 numpy 数组传 list 会直接报错 recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) recognizer.train(faces, np.array(labels, dtypenp.int32)) recognizer.save(MODEL_PATH) with open(LABEL_MAP, w, encodingutf-8) as f: json.dump(name_map, f, ensure_asciiFalse, indent2) print(f训练完成{len(set(labels))} 人 / {len(faces)} 张样本) print(标签映射:, name_map)sorted(os.listdir(...))这两处是重点。os.listdir返回的顺序由文件系统决定同一台机器上删除再新建一个目录顺序就可能变。训练时标签是 0、1、2识别时如果按另一套顺序去解释人会整体错位而且不报错。np.array(labels, dtypenp.int32)里的dtype别省。train()对标签类型有要求传 float 或者 Python list 在某些版本上会抛类型错误报错信息还不直观。标签映射写进 JSON 之后识别端就只依赖这一个文件换机器、改目录名都不影响。这是我在这类项目里坚持的一个习惯凡是跨进程传递的对应关系一律落盘不放在代码里。4.3 置信度不是百分比阈值要自己扫recognizer.predict()返回两个值标签和 confidence。这里有个巨大的认知陷阱——confidence 不是「相似度百分比」它是直方图距离越小表示越像0 是完全一致。很多人看到 confidence 是 30 就以为「相似度 30%」逻辑整个反了。因此判断「是不是这个人」不能靠 confidence 大小直接下结论得先定一个阈值超过阈值判为未知人。阈值定多少取决于你的样本质量常见范围在 50 到 80 之间需要拿数据扫出来。import cv2 import json import os recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) with open(labels.json, encodingutf-8) as f: name_map {int(k): v for k, v in json.load(f).items()} def predict_with_threshold(gray_face, threshold70.0): label, confidence recognizer.predict(gray_face) if confidence threshold: return name_map.get(label, unknown), confidence return unknown, confidence阈值扫描的做法是从数据集里留出每人 10 张没参与训练的图统一跑一遍统计「同人样本的 confidence 分布」和「异人样本的 confidence 分布」。两条分布的交叠区域就是阈值该落的位置。如果交叠区很宽说明样本区分度不够回头补样本比调阈值有效。提示阈值调低更严格能减少误认但会把本人挡在门外调高则相反。考勤这种场景宁可严格宁可让人多刷一次也别把 A 记成 B。5. 避坑与排查五个把我卡到凌晨的现场问题这一章是我自己踩过、也帮别人排查过的真实故障类型按现象、原因、解决三段写。这些问题有个共同点程序不崩只是结果不对所以特别耗时间。5.1 依赖与环境的坑第一条AttributeError: module cv2 has no attribute face现象很明确训练脚本跑到cv2.face.LBPHFaceRecognizer_create()就挂。多数人第一反应是「版本太老」于是升级 OpenCV升完还是报同样的错。原因基本是两个装的是opencv-python而不是opencv-contrib-python或者两个包都装过后装的把先装的覆盖了cv2目录里只剩主包的内容。这个错误最坑的地方在于cv2.imshow、cv2.VideoCapture全都正常你会误以为环境没问题。解决方式是彻底卸载再单独装 contribpip uninstall -y opencv-python opencv-python-headless opencv-contrib-python pip install opencv-contrib-python python -c import cv2; print(cv2.__file__); print(hasattr(cv2,face))如果第二条输出True就对了。输出False去看cv2.__file__打印的路径那个目录下如果没有cv2的.so文件而是一堆.py说明环境里有残留直接删掉整个site-packages/cv2目录重装。5.2 数据与训练的坑第二条训练时报标签类型错误或者train()参数不匹配现象是recognizer.train(faces, labels)抛出类型相关的异常换成 numpy 数组之后又提示尺寸不一致。原因有两层。一是labels传了 Python list 或 float 数组LBPH 要求整型数组二是faces里的图像尺寸不统一采集时缩放过了但中间手动补进去几张原图混在一起训练就会崩。解决办法是把两件事都做死训练前统一resize标签统一转int32。前面 4.2 的脚本里两处都做了。额外加一句断言更保险assert all(f.shape SIZE for f in faces), 存在尺寸不一致的样本 assert len(faces) len(labels), 图像与标签数量不匹配第三条识别永远返回同一个人或者所有人被认成 ID 0现象是模型能加载预测也返回值但不管谁站到镜头前输出的都是同一个名字。原因是标签映射错位。训练时用os.listdir的自然顺序分配 0、1、2识别时又用另一次os.listdir的结果去反查姓名两次顺序不一致模型输出的 0 就被解释成了另一个人。或者更常见的训练脚本里enumerate的起点被改过从 1 开始编号而识别端默认从 0 开始。解决办法是标签映射必须落盘、必须用sorted、识别端只读 JSON 不自己推。如果已经训好了模型但映射丢了可以重新训一次成本也就几十秒比反推映射快得多。5.3 运行时的坑第四条实时画面卡成幻灯片帧率掉到个位数现象是采集阶段挺流畅识别阶段一开摄像头画面一秒跳两三下鼠标都拖不动。原因是每一帧都在原分辨率上跑完整检测加识别。640×480 的帧跑一次 Haar 检测大概几十毫秒加上 LBPH 预测和多脸循环一帧轻松超过 200 毫秒。如果换成 DNN情况更糟。解决办法是降采样加隔帧检测。检测前把帧缩到一半甚至三分之一坐标再按比例放大回去检测不必每帧都做每 3 帧做一次中间帧沿用上一次的框。人脸在画面里不会一帧移动半个屏幕这个近似完全够用。另外detectMultiScale的scaleFactor从 1.1 调到 1.2 或 1.3耗时能再降一截。第五条confidence 只有二十几还是认错人现象是阈值设了 70预测出来的 confidence 是 25按理说非常像但名字是错的。原因是把 LBPH 的 confidence 当成了绝对可信度。它本质是直方图距离会受到样本数量、图像尺寸、grid 划分的多重影响。样本少的人直方图稀疏跟谁都容易算出小距离某个人的样本光照特别统一也会让他的类内距离异常小把别人吸过来。解决办法是不要迷信单一阈值做两件事一是保证每人样本量大致均衡别出现一个人 200 张、另一个 30 张二是用留出集统计每个 ID 的类内距离分布按人设定阈值而不是全局一个数。工程上更稳的做法是取 Top-2 结果如果第一名和第二名的距离差小于某个 margin直接判未知不硬给结论。注意第五条这类问题在现场排查时最容易被误判成「模型不行」实际上是数据分布不均。先把每个 ID 的样本量和平均类内距离打出来看一眼往往一眼就能定位。6. 实时识别收尾多脸降采样与一套能自证的验证流程识别脚本的骨架跟采集脚本差不多差别在于每帧要做检测加预测两件事还要处理多个脸、画框、写日志。下面这段是隔帧检测的写法实测在普通笔记本上能把帧率从 5fps 拉回 20fps 以上。import cv2 import json recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) with open(labels.json, encodingutf-8) as f: name_map {int(k): v for k, v in json.load(f).items()} cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) THRESHOLD 70.0 SCALE 0.5 # 检测降采样比例 SKIP 3 # 每 3 帧检测一次 cap cv2.VideoCapture(0) frame_id, boxes 0, [] while True: ok, frame cap.read() if not ok: break frame_id 1 if frame_id % SKIP 0: small cv2.resize(frame, (0, 0), fxSCALE, fySCALE) gray_small cv2.cvtColor(small, cv2.COLOR_BGR2GRAY) raw cascade.detectMultiScale(gray_small, 1.2, 5, minSize(40, 40)) # 坐标还原回原图尺度 boxes [tuple(int(v / SCALE) for v in (x, y, w, h)) for (x, y, w, h) in raw] gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for (x, y, w, h) in boxes: roi cv2.resize(gray[y:y h, x:x w], (200, 200)) label, conf recognizer.predict(roi) name name_map.get(label, unknown) if conf THRESHOLD else unknown color (0, 255, 0) if name ! unknown else (0, 0, 255) cv2.rectangle(frame, (x, y), (x w, y h), color, 2) cv2.putText(frame, f{name} {conf:.0f}, (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) cv2.imshow(recognize, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()坐标还原那一行是新加的坑点检测在缩放图上做框的坐标是缩放图的坐标系直接拿去切原图 ROI 会整体偏移。除以SCALE再取整误差在一两个像素内肉眼看不出。SKIP设成 3 是个经验值人在画面里移动慢的话可以设 5检测开销再降一半。模型到底行不行不能靠站在摄像头前反复试。一套能自证的验证流程是这样的从采集数据里每人随机留出 20% 不参与训练作为测试集对这些图逐张跑预测记录真实标签、预测标签、confidence最后算准确率和拒识率。指标计算方式关注点识别准确率正确识别数 / 测试总数整体水平低于 85% 说明样本或参数有问题误认率认错人的次数 / 测试总数最不能接受的错误考勤场景要压到 1% 以下拒识率判为 unknown 的次数 / 测试总数太高说明阈值过严或样本不足类内平均距离同人样本的 confidence 均值判断某个人的样本一致性我一般会先跑这个测试集把误认的那几张图单独挑出来看。十次里有八次问题出在样本上那张图本身是侧脸而训练集里没有类似角度。这时候补样本比调任何参数都快。从那以后我每次交付这类识别项目都强制走一遍「留出集 混淆矩阵 阈值扫描」三步不管现场演示多顺利。演示是骗人的留出集不会。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站