尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于RetinaFace与FaceNet的人脸识别会议签到系统工程实战

基于RetinaFace与FaceNet的人脸识别会议签到系统工程实战 简介这是一份基于深度学习RetinaFaceFaceNet的人脸识别会议签到系统完整工程面向需要落地人脸识别应用、开展毕设或科研的开发者。系统覆盖从图片URL/base64输入、人脸检测与对齐、FaceNet 128位特征提取到与库中人脸比对并返回用户信息的完整流程识别端采用Python编写以Flask封装功能接口供Java后端调用并加入基于OpenCV的直方图均衡化预处理以提升复杂光线下的识别稳定性。压缩包共2000个文件数量上以JavaScript、Markdown和JSON为主同时包含Python算法脚本、数据库文件、项目部署文档与汇报PPT整体约174MB。资源提供了端到端签到的可运行方案部署说明详细便于读者快速复现和二次开发目前已有176人学习下载适合会议签到、访客管理等场景参考。1. 基于深度学习的人脸识别会议签到系统解决的问题与适用边界企事业会议的签到场景最常见的问题不是识别不准而是排队和代签。二维码能代扫指纹要排队工牌可以转借。基于深度学习RetinaFaceFaceNet的人脸识别会议签到系统把摄像头画面中的人脸实时转成 128 维特征向量与预先注册的人员名单比对后自动写签到记录全程无需接触、无需人工核对。相比成品人脸识别门禁机自建系统的价值在于数据完全本地化、可离线运行能拿到检测框、关键点、向量距离等中间结果方便做统计报表和异常追溯。下面按“模型协同 → 数据库设计 → 部署落地 → 验收答辩”的顺序把一套能跑通的工程方案拆开讲新手照做能落地有经验的人可以拿参数边界做取舍。2. RetinaFace 检测与 FaceNet 特征提取的协同逻辑2.1 为什么人脸识别会议签到要拆成两个模型第一反应是用一个分类模型让网络直接输出“张三、李四、陌生人”。这种方案在固定场景、固定机位、固定名单的封闭实验室里表现不错但用在会议室门口会立刻露馅新员工入职要重新训练模型离职员工要从模型里删除画面里出现一个没注册过的访客时网络还是会按概率把他归到最近的类导致误签到。而 RetinaFace FaceNet 的级联方案把“人脸检测”和“身份判别”拆成了两个独立模型两个问题被彻底分开。RetinaFace 在这个项目里的职责是定位人脸框并输出 5 个关键点左眼、右眼、鼻尖、左嘴角、右嘴角。FPN 结构让它对会议场景常见的远距离小脸有稳定的召回SSH 上下文模块提升了遮挡情况的鲁棒性。FaceNet 的职责则是对裁剪并对齐后的 160×160 人脸图像提取 128 维 embedding这组向量在欧氏空间里的距离可以直接当作“人相似度”。整个链路里没有“类别”的概念所有身份信息都存在于数据库比对结果中新员工只需要在注册端拍一张照提取向量后插入人员表离职员工删掉一行数据即可。对比项RetinaFace FaceNet 级联单一分类模型新增人员提取特征向量入库全量重训或增量训练未注册访客距离超过阈值判为陌生人会被强行归到最近类别可解释性检测框、关键点、距离都可观测黑盒概率输出模型更新检测和识别可独立替换任一侧变化都要整体重训这也是大多数开源的人脸识别项目采用两段式结构的原因。哪怕在 CSDN 或 GitHub 上搜“人脸识别项目开源”下载下来看代码核心路径依然是检测器输出人脸框embedding 模型输出向量比对逻辑写在业务层而不是网络层。把这个认知建立起来后面所有设计都不会跑偏。2.2 摄像头画面到特征向量的最小推理链路我一般的做法是先搭一个不带数据库的最小验证脚本确认检测和特征提取这条链路在目标机器上能跑通再往上层加业务。下面的代码用 InsightFace 的 RetinaFace 后端和 facenet-pytorch 实现。import cv2 import numpy as np import torch from facenet_pytorch import InceptionResnetV1 from insightface.app import FaceAnalysis # 初始化 RetinaFacebuffalo_l 包含检测关键点识别这里只用检测能力 app FaceAnalysis(namebuffalo_l, providers[CPUExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) # FaceNet 使用 VGGFace2 预训练权重 facenet InceptionResnetV1(pretrainedvggface2).eval() def face_to_embedding(img_bgr): faces app.get(img_bgr) if len(faces) 0: return None, None # 画面里可能同时出现多个人取面积最大的人脸作为当前签到对象 face max(faces, keylambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1])) x1, y1, x2, y2 [int(v) for v in face.bbox] # 优先用模型的 5 点对齐结果拿不到时直接裁切缩放 aligned getattr(face, aligned, None) if aligned is None: crop img_bgr[y1:y2, x1:x2] aligned cv2.resize(crop, (160, 160)) rgb cv2.cvtColor(aligned, cv2.COLOR_BGR2RGB) tensor torch.from_numpy(rgb).permute(2, 0, 1).unsqueeze(0).float() tensor (tensor - 127.5) / 128.0 with torch.no_grad(): emb facenet(tensor).numpy().flatten() return emb, face.bbox参数说明分别解释一下。providers里写CPUExecutionProvider表示强制用 CPU 跑 ONNX在只有集成显卡的办公电脑上最稳妥如果机器有独立显卡且 CUDA 环境正常可以换成CUDAExecutionProvider。det_size(640, 640)是检测端的输入尺寸值调大到 960 能提升侧面和远处小脸的召回率但单帧耗时接近翻倍。facenet输入统一为 160×160代码里提前做了(x - 127.5) / 128的归一化这步漏掉会导致同一个人的向量距离普遍偏大误拒率升高。完成这一步后你手里就有了两组关键数据人脸框坐标和 128 维特征向量。后面的注册、比对、签到全是围绕这两个数据的操作不再涉及模型训练。2.3 128 维向量的距离阈值决定签到成败把阈值这条放在模型章节里说是因为很多项目就是在这里翻车的。FaceNet 输出的向量经过 L2 归一化用欧氏距离衡量相似度。同一个人的两张照片距离通常在 0.81.2换一个人距离一般会跳到 1.3 以上。这个“跳变区间”会因为摄像头分辨率、光照、人脸对齐质量而变化所以阈值不能照抄论文必须现场校准。阈值误识别风险误拒风险建议场景0.6几乎不会混淆稍微低头就会漏签光线非常稳定的固定机位0.8双胞胎和相似脸可能混淆侧脸、暗光可接受多数会议室默认值1.0陌生人可能被放进来很少漏签签到优先、事后人工抽查会议签到系统和门禁系统不一样门禁要严格限制陌生人阈值可以压到 0.6会议签到的诉求是“该签的人别漏”哪怕误识别的风险高一点后续还有签到记录截图和现场照片可以人工复核。这个场景差异直接影响阈值方向。3. 从会议签到业务反推数据库表结构与注册流程3.1 三张核心表人员、会议、签到流水工程上我喜欢把表拆成三张而不是把人员信息和签到记录混在一起。原因是会议签到天然有“一对多”一个员工参加多次会议一次会议有大量员工签到。分开建表之后后续要统计“某人当月出勤率”或“某会议实际到场人数”都只需要一次 JOIN。表名核心字段说明staffid, name, department, face_embedding, photo_path, created_atface_embedding 用 BLOB 存 float32 数组photo_path 保留原始照片用于追溯meetingid, title, start_time, end_time, location, statusstatus 用来标记未开始/进行中/已结束checkinid, meeting_id, staff_id, checkin_time, distance, device_id, image_url每行代表一次签到distance 记录识别时的向量距离对应 MySQL 建表语句CREATE TABLE staff ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, department VARCHAR(64), face_embedding BLOB NOT NULL, photo_path VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE meeting ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, location VARCHAR(128), status TINYINT DEFAULT 0 ); CREATE TABLE checkin ( id INT PRIMARY KEY AUTO_INCREMENT, meeting_id INT NOT NULL, staff_id INT NOT NULL, checkin_time DATETIME DEFAULT CURRENT_TIMESTAMP, distance FLOAT, device_id VARCHAR(64), image_url VARCHAR(255), UNIQUE KEY uk_meeting_staff (meeting_id, staff_id) );这里的关键设计是checkin表上的唯一索引uk_meeting_staff它从数据库层面保证了“同一场会议一个人只能有一条签到记录”。业务代码里的重复判断即使写漏了并发插入时也会被数据库拦住不会出现两条相同签到。3.2 注册阶段人员增量入库的实际代码注册端最常见的错误是直接存照片每次签到都在摄像头画面和全部照片之间做图像匹配速度慢且不稳定。正确做法是预先把每张注册照片转换成 float32 向量与照片路径一起入库。def register_staff(photo_bytes, name, department): img cv2.imdecode(np.frombuffer(photo_bytes, np.uint8), cv2.IMREAD_COLOR) emb, bbox face_to_embedding(img) if emb is None: raise ValueError(未检测到人脸请更换光线充足的正脸照片) if bbox is not None and (bbox[2]-bbox[0]) 100: raise ValueError(人脸占比过小请裁剪后再传) photo_name uuid4().hex .jpg with open(fuploads/{photo_name}, wb) as f: f.write(photo_bytes) cursor.execute( INSERT INTO staff (name, department, face_embedding, photo_path) VALUES (%s, %s, %s, %s), (name, department, emb.astype(np.float32).tobytes(), fuploads/{photo_name}) ) db.commit()注册时强制检查“人脸框宽度小于 100 像素直接拒绝”是为了避免像册里裁出来那种远距离全身照混进来这种低质量向量是后期误识别的第一来源。入库用tobytes()把 numpy 数组序列化读出时执行np.frombuffer(row[face_embedding], dtypenp.float32)即可还原。3.3 签到判定逻辑阈值判断加会议内去重签到接口拿到摄像头当前帧后先提取向量然后只和参加该会议的人员比对。这里有个性能细节不要每次请求都 SELECT 全表而是通过meeting_staff关联表示例中未列出把与会人员限定在几十人内暴力计算距离完全足够。def check_in(frame, meeting_id): emb, bbox face_to_embedding(frame) if emb is None: return {status: no_face, message: 当前画面未检测到人脸} staff_list load_staff_vectors(meeting_id) # [(staff_id, np.ndarray), ...] best_id, best_dist None, float(inf) for staff_id, vec in staff_list: dist np.linalg.norm(emb - vec) if dist best_dist: best_id, best_dist staff_id, dist if best_dist CHECKIN_THRESHOLD: return {status: unknown, message: 未在会议名单中, distance: round(best_dist, 4)} cur db.cursor() cur.execute(SELECT id FROM checkin WHERE meeting_id%s AND staff_id%s, (meeting_id, best_id)) if cur.fetchone(): return {status: duplicate, message: 已完成签到请勿重复打卡} cur.execute( INSERT INTO checkin (meeting_id, staff_id, distance, image_url) VALUES (%s, %s, %s, %s), (meeting_id, best_id, float(best_dist), fcheckin/{uuid4().hex}.jpg) ) db.commit() return {status: ok, staff_id: best_id, distance: round(best_dist, 4)}load_staff_vectors内部把每行的 BLOB 字段批量还原成 numpy 数组一次性加载到内存避免逐条查询。签到成功后保存的现场截图与原始注册照片形成对照出现争议时直接看两张图比争论阈值是多少有效得多。4. 项目部署文档里必须写清楚的环境配置与启动步骤4.1 依赖版本与 CPU/GPU 推理的取舍不少项目“代码能跑”和“别人能部署”是两回事。部署文档第一个要锁定的就是 Python 和 CUDA 版本否则在另一台机器上安装依赖时会消耗大量时间。以下是常见可行的组合组件版本建议备注Python3.8 ~ 3.103.11 及以上部分 ONNX 算子库兼容性较麻烦PyTorch2.0.x ~ 2.2.xfacenet-pytorch 依赖 torch 与 torchvisiononnxruntime1.16 或 1.18纯 CPU 环境不要强行装 GPU 版insightface0.7.3需要配套下载模型包 buffalo_lMySQL5.7 或 8.0上面 SQL 里的 BLOB 和唯一索引均兼容会议签到系统的推理是准实时而不是实时一秒钟处理一帧就足够。所以 CPU 环境下只要检测分辨率不设到 960基本也能满足使用。GPU 的意义更多在于同时开多路摄像头比如两个会议室门口或者做批量历史视频的回放测试。注意insightface的模型包默认从官方仓库下载如果目标部署机处于内网环境需要在联网机器上下载后拷贝到~/.insightface/models/对应目录部署文档要把这一步单独写成一节。4.2 一键启动脚本与数据库初始化部署文档至少应该包含三个步骤初始化数据库、安装依赖、启动服务。我习惯把前两步串进一个setup.sh让接手的人不用读第二遍文档就能把服务拉起来。#!/bin/bash # 第一步创建 Python 虚拟环境并安装依赖 python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt # 第二步初始化数据库表 mysql -u root -p schema.sql # 第三步启动签到服务监听 0.0.0.0:8000 uvicorn main:app --host 0.0.0.0 --port 8000requirements.txt里建议把关键包锁到主版本号例如torch2.2.*、facenet-pytorch2.6.*、insightface0.7.3。使用uvicorn作为 Web 服务入口方便同时提供注册和签到两个 HTTP 接口。内网部署时把--host设为固定内网 IP不要盲目使用 0.0.0.0否则会议室其他设备都能访问注册接口。如果团队里有人希望用 Java 做 Web 层最省事的做法不是把模型推理搬到 Java而是让 Spring Boot 服务通过 HTTP 调用 Python 侧的注册/签到接口模型留在 Python 进程里。4.3 部署后必做的三项验证第一注册一个真实人员然后在摄像头前正常走向签到位置观察返回的状态码是ok且距离小于阈值。第二让未注册的人员站在同一位置确认接口返回unknown。第三同一人完成签到时再次出现在画面里确认返回duplicate并且数据库的checkin表没有新增行。这个验证流程对应的是最有价值的验收材料。实际部署时经常出现一个现象本地调试时识别正常换到会议室门口就大量漏签。原因大多是背光或逆光导致检测框抖动此时应优先调整摄像头位置而不是调低阈值。还有设备驱动问题比如笔记本自带的红外摄像头在人脸识别时一直提示居中这类硬件问题应先用操作系统自带的相机应用排除再回过来查推理链路。4.4 服务端调试的两个高频报错用 VSCode 远程调试 Python 服务时如果断点处代码是灰色且提示“当前不会命中断点”先检查 VSCode 右下角选择的 Python 解释器是否指向venv/bin/python3再用which python在终端里确认虚拟环境是否已激活。另一个高频问题是推理速度慢onnxruntime在 GPU 机器上默认回退到 CPU需要显式安装与 CUDA 版本匹配的onnxruntime-gpu并且重新初始化FaceAnalysis时指定providers[CUDAExecutionProvider, CPUExecutionProvider]。5. 汇报 PPT 里最有说服力的四个素材与一次性阈值校准技巧毕业答辩或项目汇报最怕遇到“这个识别率到底多少”这类问题。与其现场演示翻车不如提前把四个素材放进 PPT第一张放 RetinaFace 在会议室门口抓拍的多路摄像头画面用检测框和 5 个关键点叠加图证明的是“检测能看清谁来了”第二张放注册照片与签到现场截图的对照旁边标注向量距离证明“比对结果是可解释的”第三张放checkin表的签到流水明细带会议主题、时间、设备号第四张放基于签到数据生成的准时率统计小表。阈值校准是汇报前必做的一步。准备 20 个人的注册照和 40 张现场签到照按下面脚本统计距离分布def estimate_threshold(vectors_of_same_person, vectors_of_different_person): same_dists [np.linalg.norm(a - b) for a in vectors_of_same_person for b in vectors_of_same_person if not np.array_equal(a, b)] diff_dists [np.linalg.norm(a - b) for a in vectors_of_same_person for b in vectors_of_different_person] print(同人最大距离:, max(same_dists)) print(异人最小距离:, min(diff_dists)) return (max(same_dists) min(diff_dists)) / 2阈值取同人最大距离和异人最小距离的中点是快速且可解释的经验做法。如果中点出现重叠说明注册照片质量或现场光照已经影响模型输出优先处理数据而不是继续调阈值。汇报时把这个过程讲清楚比贴一张漂亮的结果截图更能应对追问。本文还有配套的精品资源点击获取
返回列表