
简介面向本科、高职毕业设计的人脸识别签到系统完整项目包基于Python与深度学习技术实现适合计算机相关专业学生用于课程设计、毕业设计或工程实践。资源将系统源码、Web前端页面、人脸识别模型数据、SQLite数据库及部署说明整合为一体共27个文件压缩包约102.26MB。其中Python源码覆盖Flask应用、人脸识别算法、数据库迁移等核心模块HTML模板与CSS样式构成管理后台界面dat与sqlite文件用于存储人脸特征模型和签到记录另有requirements.txt等依赖清单与说明文档可辅助快速搭建环境、启动系统。已有4467人学习项目目录结构清晰包含完整的初始化与运行配置说明适合在此基础上二次开发或作为论文实验素材。整套代码结构规范对人脸检测、特征提取、Web交互等环节均有体现是理解深度学习落地流程与Web系统开发的优质参考。1. 基于深度学习的人脸识别签到系统先搞清楚它在解决什么问题公司考勤和教室签到现在越来越常见的是人脸识别方案。传统刷卡会代签指纹在冬季识别失败率很高而基于深度学习的人脸识别签到系统的核心思路并不神秘每张人脸经过卷积神经网络被压缩成一个高维特征向量签到变成了“当前人脸特征和人脸库中谁的距离最近”的比对问题。这个方向常用于毕业设计、企业内部考勤和课堂签到但很多人做出来只能算 demo——本地跑通几张图片一接摄像头就各种翻车要么识别慢要么误识别。这篇笔记会按落地顺序讲清楚模型选型、活体检测、识别链路、数据准备与训练参数、常见坑和验证方法让新手能复现一套能上线的系统。2. 人脸识别签到系统的技术选型模型、活体检测与部署架构怎么定2.1 识别模型选型ArcFace、MobileFaceNet与FaceNet的取舍人脸识别签到系统最核心的部分是识别模型。常见做法是用预训练的 ArcFace 或 FaceNet 模型把对齐后的人脸映射为 128 维或 512 维的特征向量再通过向量距离判断是不是同一个人。ArcFace 在 Softmax 基础上引入加性角度间隔angular margin训练时要求同一个人的特征向量在角度空间里靠得更近不同人的特征向量离得更远这个 margin 对类间可分性的提升非常直观。选型时主要看两个维度精度和部署成本。如果签到系统跑在 GPU 服务器上用 ResNet50 或 ResNet100 做 backbone 的 ArcFace 模型精度最高单张人脸特征提取在主流 GPU 上大概几毫秒到十几毫秒完全够用。如果方案是人脸识别门禁机这类一体机算力受限通常选择 MobileFaceNet 作为 backbone参数量不到 1M在 CPU 上也能跑LFW 准确率仍能到 99% 以上。我的实际经验是室内签到场景光照稳定、人脸角度变化小MobileFaceNet 已经足够不需要硬上大模型。模型组合特征维度部署环境适用场景ResNet100 ArcFace512GPU 服务器精度优先人数多、环境复杂MobileFaceNet ArcFace128 或 512CPU / 嵌入式签到一体机、低成本方案FaceNetInception ResNet128GPU / CPU快速原型精度不如 ArcFace有人会在选型时纠结“哪个模型排名第一”但这里有一个容易忽略的事实签到系统的整体精度不只取决于模型还取决于注册照片质量、阈值设置和活体检测策略。模型哪怕提升 0.1% 的准确率对用户体验的影响远不如把阈值调错来得大。2.2 活体检测为什么只靠识别模型一定会被照片骗过签到场景最大的威胁不是识别不准而是伪造攻击。一张同事的工牌照、一段录好的视频就能绕过只做特征比对的系统。所以落地时几乎都要加活体检测模块。常见方案分两种一是 RGB 活体检测用 CNN 直接从普通摄像头图像判断当前画面是否为真实人脸优点是不加硬件成本缺点是对光线和屏幕反光敏感二是红外 / 深度活体需要专用摄像头但红外图像下屏幕基本是黑的伪造攻击天然失效。一个务实的策略是分层防御正常签到只做 RGB 活体检测加人脸识别检测到可疑情况如连续失败、特征分数卡在阈值边缘时临时要求用户做点头或眨眼动作用动作验证兜底。我在实际项目里经常用这条组合拳既能控制硬件成本又不会在最容易被攻击的环节裸奔。提示RGB 活体检测的模型要定期用现场设备录制的新数据做验证。摄像头更换、安装位置变化、门口光线调整都会让原来的活体模型评估指标失真。2.3 部署架构识别服务独立部署还是嵌入终端签到系统的整体架构一般分三块摄像头采集端、识别服务、签到数据库。常见做法有两种。第一种把识别服务独立部署在一台带 GPU 的服务器上摄像头终端通过 RTSP 或 HTTP 把帧传到识别服务服务返回识别结果再写入数据库。第二种把所有逻辑封装进一台嵌入式一体机终端直接输出签到结果。这两种架构的取舍点在于升级和响应速度。识别服务独立部署模型更新、活体策略调整都可以在服务端完成终端几乎不用动缺点是每次识别都要经过网络一条教学楼网络波动就可能影响签到。嵌入式一体机响应快、不依赖网络但后续升级模型要刷机调试也不方便。我一般建议先做独立服务成本低、好调试后期如果要做门禁一体机再把服务端模型导出成 ONNX 放到终端上。深度学习环境配置也可以统一在服务端做省去每台终端重复装 CUDA、PyTorch 的麻烦。3. 从视频帧到签到记录人脸检测、特征提取与签到判断的完整链路3.1 人脸检测与关键点对齐为什么必须先把脸转正识别模型不是拿到一张图就输出的。它期望的输入是“对齐后的人脸”——两只眼睛水平、人脸居中、固定尺寸。这张图是否对齐直接决定了特征向量的质量。常见的人脸检测模型有 MTCNN 和 RetinaFace。MTCNN 轻量在 CPU 上也能跑适合简单场景RetinaFace 精度更高对角度、遮挡、远近变化更鲁棒签到场景我更推荐 RetinaFace。检测模型输出人脸框的同时会给出五个关键点左右眼、鼻尖、左右嘴角我们用这五个点做仿射变换把脸转正并裁剪到模型要求的输入尺寸。下面是基于 OpenCV 的对齐代码import cv2 import numpy as np def align_face(image, keypoints, output_size(112, 112)): # keypoints顺序: 左眼、右眼、鼻尖、左嘴角、右嘴角 left_eye keypoints[0] right_eye keypoints[1] # 根据左右眼连线计算旋转角度把脸转水平 delta_x right_eye[0] - left_eye[0] delta_y right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(delta_y, delta_x)) # 以两眼中心为旋转中心 center ((left_eye[0] right_eye[0]) // 2, (left_eye[1] right_eye[1]) // 2) rot_mat cv2.getRotationMatrix2D(center, angle, 1.0) rotated cv2.warpAffine(image, rot_mat, (image.shape[1], image.shape[0])) # 简化裁剪以两眼中心为中心取112x112区域 x center[0] - output_size[0] // 2 y center[1] - output_size[1] // 2 aligned rotated[y:y output_size[1], x:x output_size[0]] # 如果越界先灰度填充再裁剪 if aligned.shape[0] ! output_size[1] or aligned.shape[1] ! output_size[0]: canvas np.zeros((output_size[1], output_size[0], 3), dtypenp.uint8) canvas[:aligned.shape[0], :aligned.shape[1]] aligned aligned canvas return aligned这段代码做了什么先用左右眼坐标算旋转角再用仿射变换把整个人脸旋转到水平方向最后以两眼中心为基准裁剪出 112x112 的区域。ArcFace 训练时用的是 112x112如果换用其他模型这里要跟着改。参数说明里最需要注意的是 keypoints 顺序不同检测模型输出的关键点顺序不一样顺序错了脸就是歪的另一个细节是旋转后的图像边缘可能会露出黑色区域极端角度下需要用填充或重新检测兜底。实际工程里裁剪窗口不会像我这样固定取两眼中心往四周各 56 像素而是会结合人脸框和关键点动态计算。人脸在画面里偏大或偏小时固定窗口会导致漏掉部分额头或下巴这会直接影响后续特征提取的稳定性。常见做法是把人脸框向外扩 1.2 到 1.5 倍再缩放确保完整人脸都在输入图像里。3.2 特征提取ONNX Runtime 加载 ArcFace 模型输出 512 维向量对齐好的人脸图接下来送入特征提取模型。工程上最省事的方式是把 PyTorch 或 PaddlePaddle 模型导出成 ONNX再用 ONNX Runtime 推理。这样部署时不需要安装整套深度学习框架只依赖 onnxruntime 一个包环境配置简单很多。下面是一个完整的特征提取类import onnxruntime as ort import cv2 import numpy as np class FaceEncoder: def __init__(self, onnx_path, input_size(112, 112)): self.sess ort.InferenceSession( onnx_path, providers[CUDAExecutionProvider, CPUExecutionProvider] ) self.input_name self.sess.get_inputs()[0].name self.input_size input_size def preprocess(self, img): # ArcFace标准预处理BGR转RGB、resize到112x112、归一化到[-1, 1] img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, self.input_size) img img.astype(np.float32) / 127.5 - 1.0 # HWC - CHW增加batch维 img np.transpose(img, (2, 0, 1)) return np.expand_dims(img, axis0) def get_embedding(self, aligned_face): blob self.preprocess(aligned_face) feat self.sess.run(None, {self.input_name: blob})[0] feat feat.flatten() # L2归一化让特征向量模长为1后续可直接用点积算余弦相似度 norm np.linalg.norm(feat) if norm 0: feat feat / norm return feat逻辑说明先做通道转换ONNX 模型一般按 RGB 训练OpenCV 读进来是 BGR顺序不转的话特征分布会偏。再归一化到 [-1, 1]这是 ArcFace 训练时的输入范围有人统一用 ImageNet 的 mean/std 做了标准化跟训练不一致特征就乱了。最后一个关键步骤是 L2 归一化它让每个特征向量的模长变成 1两个向量点积的结果就是余弦相似度省去单独算 cosine 的步骤。参数说明一句话总结providers 里把 CUDA 放在前面有 GPU 时优先用 GPU没有 GPU 时 ONNX Runtime 会自动落到 CPU。输入尺寸必须与模型训练时的输入一致ArcFace 常见的是 112x112人脸识别领域还有不少模型用 96x96 或 128x128换模型就要同步改这里。3.3 签到逻辑阈值、去重和异常回退识别特征拿到手后签到逻辑就是和人脸底库比对。底库可以简单理解成一个 dict人员 ID 到特征向量的映射。几百人的量级直接在内存里算点积就很快如果到了几千人、几万人再换成 FAISS 一类的向量索引但签到系统一般用不上。下面是一段可直接改造的签到判断逻辑from datetime import datetime, timedelta def check_in(embedding, face_db, records, threshold0.45, cooldowntimedelta(hours24)): # 遍历底库找余弦相似度最高的人 best_person None best_score -1.0 for person_id, db_emb in face_db.items(): score float(np.dot(embedding, db_emb)) if score best_score: best_score score best_person person_id # 低于阈值认为是陌生人 if best_score threshold: return {status: unknown, score: best_score} # 时间窗去重防止反复刷脸重复签到 last records.get(best_person) now datetime.now() if last is not None and now - last cooldown: return {status: duplicate, person: best_person, score: best_score} # 通过所有检查记录签到时间 records[best_person] now return {status: ok, person: best_person, score: best_score}参数说明threshold 决定“这是不是同一个人”取值一般在 0.3 到 0.6 之间。调高阈值误识别少但漏识别多调低则相反。我不会拍脑袋定值而是用验证集把 0.25 到 0.65 每个值得 ROC 都算一遍取等错误率附近再上下浮动 0.03 作为业务阈值。cooldown 是去重窗口公司考勤往往设成一天一次课堂签到有时候要求“课前 15 分钟签到”这里按业务调整即可。多帧确认是另一个容易被忽视的细节不要拿单帧结果直接写库。常见做法是连续 3 到 5 帧都识别为同一个人、且得分都过阈值才判定签到成功。这样能过滤掉偶发的检测抖动和特征波动代价是签到响应会多几百毫秒对体验几乎没有影响。4. 数据准备与模型训练人脸底库怎么建ArcFace 训练参数怎么调4.1 人脸底库建设一个人注册几张照片才够签到系统上线前第一步是给每个人注册人脸。最常见的问题是采集太少只拍一张还站在同一个位置同一角度特征多样性严重不足。我给的建议是最少 5 张分别覆盖正面、左右偏转 15 度以内、轻微抬头低头、戴眼镜和不戴眼镜。注意不要从一张照片里裁几个区域充当多张那是同一样本的数据增强对特征多样性没有本质帮助。采集完的照片要先清洗一遍。把模糊的、过曝的、闭眼的、人脸检测都检不出来的样本直接删掉。一个很实用的检查手段是把注册照片全部跑一遍特征提取对同一个人算两两相似度如果某一对明显偏低多半是那张照片质量有问题可以补拍或删除。数据增强也是一个方向。如果每个人只有 5 张照片训练时可以用水平翻转、轻微旋转、颜色抖动来扩充。但测试集评估时不能用增强后的图片否则指标虚高。这条原则处理起来很机械但做错的人很多尤其是毕设和课程设计里。4.2 训练与微调ArcFace 的 s、margin 和其他关键参数自建数据量大的时候可以自己从零训练 ArcFace。数据一般用公开数据集CASIA-WebFace 或 MS1M预训练再用自己的人脸数据微调。ArcFace 损失函数有两个核心超参数s 是特征缩放因子论文里设 64控制 softmax 输出的温度m 是角度间隔常见设 0.5。m 调太大会导致难以收敛太小则类间区分度不够。优化器一般用 SGDmomentum0.9weight_decay5e-4初始学习率 0.1在预设的 epoch 处衰减比如分别在 8、14、20 个 epoch 时除以 10。如果做微调学习率要降到 0.001 甚至更低防止破坏预训练模型已经学到的底层特征。ArcFace 损失函数在 PyTorch 里实现并不复杂import torch import torch.nn as nn import torch.nn.functional as F class ArcFaceLoss(nn.Module): def __init__(self, s64.0, m0.5): super().__init__() self.s s self.m m self.cos_m torch.cos(torch.tensor(m)) self.sin_m torch.sin(torch.tensor(m)) self.threshold torch.cos(torch.tensor(torch.pi - m)) def forward(self, cosine, label): # cosine: (N, C)来自权重归一化和特征归一化后的内积 cos_theta cosine.clamp(-1.0 1e-7, 1.0 - 1e-7) # 根据cos值算加角度间隔后的cos sin_theta torch.sqrt(1.0 - cos_theta ** 2) cos_theta_m cos_theta * self.cos_m - sin_theta * self.sin_m # 当cos_theta接近π时改用线性近似避免数值不稳定 cos_theta_m torch.where( cos_theta self.threshold, cos_theta_m, cos_theta - 2 * self.m ) one_hot F.one_hot(label, num_classescosine.shape[1]).float() output one_hot * cos_theta_m (1 - one_hot) * cos_theta return F.cross_entropy(self.s * output, label)逻辑说明forward 的输入不是 logits而是特征向量和分类层权重各自 L2 归一化后的内积即余弦相似度。对目标类别我们把它在角度空间加上 m再把缩放因子 s 乘回去最后过交叉熵。训练的时候要监控两类指标类内距离应该持续下降类间距离应该持续上升如果 loss 在降但类间距离也在降往往是学习率偏大或者 m 不够特征没有真正被拉开。参数说明s 和 m 不要同时乱调。s 控制温度影响收敛速度m 控制类间间隔影响最终精度。实际操作中先用标准值s64, m0.5跑通流程再重点调 m从 0.1 慢慢升到 0.5每轮看验证集准确率。我自己踩过 m 一上来就设 0.8 导致 loss 不收敛的坑后来用这种 warmup 的方式才解决。4.3 评估方法LFW 指标、自建验证集与阈值选择模型训练完怎么看效果先在 LFW 上测达到 99% 以上说明模型基础能力没问题。但 LFW 的 99% 不代表现场签到能复现这个数字——LFW 是干净的、对齐好的、光照难度受控的图片而现场摄像头画面会有运动模糊、背光和畸变误识别率可能高出几个数量级。所以我的习惯是训练完立刻建一个自建验证集在签到场景用同样的摄像头录一段含多人出入的视频人工标注“第几秒到第几秒是谁”抽帧后组成正样本对同一个人和负样本对不同人。几十个人、每人十张图就够用了重点是这些样本和真实运行环境的分布一致。用这个验证集画 ROC 曲线从曲线上找等错误率对应的阈值再把阈值往严格方向调一点点比如加 0.02 到 0.05作为签到系统的默认阈值。为什么要往严格调因为签到场景漏识别一次可以重新刷脸误识别一次却是把别人的签到记到账上代价完全不同。阈值这个参数是整套系统里最需要根据现场重测的不要指望一个阈值走天下。5. 深度学习人脸识别签到系统的 5 个常见坑与排查清单5.1 坑一戴口罩、戴眼镜时识别率骤降现象同事戴上口罩后基本认不出来戴眼镜的人摘下眼镜后识别失败率明显上升。原因ArcFace 为主的预训练模型训练数据里遮挡样本占比很低模型没有学到“通过上半张脸也能判断身份”的能力眼镜反光则会在特征提取阶段引入局部噪声。解决第一种思路是用对遮挡鲁棒的训练方式比如 Partial FC 论文里提出的方法在训练时随机 mask 掉一部分特征让模型学会用剩余部分做判断第二种更接地气注册底库时给戴眼镜的人单独存一套眼镜版特征识别时先匹配同类型特征。第二种实施成本低很多效果立竿见影。5.2 坑二同一人多次识别特征距离波动大现象同一个人站在同一个位置前后两次算出的特征余弦相似度只有 0.5勉强过阈值。排查顺序先确认 L2 归一化有没有做——没做归一化时点积结果受特征模长影响模长分布不稳就导致分数剧烈波动再看预处理细节特别是 resize 尺寸和归一化方式如果训练时用的是除以 127.5 减 1推理时用了 0 到 1 的归一化特征分布完全对不上最后看检测对齐是否稳定人脸检测框在相邻帧之间抖动超过 10 个像素时裁出来的图内容变化很大特征自然漂移。我的习惯是把每帧的人脸框坐标和关键点坐标打出来如果框在跳优先修检测和对齐而不是怀疑模型。5.3 坑三GPU 显存占用很高但推理延迟没降现象部署到 GPU 服务器后显存占了几个 G但单张人脸推理还是几十毫秒和 CPU 差不多。原因最常见的是模型没走 TensorRTONNX Runtime 的 CUDAExecutionProvider 虽然有 GPU 加速但相比 TensorRT 仍少了很多算子融合和显存复用优化另一个原因是没有开启 FP16在支持的 GPU 上 FP16 推理比 FP32 快 2 到 3 倍。解决用 TensorRT 把 ONNX 模型转成 FP16 engine。签到场景一次只需要识别一个人脸batch size 设 1 就好不需要为大 batch 优化。5.4 坑四RGB 活体检测被高清屏幕绕过现象用一台高分辨率手机屏幕翻拍照片活体检测把它当成了真人签到成功。原因RGB 活体学到的最强特征往往是屏幕的摩尔纹、颜色偏差和反光当屏幕分辨率足够高时这些特征变得很弱模型就分不清了。解决最可靠的是加随机动作验证让用户按提示完成眨眼、点头或张嘴同时用关键点跟踪判断动作是否真实发生预算允许的话直接上红外加 RGB 双摄红外图里普通屏幕是纯黑的彩色照片和打印照片在红外下也完全不一样攻击难度指数级上升。这也是我的血泪经验一套看着检测率很高的 RGB 活体上线第二天就被一张打印照片破了。5.5 坑五签到了但数据库没有记录现象识别成功、界面上也显示“签到成功”但后台查不到记录。排查先用日志定位是在哪个环节丢的。绝大多数原因是识别服务和签到服务之间的 HTTP 回调失败网络超时或者异常被吞掉。解决别让识别服务只返回结果不管落库把落库放到识别服务内部先写一条 pending 状态的记录再异步更新为 confirmed或者干脆在识别服务里直接写数据库少一个中间环节就少一类故障。另一个容易出问题的是时间字段本地时间跨天和统计不同步会直接搞乱考勤报表建议统一存 UTC 时间戳展示时再转时区。6. 让签到系统跑得更稳模型量化、批量注册与端到端验收技巧模型训练完、逻辑跑通后还有几件工程上的事情能让系统从“能跑”变成“好用”。第一是模型量化。如果用 GPU 部署把 ONNX 模型用 TensorRT 转成 FP16 engine单张人脸推理延迟通常能从几十毫秒降到几毫秒显存占用也会降。没有 GPU 的嵌入式终端可以尝试把模型量化成 INT8代价是准确率会有小幅波动量完后用自建验证集重测一遍阈值别直接用原来的阈值。第二是批量注册。上百人的底库不能靠一张张手填做一个简单的批量导入脚本读取 CSV姓名、工号、照片路径逐张做人脸检测、对齐、特征提取写成 npy 文件或者存进数据库。脚本跑完打出一份注册失败名单把没检测到人脸或特征异常的样本剔除。第三是端到端验收。上线前录一段模拟真实考勤的视频多人轮流进出、有人戴帽子、有一张照片伪造攻击跑一遍完整流程记录三个指标正确识别率、误识别率、平均签到延迟。其中误识别率是红线宁可多几次漏识别重新刷脸也不要出现 A 签到了 B 账上的情况。这个方案值不值得做我的判断是深度学习人脸识别签到系统在“识别”这件事上技术已经非常成熟真正的工程难点在数据、阈值和活体这三件事上而不是模型本身。把 ArcFace、RetinaFace 这一套跑稳把底库管好把阈值校准好这套系统就能稳定服务几百人规模的使用。我自己每次部署新场景都要重新录一段视频重测阈值因为每台摄像头的色彩、视角、摆放位置都会影响特征分布。哪怕测试时一切正常也要留一个人工复核的入口防止极端情况下把陌生人当成签到人员放进来。这套习惯帮我避开了不少尴尬希望这篇笔记也能帮你少走点弯路。本文还有配套的精品资源点击获取