ARTICLE DETAIL

资讯详情

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

基于深度学习的人脸识别实验室自动签到与监控系统设计与实现

基于深度学习的人脸识别实验室自动签到与监控系统设计与实现 简介这是一套基于Python深度学习的实验室自动签到与监控系统完整项目面向高校计算机、人工智能、物联网等专业学生及教师适用于课程设计、毕业设计或项目演示覆盖人脸识别签到、实时监控告警、签到记录管理等核心环节。压缩包共包含379个文件主体为318个Python源文件另有UI界面文件、已训练模型权重pth、配置文件、IPython Notebook、PyInstaller打包的exe以及虚拟环境批处理脚本等整体仅3.8MB轻量但结构清晰部署文档齐全适合对照源码逐步复现。目前已有109人学习下载可作为课程设计或毕业设计的直接参考。项目属于高分通过导师审核的实战源码测试运行稳定可直接用于毕设或课设完整方案也可针对业务场景二次扩展——无论是快速落地演示还是钻研深度学习工程化细节都能从中获得可复用的代码与思路。1. 实验室自动签到与监控系统先想清楚要识别谁、在岗多久、异常怎么报警实验楼的考勤一直是个低技术含量却高管理成本的事。手写签到表补签成风刷卡机一台只能管一扇门指纹机在实验课前后排长队而真正的需求其实有两层第一层是“这个时段谁来过、待了多久、有没有按时离开”第二层是“无人的深夜里还有人滞留或闯入实验区”。两者放在一起做就是标题里自动签到与监控系统真正要解决的问题。这套系统的思路并不复杂摄像头负责看深度学习模型负责把画面转成“人”和“身份”业务逻辑负责把身份落成一条签到记录或一条告警。但落地时有几个容易被忽略的分水岭人脸检测与人脸识别是两条不同的技术链路录制视频里的人脸和现场直播流里的人脸阈值要分开调签到数据能不能在断电重启后完整补录比模型精度更影响口碑。接下来的内容按从理论到工程推进先拆识别链路与选型再给一套在普通办公电脑上就能跑通的 Python 环境与最小代码然后补齐数据库、摄像头采集、告警这类工程骨架最后放一个上线前必须做的验证方法。整篇按“先让模型跑出一个人脸框和一条特征向量再谈系统”的顺序来写。2. 签到为什么用深度学习识别链路拆解与模型选型2.1 人脸检测不是人脸识别别把两步混成一步第一次做这类项目的人最容易踩的坑是把“画面里有没有脸”和“这张脸是谁”当成同一个问题。检测detection解决的是位置问题输入一帧 640×480 的图像输出若干个人脸框和置信度常见做法是 RetinaFace、YOLOv5 的人脸变体或者 OpenCV 自带的 DNN 人脸检测器。识别recognition解决的是身份问题把检测出的那一小块人脸区域归一化到 112×112 之类的固定尺寸再送进特征提取网络输出一个高维向量。把两步分清楚工程上的收益很直接检测模型可以带活体属性比如是否戴眼镜、人脸角度识别模型对图像质量不敏感检测帧率和识别帧率也可以分开调。系统空闲时段把检测频率降到每秒 2 帧识别只在检测到新面孔时才触发整体负载能降一半以上。反过来如果用一个端到端模型同时输出“框、身份、活体分数”短期看着省事真到部署阶段会发现每个子任务的阈值耦合在一起很难单独调。选型上我一般会按“检测用轻量的、识别用预训练好的开源权重”的原则而不是自己做训练。微软的 InsightFace 里带了一组部署友好的模型包RetinaFace 负责检测ArcFace/MobileFaceNet 负责提取 512 维特征。依赖只有 onnxruntime不需要在部署机上再配一套训练框架。这一点对实验室这种需要拷贝给师弟师妹的交付场景尤其重要。方案采集设备成本非接触程度防代签能力与监控系统融合度IC 卡低差要刷卡代刷无法杜绝差需另设门禁指纹中一般较强差需专属设备二维码低好截屏可代扫差人脸识别中摄像头即可最好中等需活体检测强监控画面复用2.2 用特征向量而不是分类标签距离阈值是入场券分类式人脸的常规套路是 Softmax 输出“这个人是谁”改成新成员就得重训网络。签到场景天然属于“人员会变”的情况——每学期有新的助教、新的课题组学生训练一轮成本远高于特征比对的成本。所以实际采用的都是度量学习路线识别网络输出的既不是编号也不是姓名而是一个 512 维特征向量系统里预先存好每个人的向量对比时算余弦相似度或欧式距离。对实验室场景我通常把默认阈值设在 0.45 到 0.55 之间余弦相似度越高越相似。这个区间比做门禁的单位更宽松因为实验室内光线固定、没有逆光而且签到并不需要绝对防冒用只要能把漏签率压下来就好。阈值设置有一个基本规律降低阈值会减少漏检防止明明在场却没记上但会增加误识别把 A 记成 B升高阈值反之。实际调参时不要只看平均值的准确率要看两位长相接近的学生之间会不会互相串这一般要用你自己的现场照片跑一遍比对矩阵才能确认。比对过程本身不存在模型推理就是点乘所以签到系统在人数不多的情况下瓶颈几乎永远不在模型而在摄像头解码和磁盘写入。这个特性决定了后端可以用很轻的架构一台带有集成显卡的普通工控机就能支撑 4 路摄像头的并发比对。2.3 监控模块是行为事件识别先定事件再选模型实验室监控里“有人没走”、“深夜闯入”、“长时间无人在岗”这三类需求背后对应着不同的识别策略。单纯的目标检测只能回答“画面里现在有没有人”回答不了“这个人趴在桌上一动不动已经 20 分钟了”。后一种情况需要的是动作识别常规做法是姿态估计 时间序列判断先用轻量级姿态估计模型如 MediaPipe BlazePose提取人手、头、躯干的关键点坐标再按帧序列统计关键点位移。位移小于阈值的时间累计超过设定时长就触发“疑似晕倒或长时间不动”的事件。也可以完全跳过姿态模型用目标框中心点变化做简化每隔 10 秒记录一次检测框中心坐标连续 12 次中心点距离小于 8 像素就认为人长时间未移动。这个方法看起来粗糙却足够稳定CPU 占用极低适合实验室夜间值守这种不需要精细动作判断的场合。真需要识别“翻窗”这种特定动作再上姿态估计模型否则不要让高开销的模型常驻内存。事件定义是这类系统设计中最先要做的事。我会把所有要监控的行为列成一张表每条记录包含触发条件、优先级、响应动作。举两个例子“夜间 23:00 后出现人脸”对应高优先级动作是弹窗 短信通知“早晨 8:30 前无人到达指定机位”对应一般优先级动作是记录到日报。把这些规则写进配置文件比把所有逻辑堆在代码里更容易维护也方便管理员自己调整。3. Python 深度学习环境配置从 Miniconda 到 PyTorch 的最小可跑链路3.1 用 conda 固定 Python 版本避免系统和项目互相污染部署文档压缩包解压后的第一件事就是解读环境依赖。这类项目最常见的形态是 Python 3.9/3.10 PyTorch 2.x onnxruntime OpenCV InsightFace 组合。我建议直接用 Miniconda 创建独立环境不让项目依赖混进系统 Python。系统自带 Python 往往被其他工具占用强行升级 pip 里的包容易把系统脚本弄坏conda 环境则把这种风险完全隔开。创建环境的命令如下conda create -n lab_attendance python3.10 -y conda activate lab_attendance pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python onnxruntime insightface fastapi uvicorn pymysql这里 torch 和 torchvision 指定了 CUDA 11.8 的预编译版本因为实验室显卡驱动的兼容性比最新版更可控如果你的机器只是 CPU 跑把第二个 pip 命令换成pip install torch torchvision不带索引地址即可。onnxruntime 是 InsightFace 模型包的推理后端不需要额外装 CUDA 库它自带 CPU/GPU 两种执行提供程序。最后的 fastapi 和 uvicorn 用于把签到能力封装成 HTTP 接口方便 Web 端展示考勤记录。这段命令背后的选型理由签到的检测与识别推理用 onnxruntime 跑训练或微调才用到 PyTorch。InsightFace 加载模型后可以直接输出检测框、关键点、特征向量不用自己拼多个模型。如果你之后打算从头训练一个人脸识别模型再回到 PyTorch 的 DataLoader 和训练循环但那是第二步。3.2 最小的人脸签到代码检测、特征、比对环境装好后的第一步不是直接抄整个项目而是用一个最小脚本验证“读图 → 检测 → 提特征 → 比对”四步是否通。下面的代码用 InsightFace 完成检测与特征提取import cv2 import numpy as np from insightface.app import FaceAnalysis # 加载检测识别模型buffalo_l 是官方预训练包 app FaceAnalysis(namebuffalo_l, providers[CPUExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) # 读取一张含人脸的现场照片 img cv2.imread(lab_photo.jpg) faces app.get(img) for face in faces: # bbox 是 [x1, y1, x2, y2] 的整数坐标det_score 是检测置信度 bbox face.bbox.astype(int) det_score face.det_score # normed_embedding 是单位化的512维向量直接用产品化部署特征库 emb face.normed_embedding print(fbbox{bbox.tolist()}, score{det_score:.3f}, emb_dim{emb.shape}) # 把特征向量存成 .npy后面做比对时直接加载 np.save(ffeat_{face.face_id if hasattr(face, face_id) else 0}.npy, emb)这段脚本验证了三件事模型能否在当前机器上加载摄像头或照片中的人脸能否被检出特征向量能否被导出。det_size(640, 640)是检测分辨率太小会漏掉远处人脸太大则推理耗时急剧上升实验室室内监控通常 640 够用。ctx_id0只在有可用 GPU 时生效providers[CPUExecutionProvider]则强制走 CPU。二者保留其一即可我是故意留了两行方便你切换验证环境。人脸比对时加载库中所有人的特征向量计算余弦相似度known_embs [np.load(f) for f in glob.glob(feat_*.npy)] query_emb np.load(query_feat.npy) sims [np.dot(q, k) for k in known_embs] # 单位向量的点积即余弦相似度 best_idx int(np.argmax(sims)) print(best_similarity, round(sims[best_idx], 3))如果最高相似度高于预设阈值比如 0.45就认为签到成功否则提示“未登记人员”。这里要说明一个工程细节normed_embedding已经做过单位化所以直接用点积表示余弦值不需要再调cosine_similarity。阈值千万不要写死进代码放进配置文件或环境变量后面调参时只改一处。3.3 VSCode 里接环境与 CUDA 不匹配的三个常见报错代码写完后最常卡住人的是 VSCode 的解释器没有指向 conda 环境导致 import 的包都是另一个环境的。在 VSCode 里按CtrlShiftP输入Python: Select Interpreter选择lab_attendance环境即可。命令行直接运行python inference.py一般没问题但用 F5 调试时会走到默认解释器这一点务必提前确认。排查环境问题时我一般先区分三类报错第一类是一启动就报ModuleNotFoundError说明包没有装进当前环境第二类是报No module named torch但明明装过多半是解释器选错第三类是程序运行时提示 CUDA 相关错误——这种最隐蔽因为 Python 层面不会报语法错误而是在模型加载时崩溃。常见的处理方式如下表报错现象原因处理方式ImportError: No module named onnxruntime解释器没切到 conda 环境重选解释器或执行pip list看包属不属于当前环境CUDA error: no kernel image is available for execution on the device显卡驱动太旧与预编译的 cu118 不匹配把推理切到 CPU或重装对应驱动版本cv2.VideoCapture(0) 打开黑屏摄像头是 USB 且被其他进程占用杀掉占用进程或改用cv2.CAP_DSHOW后接cv2.VideoCapture(0, cv2.CAP_DSHOW)还有一个和深度学习无关但要提醒的点实验室多个摄像头同时长时间连接时Windows 下容易遇到VideoCapture对象不稳定表现为偶发黑帧。处理方式是给采集线程设置连续读 3 帧、取最近 1 帧的策略不要用read()一次就更新一次界面否则识别结果会闪跳。这个主题的完整工程化方案下一步就会涉及。4. 自动签到与监控系统的工程实现从人脸识别到行为告警4.1 数据表设计人员表、签到表、告警表识别链路跑通后系统就变成一个常规的信息管理工程。数据层我一般设计三张核心表人员表 storage、签到记录表 attendance、告警表 alert。人员表存学生/教职工的学号、姓名、角色以及一条主特征向量可以为二进制或 Base64 字符串。特征向量直接存库比存文件更省事迁移时不会出现文件丢路径的问题。CREATE TABLE person ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT 学号/工号, name VARCHAR(50) NOT NULL, role TINYINT DEFAULT 1 COMMENT 1学生 2教师 3管理员, feature BLOB COMMENT 512维特征向量的npy二进制, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, person_id INT NOT NULL, event_type TINYINT NOT NULL COMMENT 1签到 2签退, captured_at DATETIME NOT NULL COMMENT 识别时间, camera_id VARCHAR(20) DEFAULT cam_01, snap_path VARCHAR(255) COMMENT 抓拍帧保存路径, INDEX idx_captured (captured_at), INDEX idx_person (person_id) ); CREATE TABLE alert ( id INT PRIMARY KEY AUTO_INCREMENT, alert_type VARCHAR(50) NOT NULL COMMENT 夜间闯入/长时间无人/离岗超时, detail TEXT, occurred_at DATETIME NOT NULL, is_handled TINYINT DEFAULT 0, INDEX idx_occurred (occurred_at) );字段类型的选择上feature用 BLOB 是因为特征向量本质是二进制文件snap_path保存抓拍帧路径而不是图片二进制方便后续在浏览器里直接通过静态目录预览camera_id预留是为了后期扩展多摄像头避免在代码里写死来源。所有时间字段统一用 DATETIME不引入时区概念因为实验室系统不出本地网时区只会增加排错成本。4.2 摄像头采集与识别解耦队列 异步任务直接在主线程里循环读帧并调用模型识别期间新的画面帧会堆积在摄像头缓冲区时间一长就会出现“画面延时 5 秒”的假死现象。更稳妥的做法是把摄像头读取和推理拆开采集线程只负责读帧并将最新帧放入一个定长队列推理线程负责从队列取帧做人脸检测、特征比对、写库。队列用 Python 标准库queue.Queue即可长度控制在 48 帧超过就丢弃最旧的帧。import queue import threading import cv2 frame_q queue.Queue(maxsize8) def capture_loop(camera_id0): cap cv2.VideoCapture(camera_id, cv2.CAP_DSHOW) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ok, frame cap.read() if not ok: continue if frame_q.full(): try: frame_q.get_nowait() # 丢弃最旧帧保持实时性 except queue.Empty: pass frame_q.put(frame) def infer_loop(): app FaceAnalysis(namebuffalo_l, providers[CPUExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) while True: frame frame_q.get() faces app.get(frame) # faces 非空时进入特征比对、写考勤、抓拍保存这段逻辑的关键是maxsize8与满时丢弃策略。监控图像的实时性比完整性重要少一帧不影响识别结果但队列阻塞导致推理线程卡住会直接拖垮整条链路。CAP_PROP_FRAME_WIDTH设置为 1280×720 兼顾了远处小目标的检出率与推理耗时不需要盲目上 2K分辨率翻倍带来的检测精度提升有限耗时却接近翻倍。后台保存抓拍帧时需要控制磁盘占用。单帧 JPG 大约 200400KB如果每次识别都保存一天下来轻松超过 20GB。常见做法是只在“新事件”时存档——比如该人当天第一次出现、或触发告警普通签到只写记录不存帧。这样日志依然可追溯磁盘压力却只有原来的十分之一。4.3 到岗统计与异常告警怎么算签到记录写进attendance表后业务层要解决两个统计问题一是某人某天在岗时长怎么算二是异常事件怎么定义。在岗时长的计算口径影响结果很大。有人 9:01 签到、18:00 签退中间外出 2 小时按首末次时间相减得 9 小时这显然虚高。诚实的计算方法是把当天该人的所有签到、签退记录按时间排序相邻记录配对成“在岗区间”再累加。由于人脸签退不一定可靠人走时不刷脸我一般按“连续 30 分钟检测不到该人”作为一次签退事件这样算出的时长更接近真实。对实验室考勤系统而言下面几类规则最常用优先级从高到低排列高优先级触发时可以直接打断低优先级的流程事件名称触发条件响应动作夜间闯入当天时间在 23:00次日 6:00检测到未授权人脸立即保存抓拍帧 短信通知管理员长时间无人日间工作时段内连续 4 小时无人脸出现写入日报不实时打扰离岗超时已签到人员连续 60 分钟未再检测到记录一条离岗日志这里的判断逻辑全部在 SQL 或规则引擎层完成。比如离岗超时用一条查询即可算出SELECT person_id, MAX(captured_at) AS last_seen FROM attendance WHERE captured_at NOW() - INTERVAL 60 MINUTE GROUP BY person_id;如果某人的last_seen超过 60 分钟就判定离岗。这个写法简单高效不需要维护内存里的状态字典适合重启后能完整恢复现场。多摄像头时把camera_id加进 GROUP BY可以精确到“人从这个摄像头视野走到了另一个视野”避免同一个人的连续检测被误判成两个新事件。4.4 部署要点进程守护与资源限制这种系统的生产部署不像上层 Web 应用那么复杂常见做法是一台普通办公机装好 Python 环境模型常驻内存用 Supervisor 守护采集推理进程对外提供一个 FastAPI 接口给 Web 端查询。不需要容器化因为 onnxruntime 与摄像头驱动都依赖宿主机硬件Docker 反而增加了一层设备映射的复杂度。FastAPI 服务端的关键参数是uvicorn.run(app, host0.0.0.0, port8000, workers1)这里必须用单 worker。多个 worker 会各自加载一份模型内存翻倍且队列数据各持一份workers1再加上异步接口应对实验室内部几十个并发查询没有问题。下面是 Supervisor 的一个最小配置[program:lab_attendance] command/opt/miniconda3/envs/lab_attendance/bin/python /opt/lab_attendance/src/main.py directory/opt/lab_attendance autostarttrue autorestarttrue startsecs5 startretries3 redirect_stderrtrue stdout_logfile/var/log/lab_attendance.outautorestarttrue的价值在于摄像头偶发断流、程序异常退出时能够自动恢复。如果进程挂了但 Supervisor 没有拉起来早上到实验室看到的就可能是“门开了但系统一夜无记录”这种故障的代价比一次识别失败大得多。另外建议在main.py里注册atexit回调把退出原因写进日志排查“为什么凌晨 3 点进程重启过”时很有用。5. 签到准确率验证与活体检测上线前先做这两件事5.1 用一段带标注的现场视频算 Top1 准确率模型在自己机器上跑得动不代表放进真实实验室就好用。我会在目标摄像头位置录一段 35 分钟的视频覆盖不同时刻的自然光、人员走动、一米外与三米外的人脸然后人工标注每帧里出现的是谁再用下面的脚本统计 Top1 准确率。import json, glob import numpy as np from insightface.app import FaceAnalysis app FaceAnalysis(namebuffalo_l, providers[CPUExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) with open(video_annotation.json, r) as f: ground_truth json.load(f) # {frame_0001.jpg: zhang, ...} correct 0 total 0 for img_path, label in ground_truth.items(): img cv2.imread(img_path) faces app.get(img) if len(faces) 0: continue # 视频里确实没人跳过该帧 # 这里用最大人脸框作为主要识别目标 best_face max(faces, keylambda x: x.bbox[2] - x.bbox[0]) emb best_face.normed_embedding sims [float(np.dot(emb, k)) for k in known_embs] pred names[int(np.argmax(sims))] total 1 if pred label: correct 1 print(ftop1_accuracy over {total} frames: {correct / max(total, 1):.2%})运行后如果准确率低于 90%先调检测分辨率再看角度。监控摄像头位置太高人脸俯仰角过大特征提取效果会明显下降这时把摄像头调整到与人脸平齐或略高的位置比换更强的模型更有效。还建议记录每个人员的最低相似度输出一张表——相似度最低的那个人的数值就是系统在真实光线下的脆弱点所在比一个整体准确率更有诊断价值。5.2 活体检测别让一张照片把系统打穿单靠静态人脸比对一张打印照片就能通过签到。实验室场景最常见的攻击就是学生把手机照片放在摄像头前。最轻量的防护是“动作活体”识别到人脸后要求人员完成一次指定动作眨眼或左右转头在 3 秒内检测到关键点变化即通过。InsightFace/MediaPipe 都能输出眼部关键点不必额外引入深度相机硬件。动作活体的实现成本很低只依赖一个人脸关键点检测模型每秒推理一次即可。它带来的一个副作用是签到耗时增加 23 秒在实验课高峰期门口会排一小队。所以常见做法是区分时段白天松散时段不启用活体只在夜间告警和重要实验设备开机前要求活体验证。多一个开关比让管理员一直忍受体验损失要好。帧缓冲本身也是个值得调整的运维参数。摄像头采集线程的frame_q.maxsize调大后可以缓冲更多帧避免推理慢时丢帧但调太大会导致识别结果延迟到几秒后体验很差。一般建议按“推理一帧耗时的 5 倍”来设推理 80ms 就设 maxsize8100ms 就设 10。先测单帧推理耗时再定队列长度的做法会让在线调试少走很多弯路。本文还有配套的精品资源点击获取
返回列表