ARTICLE DETAIL

资讯详情

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

人脸识别考勤系统源码解析:从模型链路到阈值调优

人脸识别考勤系统源码解析:从模型链路到阈值调优 简介这是一份基于深度学习的人脸识别考勤系统毕业设计源码包面向计算机相关专业毕业生可用于毕业设计、课程设计或期末大作业也适合需要项目实战练习的开发者。项目覆盖人脸检测、特征提取与比对、考勤记录入库等完整流程代码按训练、预测、工具模块划分并包含三元组损失训练验证与模型评估脚本经过严格调试可直接运行并在此基础上二次开发。资源共48个文件整包约13.4MB包含17个Python源文件模型训练、预测、工具函数、9个pyc编译文件、7张示例图片、4个txt说明、3个xml配置、2个md文档、1个docx操作手册以及预训练h5模型文件等目录结构清晰便于按模块查阅学习。已有532人学习下载可用作开题、答辩或功能演示的可靠参考也能帮助深入理解深度学习在人脸识别考勤中的落地路径。1. 人脸识别考勤系统最难的从来不是模型而是“别认错人”有人问过我一件事基于深度学习的人脸识别考勤系统和普通的人脸识别门禁机到底差在哪。我说差在“考勤”这两个字。门禁机只要判断你是不是这个人考勤系统还要决定什么情况下算你打了卡、什么情况下不算还要把每一次判断写成一条能追溯的记录。所以这类源码包的价值不在模型有多新而在它把“人脸识别”和“考勤记录”串成了一条完整链路检测、对齐、提特征、比对、写库。这篇文章面向三类人。第一类是毕业设计拿到这套源码、想尽快跑通的人第二类是想把人脸识别摄像头搬进自己项目、但不想从零写模型的工程师第三类是认真评估“我能不能用一套开源毕设做小范围考勤”的人。我会沿着“识别链路→数据准备→跑通工作流→常见翻车→验证与进阶”这个顺序讲中间会给出可以抄的参数和代码。先说明一个反直觉结论这套系统里最坑的不是深度学习模型而是那个你随手填的比对阈值。2. 识别链路与模型选型检测、对齐、提特征、比对缺一环就翻车一个人脸识别考勤系统无论外面包的是 Django 界面还是 PyQt 窗口核心链路永远只有四步人脸检测、人脸对齐、特征提取、特征比对。这四步每一环都有独立的模型或算法毕设源码的跑通难点也基本分布在这四环里。2.1 检测与对齐为什么不能只靠 OpenCV 的 Haar 级联第一步是把摄像头画面里的人脸框出来。早期教材喜欢用 OpenCV 自带的 Haar 级联分类器因为它零依赖、一行代码就能跑。但用在考勤场景里它有两个致命毛病一是正脸检测好侧脸和二三十度俯仰角直接丢框二是背景里偶尔会把拉杆箱、阴影当成人脸。基于深度学习的检测器在这两点上明显更稳。毕设系统里最常出现的检测器是 MTCNN。它在 CPU 上也能跑检测同时返回人脸框和五个关键点坐标这个副产品直接决定了对齐能不能做。稍微新一点的方案是 RetinaFace对侧脸和模糊帧更鲁棒但权重文件更大、依赖更强。选型建议很简单想少装依赖就 MTCNN想精度高一点就 RetinaFaceUltraFace 之类的轻量模型不建议在毕设阶段折腾。检测模型CPU 表现侧脸/遮挡返回内容毕设推荐度Haar 级联快几乎不行只有人脸框不推荐MTCNN可用一般框 5 关键点推荐RetinaFace慢较好框 关键点 3D 信息追求精度选它这里特别强调对齐。检测框只告诉你“脸在哪”对齐才告诉你“眼睛鼻子嘴分别在哪”。MTCNN 返回的五个关键点就是用来做仿射变换的把两只眼睛的连线拉平把鼻尖对准图像中心把脸统一缩放到 160×160 或 112×112。不做对齐就直接提特征同一张脸换个角度就提取出完全不同的向量考勤系统会当场翻车。所以拿到源码后先别急着跑先确认流程里有没有关键点仿射这一步。没有的建议你后续自己补上。2.2 特征提取FaceNet 还是 ArcFace毕设数据量决定选择对齐之后人脸图要喂给特征提取模型。所谓“特征”不是人能看懂的纹理描述而是模型把一张人脸压缩成一个 512 维浮点向量。考勤识别的本质变成注册库里保存每个人若干条 512 维向量现场来一张脸提取向量后和库里逐一比较距离。这里有个关键认知这类源码包一般不会让你从零训一个特征提取模型而是加载预训练权重。常见的预训练选择是两个流派。FaceNet 是 Google 提出的三元组损失模型facenet-pytorch 里提供了现成的 InceptionResnetV1加载vggface2或casia-webface预训练权重就能用。ArcFace 属于基于角间隔的 softmax 改进insightface 项目里提供了arcface_r100、w600k_r50等权重。两者的核心差别不在精度高低而在生态和使用难度。模型向量维度预训练数据规模姿态宽容度落地难度FaceNet (InceptionResnetV1)512百万级中等依赖少pip 即用ArcFace (R100)512千万级较高依赖 insightfaceC 扩展多毕设数据量通常只有几十个人、每人几十张图这种规模下 ArcFace 的优势发挥不出来FaceNet 反而因为安装简单、报错少成为首选。如果你手里这批数据的采集条件一般比如室内灯光不均匀、手机拍摄居多那我一般会建议直接用 FaceNet 预训练权重。数据规模上了 500 人以上再考虑 ArcFace那个量级下它的识别边界才会明显更好。检测、对齐、特征提取三个模型是串联的每次识别都要走完一整条链路。这也是为什么这类系统对 GPU 不敏感、对 CPU 更敏感单帧跑下来大部分时间耗在 MTCNN 检测上。2.3 比对逻辑余弦相似度和阈值才是考勤系统的决策点特征提取之后就是比对。常见做法是计算两个人脸向量的余弦相似度公式是两向量点积除以各自模长的乘积结果落在 -1 到 1 之间。余弦相似度越接近 1表示两个向量方向越一致人脸越像。也有用欧氏距离的FaceNet 原论文用的是欧氏距离距离越小越像但考试系统代码里用余弦相似度更直观。比对逻辑本身很简单真正要命的是阈值设置。阈值就是那条分界线相似度大于阈值判定为同一人小于等于阈值判定为陌生人。如果是考勤系统每次判定还要追加一个动作——大于阈值就写入一条打卡记录小于阈值就让新面孔进入“是否注册”的流程。这里有一个普遍误区初学者会把阈值拍脑袋设成 0.85 甚至 0.9结果同一个人换个角度就识别失败另一些人设成 0.5结果谁都能互相打卡。正确做法是统计一批“同一人的不同照片”和“不同人的照片”算出两类相似度的分布在中间找一个漏报和误报都少的位置。后面第五章会给具体的验证方法。先记住一点阈值没有万能值和你手里的摄像头、光照、预训练模型强相关但通常落在 0.6 到 0.75 区间。3. 拿到源码和全部数据后的跑通工作流三步做完一次完整考勤前面把原理拆完了这一章开始动真格。拿到“源码全部数据”的压缩包建议先看目录结构再做环境不要先装了三个小时的依赖才发现模型文件路径是错的。这里的步骤是这类毕设项目最常见、稳定的做法。3.1 环境准备先把 Python 装对深度学习框架别混着装这类源码大部分基于 Python 3.7 到 3.9 写的3.10 以后某些老代码会踩到类型转换的坑。我一般会建议用 conda 建独立环境避免和系统 Python 打架。conda create -n face_attendance python3.8 conda activate face_attendance pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install facenet-pytorch opencv-python numpy pip install insightface onnxruntime第一行创建 Python 3.8 环境所有依赖都装在这里面不污染系统环境。第二行安装 PyTorchcu118表示 CUDA 11.8 版本如果你的机器只有 CPU把--index-url那一段去掉直接pip install torch torchvision即可。facenet-pytorch 是 FaceNet 的 PyTorch 实现它自带 MTCNN 检测器和 InceptionResnetV1 特征模型一个包覆盖了链路的大半。insightface 是 ArcFace 派系的工具库如果你决定用 ArcFace 路线它和 onnxruntime 搭配。装完之后先验证环境这步能省掉后面一半的排错时间。import torch print(torch.__version__) print(torch.cuda.is_available()) from facenet_pytorch import MTCNN, InceptionResnetV1 print(facenet-pytorch loaded ok)如果torch.cuda.is_available()返回 False不影响跑通识别速度会慢一些。MTCNN 在 CPU 上单帧检测大约 200~300 毫秒摄像头场景下勉强能用。这里看到的InceptionResnetV1就是我们后续要用的特征提取器。很多人直接跳过验证开始写主程序结果到识别那一步才发现模型的权重下载失败白白浪费一晚上。3.2 人脸数据目录怎么摆一个数据组织脚本省掉注册模块的返工“全部数据”是什么意思通常指两类数据一类是预训练模型的权重文件另一类是各个人的人脸照片。照片的组织方式直接决定后面识别主程序怎么写。我见过太多源码注册人脸时一个 List 里塞几十个 BGR 数组看起来能跑但第二天加人就要改代码。正确做法是用文件夹结构组织人员数据。data/ known_faces/ student_01/ frame_01.jpg frame_02.jpg frame_03.jpg student_02/ frame_01.jpg frame_02.jpg frame_03.jpg每个子文件夹代表一个人员里面的图片就是这个人各个角度的照片。代码里用os.listdir()遍历每个子文件夹取平均值或选择与现场最相似的一张作为该人的代表向量。这个结构的好处是加人只加文件夹删人只删文件夹主程序完全不用改。为了让你对齐真实数据集我给一段常用的统计脚本可以快速看出每个人的样本量够不够。import os root data/known_faces for person in sorted(os.listdir(root)): person_dir os.path.join(root, person) imgs [f for f in os.listdir(person_dir) if f.lower().endswith((.jpg, .jpeg, .png))] print(f{person}: {len(imgs)} 张)这段脚本遍历每个人员文件夹统计里面的图片数量。刚拿到数据时先跑一遍如果发现有人的图片少于 15 张要重点标记。人脸识别在样本量少的时候特别容易阈值漂移一个人总共 3 张正面照换张侧脸基本就识别不出来了。补充拍摄时尽量覆盖正脸、左右侧脸、戴不戴眼镜、不同光照这几种情况。3.3 最小跑通主流程从摄像头到考勤表的一段完整流程环境没问题、数据目录也对了接下来是核心主流程。下面这段代码是一个极简实现做的事情是打开摄像头→MTCNN 检测和对齐→FaceNet 提取特征→与已有的人员特征比较→写入 SQLite 考勤表。import cv2 import numpy as np import sqlite3 from facenet_pytorch import MTCNN, InceptionResnetV1 import torch device torch.device(cuda if torch.cuda.is_available() else cpu) mtcnn MTCNN(image_size160, margin0, min_face_size40, devicedevice) resnet InceptionResnetV1(pretrainedvggface2).eval().to(device) def get_embedding(frame): # 检测并对齐人脸返回 160x160 的 Tensor face mtcnn(frame) if face is None: return None # 增加 batch 维度后送入特征提取模型 emb resnet(face.unsqueeze(0).to(device)) return emb.detach().cpu().numpy()[0] def cosine_sim(a, b): a a / np.linalg.norm(a) b b / np.linalg.norm(b) return float(np.dot(a, b)) THRESHOLD 0.68 known_embs {student_01: get_embedding(cv2.imread(data/known_faces/student_01/frame_01.jpg)), student_02: get_embedding(cv2.imread(data/known_faces/student_02/frame_01.jpg))} cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break emb get_embedding(frame) if emb is None: continue best_person, best_score None, -1 for person, known_emb in known_embs.items(): if known_emb is None: continue score cosine_sim(emb, known_emb) if score best_score: best_score, best_person score, person if best_score THRESHOLD: conn sqlite3.connect(attendance.db) conn.execute(INSERT INTO attendance (person, ts) VALUES (?, datetime(now)), (best_person,)) conn.commit() conn.close() print(f{best_person} 打卡成功相似度 {best_score:.3f}) # 这里可以加显示逻辑省略这段代码的逻辑分三段。第一段初始化 MTCNN 和 FaceNetMTCNN 的image_size160决定了对齐后的脸统一缩放到 160×160min_face_size40表示小于 40 像素的人脸直接忽略避免远处小脸误检margin0表示对齐时不额外扩边。第二段是特征提取函数先检测人脸检测不到返回 None检测到了就增加一个 batch 维度送进 resnet最后转成 numpy 数组。第三段是摄像头循环每一帧提取特征后遍历库里的人员特征算余弦相似度超过阈值就写库。写考勤表那一步用了 SQLite这是毕设系统最常用的方案单文件、零部署。字段只有person和ts后者是datetime(now)自动生成的时间戳。这个表结构够跑通但如果一天只打一次卡需要你自行加时间去重逻辑。3.4 源码包的文件结构先按数据→模型→主程序→界面的顺序找入口拿到压缩包不要急着运行。先浏览一遍目录识别出这个项目的主入口文件。毕设级源码包通常包含下面几类文件按重要程度排是这样的文件角色常见命名作用数据整理/采集脚本collect_data.py、sort_data.py把原始照片整理成上面讲的按人分文件夹训练或特征提取脚本train.py、extract.py对已知人脸提取特征生成代表向量识别主程序main.py、recognize.py摄像头实时识别负责比对和显示考勤记录模块attendance.py、db.py负责数据库写入和查询界面层ui.py、app.py可视化操作界面可选用 PyQt/Tkinter 实现找入口有个经验先找main.py再看它 import 了哪些本地模块。如果main.py里 import 了一堆不认识的自定义模块把项目根目录加入PYTHONPATH或者用 PyCharm 直接打开整个项目目录。很多毕设源码依赖相对导入直接python xxx.py会报ModuleNotFoundError原因就是没有从项目根目录执行。我把这一步称为“先理顺目录再谈运行”能帮你避开第一波崩溃。4. 人脸识别考勤系统的 5 个常见翻车点现象、原因、解决如果说前几章是纸上谈兵这一章就是血泪经验。毕设级人脸识别考勤系统的报错集中在下面五个位置每条都按“现象→原因→解决”的顺序写方便你对号排查。4.1 翻车点一阈值设成 0.5谁的脸都能打上卡现象是 A 同学站在摄像头前识别结果显示 B 同学打卡成功而且相似度显示 0.68、0.72 这种忽高忽低的值。如果阈值只有 0.5等于把所有“看起来是亚洲年轻男性”都判成同一个人了。原因很简单预训练模型的同类相似度分布通常在 0.6~0.9 之间异类相似度在 0.2~0.6 之间0.5 正好落在重叠区。你把分界线放在重叠区误报不可避免。解决方法是立刻调阈值从 0.65 起步逐步往上加。有一个快速办法准备三组照片一组是“同一个人不同角度”一组是“不同的人”分别跑相似度画成两个区间看重叠位置。实测下来室内固定摄像头场景0.7 左右是安全区。4.2 翻车点二同一个同学换个光线就提示“新用户”现象是注册时用的是工位正脸照现场走进来的是逆光侧脸系统直接判成陌生人连注册流程都进不去。原因有两层。第一层是数据层注册照片只有一张正面照模型提取出的向量覆盖不了多姿态变化第二层是链路层摄像头画面质量差、人脸没对齐提取出的特征和注册时差异较大。解决分三步。第一步每个人员补到 20~30 张图覆盖正脸、左右转头、戴上和摘下眼镜。第二步确认检测器的min_face_size不要设得太大我常用 40太大时远一点的脸直接不检测。第三步把注册时的所有人脸向量做平均或取中位数不要只存一张图的特征。4.3 翻车点三界面显示识别成功考勤表里却没有记录现象是屏幕上已经打出了“张三 打卡成功”但打开数据库查询是空的或者只有第一天有记录。原因大概率出在数据库写入环节。毕设源码里常见的坑有三个一是没执行commit()SQLite 的事务没有提交二是插入表时字段名和时间格式对不上写库抛异常但界面异常没有被打印出来三是考勤记录表里设置了唯一约束同一个人第二次打卡违反了约束整个插入操作被回滚。解决的第一个动作是把代码里的裸conn.execute()改成先print(sql)再执行确认 SQL 语句没问题。第二个动作是在写库外面套 try-except把异常信息打印出来而不是吞掉。第三个动作是确认需求如果一天只允许打一次卡唯一约束加在(person, date(ts))上如果允许多次就不要加唯一约束。4.4 翻车点四GPU 显存溢出摄像头画面一卡一卡现象是程序运行几秒后报CUDA out of memory爆显存。原因是很多毕设源码会把检测模型、特征提取模型以及多帧缓存全部塞进 GPU。MTCNN 本身有内部缓存机制长跑过程中逐步积累加上特征模型占用的显存小显存卡很容易扛不住。而且这类代码常犯的毛病是每一帧都调用一次torch.no_grad()但检测器内部默认keep_aspect_ratio时会创建大量中间张量。解决方法是做显存分配把 MTCNN 放在 GPU 上特征模型也放 GPU但强制限制 PyTorch 显存占用另一个更粗暴有效的办法是把 MTCNN 放到 CPU 上特征模型用 GPU。实测这样组合后4G 显存的机器可以稳定跑摄像头流。另外帧率不要拉满设置cap.set(cv2.CAP_PROP_FPS, 15)给检测器留出释放中间缓存的时间。4.5 翻车点五源码换一台电脑就崩报找不到模型文件现象是 compressing 到别的机器上运行报错FileNotFoundError: No such file or directory: C:/Users/xxx/models/facenet.pt。原因是源码里写死了绝对路径。毕设项目从自己电脑到交到别人手里路径一定会变这是最典型的便携性坑。另一个相关坑是相对路径没有基于项目根目录计算而是基于当前工作目录。解决方法是建一个config.py统一存放所有路径和参数并且路径全部基于项目根目录计算。import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) MODEL_DIR os.path.join(BASE_DIR, models) KNOWN_FACES_DIR os.path.join(BASE_DIR, data, known_faces) ATTENDANCE_DB os.path.join(BASE_DIR, attendance.db) RECOGNITION_THRESHOLD 0.68这段代码把模型目录、数据目录、数据库路径、阈值都收拢到一个文件里。以后换机器只改RECOGNITION_THRESHOLD和确认MODEL_DIR下文件真实存在其他地方不用动。这是我在这类项目里坚持的习惯一切会变的参数都必须集中管理散落在代码里的硬编码迟早是要还的。5. 把考勤系统验证到可用阈值校准、反作弊和离线模拟系统能跑通只是第一步能不能真正用于考勤还要经过验证和进阶调整。这里我讲一个定期要做的小实验以及一个容易被毕业设计忽略的现实边界。5.1 用同人/异人对做阈值校准比拍脑袋填数可靠得多验证阈值最有效的操作是只建一个临时脚本加载你手里的数据统计两类相似度分布同一个人不同照片的相似度和不同人的照片之间的相似度。统计完画两个区间的直方图或者只打印最小值和最大值。对比类型相似度最小值相似度最大值建议阈值同一个人0.720.930.70 可接受不同的人0.210.660.70 安全这组数据是想说明一个思考方式阈值取同人最小值以下、异人最大值以上才是最理想位置。如果同人最小值和异人最大值之间有间隙直接取中点如果没有间隙说明当前预训练模型对这批数据的区分度不够再好的阈值也救不回来只能换模型或补数据。另一个简单的进阶验证是重放一段视频把注册人的视频提前录好用离线脚本逐帧识别并记录相似度曲线观察有没有连续掉到阈值以下的时段。这样做的好处是避免反复站到摄像头前测试。5.2 人脸识别考勤系统反作弊的现实边界打印照片、平板里循环播放视频是这类毕设系统最常见的作弊方式。照片的攻击方式是用静态帧的相似度骗过识别器视频攻击则可能直接骗过特征提取。这类系统本身没有活体检测机制我不建议在毕设阶段强行上复杂的 3D 活体方案。可靠且简单的缓解办法是识别成功后再随机要求一次眨眼或转头用一个轻量的关键点运动判断来拦截静态照片。这个方案只能挡住照片挡不住视频但在教学或班级考勤场景里已经足够。另一个角度是多个摄像头交叉验证成本高小场景不推荐。5.3 我的一个习惯和最后一条建议我现在接手任何一个人脸识别考勤项目第一件事就是强制编辑config.py把所有阈值、路径、模型名集中放好然后才对主流程做单元级的测试。这个习惯起源于一次把阈值写在主程序里、后来调参找了一晚上的翻车经历。诚心建议你也做同样的事。人脸识别考勤系统的复杂度不在数学而在工程聚合检测、对齐、特征、比对、写库、异常处理全都咬合在一起任何一环崩了都表现为“识别坏了”或“记录丢了”。先把变量管好再把链路跑熟最后再谈优化。希望这篇文章能帮你少走这一段弯路也希望你能在跑通之后把阈值校准和路径管理这两个习惯一起带走。本文还有配套的精品资源点击获取
返回列表