ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLO与MobileFaceNet的离线人脸识别会议签到系统实践

基于YOLO与MobileFaceNet的离线人脸识别会议签到系统实践 每年毕业设计季人脸识别签到都是热门选题但很多同学拿到“基于 YOLO MobileFaceNet 的会议签到系统”这个题目时第一反应往往是懵的YOLO 不是做目标检测的吗怎么跟人脸识别扯上关系MobileFaceNet 又是什么来头这两个模型怎么配合这篇文章我就以自己实际做过的一个同类项目为蓝本把整个系统的设计思路、模型分工、训练细节、代码实现和踩坑记录全部拆开讲清楚。无论你是正在做毕业设计还是工作中需要快速搭一套离线人脸签到方案都能从这里找到可以直接抄作业的路径。我会尽量少讲虚的多讲“我当时是怎么做的、为什么这么做、遇到了什么问题”让你少走弯路。1. 项目定位这个签到系统到底要解决什么问题1.1 传统会议签到的痛点先说场景。公司周会、高校讲座、行业协会活动只要有几十人以上的场合签到就是个麻烦事。纸质签到表不仅效率低代签情况严重会后还要人工录入电脑录入错了又是一通折腾。后来有些地方用扫码签到但又存在“码传人”的问题一个人能帮全组人扫。再后来有了人脸识别门禁机但那东西贵而且固定安装在门口灵活性差还要外接电源和网络临时会议室根本不好部署。这个项目要解决的就是“在普通电脑或嵌入式设备上靠一个普通 USB 摄像头实现本地化、离线、秒级完成的人脸识别签到”。之所以强调离线是因为很多会议场所的网络环境不确定而且人脸数据属于敏感信息最好不出本地。我采用的硬件方案不复杂一台带摄像头的电脑或者树莓派加 USB 摄像头就能跑起来整个系统。1.2 为什么是 YOLO MobileFaceNet而不是“一个大模型全搞定”刚开始我也想过现在人脸识别框架那么多比如 FaceNet、ArcFace、InsightFace直接调用不就行了为什么还要引入 YOLO后来在实际测试里发现现成的人脸识别模型虽然是端到端的但它是“从整张图里找最大的人脸并识别”一旦画面里有多个目标或者目标在远处、侧脸、遮挡状态下效果会明显下降。这就是 YOLO 登场的意义。YOLO 负责第一步目标检测。它会在画面里把所有“人”或“人脸”的位置框出来不管你是正脸侧脸、近处远处、一个人还是八个人它都能给出候选框。MobileFaceNet 负责第二步身份识别。它把 YOLO 剪裁出来的那一小块人脸区域压缩成一个高维特征向量然后和库里注册好的特征向量做相似度比对相似度超过阈值就判定为“这个人”。分工之后好处很明显检测和识别互不干扰可以分别优化各自的问题。YOLO 检测到的人脸区域更精准喂给识别网络的图片噪声更少识别率更高。系统可以快速扩展想提高检测能力就换更强的 YOLO 版本想提高识别精度就换更大的识别骨干网络二者互不影响。1.3 技术选型的核心矛盾准确率与速度的平衡人脸识别类项目最怕两件事一是识别不准误判、漏判二是速度太慢画面卡顿、识别要好几秒。这两个需求在实际工程里往往是矛盾的。YOLO 目前已经迭代到了 v8、v9、v10甚至 v11 也出来了。版本越新精度越高但模型体积和计算量也越大。MobileFaceNet 则是一个专门为移动端和嵌入式设备设计的高效人脸识别网络模型非常小在 CPU 上也能跑得很快精度比大模型略低但配合 ArcFace 损失函数训练后在 LFW 这类数据集上也能达到 99% 以上的准确率。我的选择是 YOLOv8n MobileFaceNet。YOLOv8n 是 v8 系列里最小的版本参数量约 3.2M检测速度和精度都比较均衡在 CPU 上也能跑到 20 FPS 左右MobileFaceNet 的参数量更小不到 1M提取一次人脸特征在 CPU 上约 10 毫秒。这两个模型加起来整体推理速度远超过单纯用一个大模型的方案。注意学过深度学习的同学都知道模型选型从来不是看谁参数多、谁层数深而是看部署环境和业务需求。如果会议室电脑没有独立显卡就别选 YOLOv8x 这种大模型跑不动等于白搭。2. 系统架构与核心流程拆解2.1 整体流程检测、对齐、识别、签到整个系统的核心流程可以概括为四步视频帧采集通过 OpenCV 读取摄像头画面每帧图像送入检测模块。人脸检测YOLO 在画面中找出所有人脸框输出坐标和置信度。人脸对齐与识别将每个检测到的人脸框裁剪出来经过人脸对齐后送入 MobileFaceNet得到 128 维特征向量。特征比对与签到记录将当前特征向量与注册库中的所有特征做余弦相似度比对取最大值。若超过阈值则记为签到成功并将人脸框、时间、姓名等信息写入数据库。这个流程看起来简单实际实现时每一步都有不少细节。比如检测频率问题——如果每一帧都做完整流程CPU 会吃不消我会设计成检测每 5 帧做一次中间帧直接沿用上一次的结果这样既保证了流畅度又不会漏人。再比如人脸对齐这一步很多人会忽略。人脸识别网络对输入图片的要求很高如果人脸是歪的、偏的直接送去识别效果会打折扣。标准做法是先检测到 5 个关键点左眼、右眼、鼻尖、左嘴角、右嘴角然后通过仿射变换把人脸转正、缩放再送入识别网络。MobileFaceNet 官方权重一般包含关键点检测的预处理流程这部分一定要配置好。2.2 模块划分与工程结构一个好的毕设项目不能只有模型推理代码还要有工程化的模块划分。我当时把项目分成了这些目录dataset存放训练数据、注册人脸图片、测试视频。models存放 YOLO 权重、MobileFaceNet 权重以及特征提取的封装类。detectionYOLO 推理代码负责从视频帧中检测人脸框。recognitionMobileFaceNet 推理代码包括人脸对齐、特征提取、相似度比对。databaseSQLite 数据库操作封装学生/员工信息表、签到记录表、注册人脸特征表。ui图形界面用于手动注册人脸、启动签到大屏、查看签到结果。utils各种辅助函数比如画框、计算相似度、日志打印。这种分层设计的好处是如果后续想换掉某一个模块比如把 SQLite 换成 MySQL或者把 YOLOv8n 换成 YOLOv5s只需要改动对应的类其他模块完全不用动。毕设答辩时这也是一个很好的加分点可以向老师展示你考虑到了系统的可维护性。2.3 架构设计中容易忽略的三个问题第一个是特征库的更新策略。签到系统的注册库不是一层不变的新人要录入旧人要删除。MobileFaceNet 提取的特征向量是 128 维浮点数组直接存在 SQLite 表里就行注册时插入一条记录注销时删除一条记录不用重新训练模型这是它最大的优势。第二个是去重签到问题。同一个视频流中一个人会被连续多帧检测到如果不做去重系统会在一秒钟之内给同一个人签好几次到这显然不行。我的做法是每次签到成功后记录时间戳同一 ID 在 5 分钟内重复识别到不再次计入签到操作。第三个是多人同时签到的处理。会议室入口经常有一群人同时进来画面里会出现五六张脸。检测模块把它们全框出来识别模块逐个提取特征这些操作是独立的可以多线程并行处理避免前面一个人比对的时间阻塞后面所有人的签到。3. 模型训练数据准备、损失函数与训练策略3.1 YOLO 检测模型用预训练权重还是自己训练很多人一听到“YOLO 训练”就觉得头疼觉得要从零开始标注海量数据。实际根本不需要。YOLO 本身是在 COCO 数据集上预训练过的里面的 80 个类别本来就包含 person 类。人脸检测本质上就是“检测人的人脸”但 COCO 里并没有专门的人脸框标注所以如果直接拿预训练权重检测人脸效果不会太好它会把人头、身体、整个人都框出来。我当时采取的办法是二次微调在公开人脸检测数据集上把 YOLO 的 person 检测能力迁移到人脸检测上具体用的是 WIDER FACE 数据集。这个数据集有 3 万多张图片包含各种角度、遮挡、光照条件下的人脸框标注是人脸检测领域最常用的数据集之一。训练配置我用的是 YOLOv8n 的默认配置输入图片尺寸设为 640batch size 16训练 100 个 epoch。WIDER FACE 的标注格式需要转换成 YOLO 的 txt 格式也就是每个文件一行一个目标内容为 class x_center y_center width height坐标要归一化到 0-1 之间。这个转换代码我写了个脚本用 pandas 读标注 JSON再用 OpenCV 读图片尺寸做归一化几十行就搞定了。训练完成后模型在 WIDER FACE 验证集上 mAP 大约能到 0.92 左右实际摄像头场景下正脸、侧脸、戴眼镜、逆光环境基本都能稳定框出来近距离多个人脸也都能检测到。对于毕设来说这个精度完全够用。3.2 MobileFaceNet训练细节与关键参数MobileFaceNet 的网络结构是谷歌 MobileNetV2 的改进版它的核心是用深度可分离卷积替换普通卷积同时针对人脸识别的特点做了全局平均池化和瓶颈层的优化。这种结构能在极小的计算量下提取出判别力很强的人脸特征。训练 MobileFaceNet 要重点关心的是损失函数。早期的人脸识别用 Softmax Loss但 Softmax 只要求类别可分不要求类内紧凑、类间远离在真实场景下很容易翻车。现在主流做法是用 ArcFace Loss加性角度边距损失它在 Softmax 的基础上增加了一个角度边距让模型在训练时不仅把不同人分开还要尽可能把同一个人的特征聚在一起、把不同人的特征推开。ArcFace 有个关键参数叫 margin我设置为 0.5。这个值越大类内聚合越强但也可能导致模型过拟合训练集太小则区分度不够。0.5 是一个经验和实验折中的值在 LFW 上能到 99% 以上的准确率。训练数据集用的是 MS1MV2 或 Glint360K但这两个数据集完整版非常大普通学生党下载和训练都不现实。更灵活的做法是直接用 InsightFace 官方提供的在 MS1MV2 上训练好的 MobileFaceNet 预训练权重然后拿自己拍摄的会议场景人脸数据做一个轻量微调。我当时下载了预训练权重用自己实验室 20 个人的照片各 30 张做了微调效果提升非常明显同一个人的不同角度、不同光线条件都能稳定识别。3.3 训练时值得注意的“脏数据”问题训练人脸识别模型时最怕的是数据标签错乱。MS1MV2 这种大数据集是从互联网上抓取的里面有大量脏数据比如同一个人的照片里混入了另一个人图片分辨率过低、模糊甚至有些图片根本不是人脸。用脏数据训练出来的模型表现会很差但又很难排查。我当时做微调时自己拍摄的数据也犯过一个错给某人拍了 30 张照片但其中几张是他侧着身子和旁边人说话时抓拍的人脸区域只占了很小一块而且严重模糊。这几张“脏样本”导致模型在识别这个人时出现不稳定的情况。后面我把所有样本用 YOLO 先检测一遍人脸区域过滤掉置信度低于 0.9 的图片再统一裁剪、对齐到 112x112问题就消失了。这个经验后来我反复用到其他项目中任何情况下对待训练数据都要抱怀疑态度最好的办法是写个自动筛查脚本先用预训练模型对数据做一遍质量检查把低置信度的样本挑出来人工确认。宁可数据数量少一点也要保证数据质量过硬。4. 核心代码实现与部署实测4.1 YOLO 推理封装让检测模块可以随时替换YOLO 推理这块我用的方式是直接调用 ultralytics 库。这个库把推理封装得非常友好几行代码就能完成检测。但为了让代码结构更清晰我建议不要直接在主流程里调用 ultralytics而是封装成一个 FaceDetector 类from ultralytics import YOLO class FaceDetector: def __init__(self, weights_path, conf_thres0.5, img_size640): self.model YOLO(weights_path) self.conf_thres conf_thres self.img_size img_size def detect_faces(self, frame): results self.model.predict( sourceframe, confself.conf_thres, imgszself.img_size, verboseFalse ) boxes [] if results and results[0].boxes is not None: for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].cpu().numpy() conf float(box.conf[0]) if conf self.conf_thres: boxes.append([int(x1), int(y1), int(x2), int(y2), conf]) return boxes这样做的好处是主流程里只需要调用 detector.detect_faces(frame) 就能拿到所有人脸框如果以后想换 YOLOv5 或 YOLOv11只需要改这个类内部实现。我在实际项目里还做过一个测试把 YOLOv8n 换成 YOLOv5s 后只需要调整权重路径其他代码一行都不用动。4.2 MobileFaceNet 特征提取与比对MobileFaceNet 我用的是 insightface 项目中提取出来的模型权重配合 onnxruntime 做推理。onnx 格式的优势是不需要安装 PyTorch 就能跑推理部署起来非常方便在 CPU 上也能达到很好的性能。import cv2 import numpy as np import onnxruntime as ort class FaceRecognizer: def __init__(self, onnx_path, input_size(112, 112)): self.session ort.InferenceSession(onnx_path) self.input_name self.session.get_inputs()[0].name self.input_size input_size def get_embedding(self, face_img): face_img cv2.resize(face_img, self.input_size) face_img face_img.astype(np.float32) / 255.0 mean np.array([0.5, 0.5, 0.5], dtypenp.float32) std np.array([0.5, 0.5, 0.5], dtypenp.float32) face_img (face_img - mean) / std face_img np.transpose(face_img, (2, 0, 1)) face_img np.expand_dims(face_img, axis0).astype(np.float32) embedding self.session.run(None, {self.input_name: face_img})[0] embedding embedding.flatten() norm np.linalg.norm(embedding) return embedding / norm def match(self, embedding, db_features, threshold0.5): max_sim -1.0 best_id None for person_id, db_emb in db_features.items(): sim float(np.dot(embedding, db_emb)) if sim max_sim: max_sim sim best_id person_id if max_sim threshold: return None, max_sim return best_id, max_sim相似度的计算用的是余弦相似度而不是欧氏距离。MobileFaceNet 输出的特征向量我们做了 L2 归一化此时余弦相似度和向量内积是等价的取值范围在 -1 到 1 之间。阈值我设置为 0.5在测试里正脸、正常光线的相似度通常在 0.7 以上误识别人脸的相似度一般不会超过 0.3这个阈值给两者留了充分的缓冲空间。4.3 签到主流程与 UI 界面签到主流程用 OpenCV 持续读取摄像头帧每 5 帧做一次检测和识别。检测到人脸后如果有未注册的新面孔就在界面上显示“未登记”如果是已注册人员显示姓名、部门和签到时间。我用的 UI 框架是 PyQt5。网上很多人推荐 tkinter但 tkinter 做这种实时视频界面效果很一般刷新率低控件风格也老旧。PyQt5 的 QLabel 可以很方便地显示 OpenCV 的帧图像QTimer 可以定时刷新同时信号槽机制适合处理多线程识别结果。界面上我用两个区域左侧是实时视频画面右侧是签到记录表下方是注册新人和导出报表的按钮。签到记录存 SQLite。表结构很简单CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, department TEXT, registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE face_features ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, feature BLOB NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE attendance_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, check_in_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) );features 存 BLOB 类型用 numpy 的 tobytes() 转成字节流存进去读取时再用 np.frombuffer() 还原非常简单。签到记录支持按日期查询和导出 CSV 格式会议结束后可以直接把签到表发到群里省去人工录入的时间。4.4 实际部署经验性能数据与硬件条件我在两台设备上做过实测。一台是普通办公笔记本Intel i5-1240P16G 内存无独立显卡Win11另一台是树莓派 4B4G 版本。笔记本上的表现很理想YOLO 检测人脸框大约 45 毫秒MobileFaceNet 特征提取约 10 毫秒加上预处理和数据库写入单个人脸完整流程在 80 毫秒以内视频流能达到 20 FPS 以上画面流畅无卡顿。树莓派 4B 上就要差一些YOLO 检测约 250 毫秒MobileFaceNet 约 40 毫秒整体 FPS 只有 5 左右视频预览会有些卡顿但测试下来仍能稳定完成签到任务这得益于我用了“每 5 帧检测一次”的策略识别动作虽然慢一点但并没有漏人。如果你的部署环境比树莓派还弱还想要更快的速度可以把检测线程和识别线程分开。检测线程持续跑 YOLO识别线程用队列接收检测到的人脸框两个线程并行执行互不阻塞。我当时在树莓派上测试了这个方案FPS 能提升到 10 左右体验好了不少。5. 常见问题排查与避坑指南5.1 问题速查表这几个问题是同学之间交流时出现频率最高的我把当时的解决思路整理成了一张表问题现象可能原因排查方法解决方案人脸检测不到阈值设太高、人脸过小、光线太暗打印检测框观察置信度分数调低 conf_thres 到 0.3输入图尺寸改 640 或 800画面很卡每帧都做完整推理CPU 占用高查看 CPU 占用率和推理时间检测改为每 5 帧一次识别单独线程认识的人识别成“未登记”注册照片和现场光线差异大、角度不同打印相似度分数用现场环境重新注册照片或降低阈值到 0.4不同人被误识别成同一个人阈值太低、注册照片太模糊比对相似度分数提高阈值到 0.6重新采集清晰注册照片多人同时入画面有人被漏掉YOLO 对人群遮挡场景漏检查看单帧检测框数量把检测输入尺寸提到 640或换 YOLOv8s 版本摄像头打不开权限未开启、OpenCV 版本问题单独测试 cv2.VideoCapture(0)Windows 设置里开启相机权限升级 opencv-pythonSQLite 写入失败多线程同时写数据库出现锁冲突查看报错信息SQLite 连接加 timeout10写入操作加线程锁5.2 阈值调参的独家心得相似度阈值是整个人脸识别系统里最敏感的参数没有之一。阈值调太高会出现大量漏签参会人到门口刷半天脸卡在“未登记”体验非常差调太低会出现大量误签别人还没走到镜头前屏幕就弹出了他的名字。我踩过的最深的一个坑是在答辩前一周把阈值从 0.5 降到了 0.35想提高通过率。结果演示的时候屏幕旁边站了另一个同学系统把他误识别成了项目组成员直接签到成功场面一度非常尴尬。后来我学乖了阈值设置前会先做一组统计测试让每个人从距离摄像头 1 米、2 米、3 米的位置分别正面、侧面走一遍记录各自的相似度分布再找一个让所有正样本相似度都高于、所有负样本相似度都低于的中间值。以我的经验MobileFaceNet 配合 0.5 到 0.55 的阈值是最稳的。这个范围内正脸识别率能保持在 95% 以上误识别率接近于零。但如果会议室光线很差或者摄像头分辨率低建议注册人脸时直接在同样的光线下拍照效果比调阈值更明显。5.3 注册人脸照片的采集规范很多人以为注册人脸就是随便导入一张生活照就行了实际上注册照片的质量直接决定整个签到的成功率。我总结出几条硬性规范注册照片必须是人脸正面照闭眼、大笑、夸张表情都不行。照片分辨率至少要 200x200 以上越清晰越好。照片中脸部区域最好占整张图片的 60% 以上。一个人建议注册 3 到 5 张不同角度的照片正脸、轻微左偏、轻微右偏特征取平均值或全部入库。注册时最好在会议现场的实际光线下拍摄避免“注册时光线亮、现场光线暗”带来的特征漂移。如果你把这个规范写进系统的“用户注册”页面并加上照片实时预览功能相当于提前过滤掉了一批低质量注册数据会省去后期大量调参和投诉的时间。答辩时老师问起“这个系统的鲁棒性怎么保证”你也可以拿出这套采集规范作为论据。5.4 毕设答辩时的几个加分细节答辩时除了演示系统能用老师更关心的是“这个系统到底是不是你的你对它的理解有多深”。我准备了几个额外的加分项视频演示提前录制一段三分钟的系统使用视频包含注册、签到、多人同时签到、未登记人员提醒四种场景。现场演示因为摄像头角度、光线等问题可能翻车视频是保底方案。速度对比表做一张表格对比“纯 MobileFaceNet 直接识别整张图”和“YOLO MobileFaceNet 先检测后识别”两种方案在准确率、耗时的差异。数据说明这个系统为什么要拆成两段而不是一个模型做完。离线部署方案讲清楚系统在无网络环境下的运行流程模型文件都在本地数据库也在本地所有处理不出这台机器。这一点在隐私保护要求严格的场合非常加分。可扩展性说明说明如果会议规模变大可以把 SQLite 换成 MySQL把签到端做成云端服务用浏览器访问摄像头后台统一管理数据。我在答辩时讲到信息安全和本地化处理这一块儿的时候明显看到几个老师点头了。这个时代的数据隐私话题比较受重视而人脸数据又是强个人隐私能主动把这一点考虑进系统设计里会让你和其他只会跑通代码的同学拉开差距。5.5 本地化部署的一个坑C 打包还是 Python 脚本最后一个很实际的问题毕设展示时你是期望在自己的电脑上跑通就完事还是要把系统交付给别人使用如果是后者Python 环境的部署会非常痛苦什么 ultralytics、onnxruntime、PyQt5、torch光配置环境就能劝退一个非技术用户。我的建议是最终交付时至少提供两个东西。一个是完整源码和 requirements.txt 依赖清单方便评审老师核对和复现另一个是打包好的可执行程序。Python 打包推荐用 PyInstaller把主程序打包成 exe 文件。不过要注意onnxruntime 用 PyInstaller 打包时经常遇到模型文件路径找不到的问题处理办法是把 onnx 模型文件嵌入成 base64 字符串放在脚本里或者打包时用 --add-data 参数把模型路径加上。因为 PyInstaller 打包后文件运行路径和开发路径不一致路径拼接要用 sys._MEIPASS 处理这个细节我调了一个下午才搞定。还有一个更轻量的方案用 ONNX Runtime 的 WebAssembly 版本把整个系统做成浏览器网页应用用户直接用浏览器打开本地 HTML 页面调用摄像头完成签到完全不需要安装 Python 环境。不过这个方案对前端功底有一定要求如果你有精力可以试试作为一个技术亮点写进论文里很出彩。最后再分享一点个人体会做完这个项目之后我对“人脸识别系统”这件事的理解变了很多。以前总觉得人脸识别就是一堆高大上的算法和模型做完才发现真正花时间的不是训练模型而是清理数据、调阈值、适配各种实际场景的光线、角度和网络状况。模型只占了项目一半的工作量另一半是工程化的细节。如果你正在做这个题目我建议你先把 YOLO 和 MobileFaceNet 各自的推理单独跑通确认检测框和特征向量都没问题之后再合并成完整流程。不要一上来就写大而全的图形界面那样出了问题你都不知道是哪个环节挂了。还有所有核心参数置信度阈值、相似度阈值、检测间隔帧数一定要写在一个配置文件里面不要硬编码在代码中你后面调参的时候会感谢自己当初的这个决定。这套方案做完后可以扩展的方向也不少。比如把 YOLO 的检测类别从人脸扩展到“人脸 口罩检测”在疫情期间就很有用再比如把签到数据接上企业微信或钉钉机器人自动发送参会统计。希望这篇文章能帮你在毕设路上少踩几个坑顺利交出一份自己满意的作品。
返回列表