简介面向计算机相关专业毕业设计的高分项目以深度学习人脸识别考勤系统为核心适合正在准备毕设、课程设计或期末大作业的学生使用。项目基于FaceNet算法提取人脸特征覆盖人脸录入、人脸识别、考勤管理、课堂管理、班级管理、日志管理等完整功能模块可直接作为毕设主体也可用于项目实战练习。压缩包共2000个文件以1957个Python源码文件为主辅以PDF论文文档、TXT说明与少量前端样式脚本总大小84.31MB目录结构清晰便于按模块查阅与二次开发。资源已经过严格调试下载后即可运行并附带毕业论文相关文档可帮助理解算法原理与系统设计思路。目前已有698人学习下载适合需要完整可运行项目作为参考的计算机专业高年级学生。1. 为什么毕设选人脸识别考勤从答辩现场最怕的问题说起答辩现场老师第一句话往往是这个考勤系统的核心创新点在哪如果你答「用 OpenCV 打开摄像头、框个人脸、打个卡」大概率会被追问到沉默。人脸识别考勤这个题目难不在「识别准不准」而在「从摄像头到考勤记录这整条链路是否闭环」——人脸检测、特征提取、身份比对、考勤写入、异常处理缺一不可。这也是为什么它常年是 Python 本科毕设里性价比最高的选题之一深度学习的点有了软件开发的工作量有了论文也有得写。本文按我做这类毕设辅导项目的经验把整套系统的选型、编码、防坑和论文打法拆开讲。按这套思路哪怕从零开始六周内也能交付一个能支撑「高分项目」四个字的完整方案。2. 技术选型框架与模型怎么定直接决定你后面六周是写代码还是写配置2.1 人脸检测与人脸识别模型组合的对比人脸识别考勤系统的核心链路是「检测 → 对齐 → 特征提取 → 比对」。每一环都有不同选型毕设场景下组合方式通常有四类组合方案检测模型识别模型输出特征优点缺点传统视觉方案Haar CascadeLBPH数值型特征代码简单精度低受光照影响严重经典深度学习组合MTCNNFaceNet128维向量精度高、教程全、资料多检测速度略慢工业级方案RetinaFaceArcFace512维向量精度最高依赖大论文难写明白云端 API—第三方接口由服务商定义效果最好没法写进论文答辩一问就穿帮我一般建议毕设选第二类MTCNN 负责从图像里找出人脸框FaceNet 把人脸区域转成 128 维的特征向量。相比 Haarcascade 加上 LBPH 这种传统方案深度学习方案的识别率在考场、教室这类场景能到 98% 以上而且训练好的模型是现成可用的——你不需要自己从零训练一个 FaceNet直接在开源社区下载预训练权重就行。ArcFace 确实更强但 RetinaFace 的安装对环境要求更高很多同学的机器装到一半就放弃了不值当。这里提到一个常见误区不是所有写「深度学习」的毕设都需要自己训练模型。人脸识别考勤系统的重点是「工程化落地」不是「复现一篇顶会论文」。用预训练模型做推理把系统做好了、测试做透了论文一样有东西写。2.2 考勤业务对模型的要求先从「用什么机器跑」倒推选型前先问自己一个问题这套系统最终跑在哪台机器上如果你的开发机只有 CPU没有 NVIDIA 显卡那么 MTCNN FaceNet 的 CPU 推理速度大概是单人识别 200~500ms。一次考勤识别两个人脸加上摄像头采集和比对写库总耗时在 1 秒上下——对考勤场景完全够用。如果追求实时显示识别框的「门禁感」可以用 OpenCV 的 VideoCapture 把帧率压到 10fps 左右再配合多线程处理视觉上不会有明显卡顿。这是我反复验证过的一个结论本科毕设的人脸识别考勤系统模型不是瓶颈数据采集和业务闭环才是。你花一周调模型不如花两天把「摄像头 — 检测 — 比对 — 写库 — 展示」这条链路跑通后面所有时间都花在打磨这条链路上。如果机器没有 GPU就不要再想用 ArcFace 了那玩意儿 CPU 跑到飞起FaceNet 的权重文件约 90MB单帧推理几百毫秒是 CPU 机器上最舒服的方案。2.3 版本选择Python 与依赖库的搭配环境配置是卡住新手的第一道坎。这里直接给一套我测试过不会有兼容性灾难的版本组合Python 3.8 或 3.9 TensorFlow 2.10或直接 tensorflow-cpu 2.10.0 MTCNN 0.1.0自带网络定义和权重 facenet-pytorch 或 keras-facenet二选一推荐 keras-facenet安装即用 opencv-python 4.5.5 Flask 2.2用 Keras-FaceNet 的原因是它在 PyPI 上有直接封装好的包拉下来就能用不需要自己去折腾模型权重和预处理逻辑。MTCNN 也同理pip install mtcnn 之后直接 import。TensorFlow 版本之间坑很多TensorFlow 2.10 以上版本对 CUDA 的支持变了很多老教程里的 GPU 配置方式在 2.11 之后直接失效。如果你的机器没有独显直接装 tensorflow-cpu别装完整版——完整版会尝试加载 CUDA找不到库以后会报警告虽然不影响跑但看着烦。用 VSCode 写代码时记得ctrlshiftenter可以单行执行Python代码调这种模型推理的逻辑时特别好用。3. 系统设计到代码Flask MTCNN FaceNet 的最小可运行系统3.1 系统模块划分别把代码全塞进一个 main.py很多毕设源码翻车就翻在把所有功能写进一个 main.py启动、检测、比对、写库全在同一个文件里改一个功能动全局。我推荐按模块拆分按这个结构组织目录face_attendance/ ├── app.py # Flask 入口与路由 ├── face_module/ │ ├── detection.py # MTCNN 人脸检测封装 │ ├── embedding.py # FaceNet 特征提取封装 │ └── recognition.py # 比对与阈值判断 ├── database/ │ ├── db.py # 数据库连接与初始化 │ └── models.py # 表结构定义 ├── static/ # 前端静态文件 ├── templates/ # 前端页面 └── requirements.txt # 依赖清单这么拆有一个实际好处论文的技术架构图直接照这个目录画教师一眼就觉得你软件工程的基础是有的。而且调错时不用在一个千行文件里来回翻。前端页面的做法常见方案是用 HTML JavaScript 调摄像头把视频流用 Canvas 截取帧后 POST 到后端。如果你不想写前端用 Flask 的 render_template 返回一个包含摄像头的 HTML 页面即可。不建议在这个项目上引入 React/Vue——前端不是毕设的重点能跑就行。3.2 数据库建模考勤不是「认脸」而是「记账」人脸识别考勤系统的核心数据模型是「员工/学生 → 人脸特征 → 考勤记录」。需要三张核心表用户表user、人脸特征表face_feature、考勤记录表attendance。用户表字段设计字段类型说明idint, primary key主键user_novarchar(20)学号/工号namevarchar(50)姓名departmentvarchar(50)院系/部门create_timedatetime注册时间人脸特征表不能和用户表合并一个人可能多次重新录入人脸每次特征可能不同且特征向量是 128 维的浮点数组要么直接存二进制序列化对象要么存文本比如 JSON 数组单独一张表更清晰CREATE TABLE face_feature ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, feature BLOB NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) );早些时候我喜欢用 MySQL但后来觉得本科毕设如果用 SQLite 会更方便零配置、单文件、交付源码时不需要对方装数据库。毕设演示的时候最怕就是现场 MySQL 连不上、密码不对这种低级事故。SQLite 的 BLOB 字段也可以存储特征数据完全够用。除非你的指导教师特别强调使用 MySQL否则 SQLite 是最稳的选择。考勤记录表包含记录时间、考勤类型签到/签退、结果状态正常/迟到/缺勤这个后面可以在系统自动写入考勤后再配合一个后台管理页面做手动修正。3.3 注册与考勤的编码实现把链路跑通第一步是人脸注册。用户在摄像头前拍一张照片检测到人脸、提取特征后存库。关键代码如下# face_module/embedding.py import cv2 import numpy as np from mtcnn import MTCNN from keras_facenet import FaceNet detector MTCNN() # 人脸检测器 embedder FaceNet() # 特征提取模型 def register_face(image_path, user_id, db): 用户注册检测人脸并提取 128 维特征存入数据库 返回是否成功及人脸数量 img cv2.imread(image_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) faces detector.detect_faces(img_rgb) if len(faces) ! 1: return False, len(faces) # 必须只有一张人脸 x, y, w, h faces[0][box] # 扩大 20% 边界避免裁掉脸颊边缘 margin int(0.2 * w) x max(0, x - margin) y max(0, y - margin) x2 min(img.shape[1], x w margin * 2) y2 min(img.shape[0], y h margin * 2) face img_rgb[y:y2, x:x2] face cv2.resize(face, (160, 160)) feature embedder.embeddings(np.expand_dims(face, axis0))[0] db.save_feature(user_id, feature.tobytes()) return True, 1逻辑与参数说明detect_faces返回的 box 是[x, y, width, height]注意不是x1, y1, x2, y2格式这是最常见的粗心错margin 用来扩充边界因为 FaceNet 训练时对同样包含额头和下巴的裁剪方式更敏感直接按检测框裁剪会损失准确率160x160 是 FaceNet 的标准输入尺寸不能用其他值。所有特征在入库前用.tobytes()转成二进制匹配的时候再转回 float32 数组。第二步是考勤识别。摄像头捕捉到人脸后提取特征与库中特征逐一比对# face_module/recognition.py import numpy as np def cosine_similarity(a, b): 余弦相似度值越接近1表示越相似 return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) def match_faces(feature, threshold0.6): 在数据库特征集中查找匹配 返回 (user_id, similarity)无匹配返回 (None, 0) features db.get_all_features() best_id None best_score 0.0 for uid, feat in features: feat_array np.frombuffer(feat, dtypenp.float32) score cosine_similarity(feature, feat_array) if score best_score: best_score score best_id uid if best_score threshold: return best_id, best_score return None, best_score这个比对逻辑是开箱即用的不引入 scikit-learn自己实现余弦相似度反而让论文里可以放一个公式推导。threshold0.6是一个经验初值后面避坑章节我会专门讲这个值的调法。第三步是考勤记录写入。这一步的逻辑容易被忽视——识别到人脸只是拿到了「你是谁」考勤记录需要判断「今天是否已经打过卡」「是签到还是签退」# app.py from flask import Flask, request, jsonify from datetime import datetime, date app Flask(__name__) app.route(/attendance, methods[POST]) def take_attendance(): 接收前端摄像头传回的图片执行识别并写入考勤记录 file request.files[image] img np.frombuffer(file.read(), np.uint8) img cv2.imdecode(img, cv2.IMREAD_COLOR) feature_list detect_and_extract(img) # 返回特征列表 if not feature_list: return jsonify({status: no_face}) user_id, score match_faces(feature_list[0]) if user_id is None: return jsonify({status: unrecognized, score: score}) # 判断当天是否已签到 today date.today() if db.is_checked_in(user_id, today): return jsonify({status: already, name: db.get_user(user_id).name}) db.insert_attendance(user_id, datetime.now()) return jsonify({status: success, name: db.get_user(user_id).name})这里避开了重打卡漏洞考勤系统的业务要求是「一个学生一天只能打一次卡」不能识别成功就一直写记录那数据库会刷屏论文答辩时用这个逻辑也能展示你对业务有考虑。4. 避坑清单五个最容易让毕设翻车的真实问题4.1 现象识别准确率很高但 1:1 比对时经常「查无此人」原因注册时摄像头拍的是正面照片考勤时学生稍微侧脸、低头、戴眼镜MTCNN 仍能检出人脸框但 FaceNet 提取的特征和注册时的特征差异变大相似度降到阈值以下。解决这是考勤系统的常态不是 bug。不要把阈值调太低比如 0.5否则会导致不同人被误认为同一人。正确的做法是双重策略第一次匹配不通过时尝试对检测到的人脸做「人脸对齐」用 MTCNN 返回的眼睛、鼻子、嘴角关键点做仿射变换再提取一次特征重新比对。代码量不大但能明显提升侧脸场景通过率。在论文里写「加入人脸对齐后识别率从 91.3% 提升到 96.8%」是一个很漂亮的数据点。4.2 现象训练时没有报错但程序运行十几分钟后内存越占越大原因这是视频流处理最常见的坑。cv2.VideoCapture在循环里持续读取帧如果不把旧帧释放或者 Flaks 的 request.files 读取后没有关闭对象内存会逐渐累积。另一个隐藏原因每次识别比对都在遍历全表特征特征列表被重复加载到内存。解决这个坑的排查步骤很固定。第一把所有读取到的图像帧在使用后显式执行del或让变量重新赋值第二把全表特征在服务启动时加载一次到全局列表做增量更新不要每次请求都查库第三对摄像头推流使用cap.read()后立即cv2.waitKey(1)清空队列。在 code review 里查这三点内存超标的根因一定能找到。4.3 现象阈值调到 0.6A 同学能打卡B 同学打不了卡原因这不是阈值的问题是注册照片质量问题。有些同学注册时摄像头有强烈侧光导致提取出来的特征本身就偏或者注册时用的照片不是现场拍的而是从学信网导入的证件照。解决注册环节必须做规范建议三点硬性控制。第一现场拍照注册不允许上传历史照片第二检测不到人脸或检测到多张人脸时拒绝注册第三用「双次注册」——让用户拍两张照片分别提取特征计算两者相似度如果低于 0.7 说明照片质量有问题要求重拍。这就是用工程手段补数据质量。4.4 现象TensorFlow 版本装好后又装 opencv然后 OpenCV 报错找不到库原因这是一个经典的 Python 环境冲突。opencv-python 和 opencv-contrib-python 不能共存否则cv2的命名空间会被覆盖导致依赖异常另外在 macOS 上opencv 的摄像头权限需要在终端里授权第一次运行时容易被系统静默拦截。解决装之前先彻底卸载已有的 opencv 相关包再安装其中一个用 Anaconda 创建独立环境不要装在 base 环境里在代码入口处打印cv2.__version__和tensorflow.__version__确认双方都能导入。这是个血泪经验环境问题不会让你写不出代码但会让你在答辩前一天崩溃。4.5 现象用手机照片能骗过摄像头老师指出「没有活体检测」原因深度学习做人脸识别只能解决「是不是这个人」不能解决「是不是活人」。毕设答辩时不少老师会拿手机里的照片或者视频对着摄像头试一旦刷进去场面非常尴尬。解决本科毕设级别的活体检测不需要多高级用一个开源的 blink detection眨眼检测即可用 MTCNN 把人脸框出来后检测眼睛关键点的纵横比EAR连续若干帧内 EAR 值有明显从大到小的变化就判定为一次眨眼10 秒内至少有 1 次眨眼则通过活体校验。代码量 40 行左右论文里写「引入基于 EAR 的眨眼活体检测机制」足以应对 90% 的答辩质疑。5. 从源码到毕业论文高分项目交付的细节技巧5.1 测试方法与数据答辩老师最喜欢编的「数据从哪来」如果你在论文里写「用 XX 数据集测试准确率 96%」老师一定会追问「这个数据集里有你学生的人脸吗」这块需要自己构建测试集。做法是找同班 15~20 位同学每人采集 5 张照片——3 张作为注册集2 张作为测试集。分两次采集时间间隔至少一晚以保证测试集与注册集的拍摄环境不完全一致。准确率要算三个数识别率测试集中被正确识别出身份的比例、误识率把 A 识别成 B 的比例、拒识率对上的人没被认出的比例。论文里画一张三者的对照表再和传统 LBPH 方案的结果做对比这个数据就有说服力了。5.2 论文写作深度学习部分不要抄课本要写你自己的设置很多毕设论文的人脸识别章节会花大篇幅介绍卷积神经网络的概念和结构这是最浪费篇幅的做法。老师要看的是「你在这个系统里用了什么模型、输入输出是什么、为什么选它、参数怎么设的」。建议按这个顺序写先放系统总体架构图再写人脸检测子模块MTCNN 的结构图 你在代码里的参数设置再写特征提取子模块FaceNet 的损失函数设计、特征向量维度、归一化方式最后写考勤业务模块Flask 路由设计、数据库表关系。每个模块用「设计思路 — 代码实现 — 关键参数」三段式组织。源码不要全贴贴核心 20 行以内的代码段加上行内注释。这里要强调一个容易踩的细节论文里的所有图表必须和源码一致——如果架构图画的模块和目录结构对不上答辩时一眼就被看穿不如不做。5.3 答辩演示录视频比现场演示更让老师安心最后半小时的演示环节往往决定最终分数上限。现场演示最大的风险在于会场 WiFi 不稳定、摄像头驱动不对、光线条件差、电脑风扇声音盖过讲解。我见过太多因为现场翻车而把高分项目讲成及格项目的例子。一个更稳的做法是用事前录制好的演示视频分三个片段注册流程录入三个人脸、考勤打卡流程正常打卡、重复打卡拦截、陌生脸拦截、后台管理查看当天考勤报表。每个片段控制在 30 秒内视频画质要高、手机拍摄即可但要保证界面文字可读。如果老师要求现场演示再打开系统跑一遍实时识别视频作为备份。关于这个 Demo 的录制我有个习惯用系统自带的识别框叠加显示把相似度分数也显示在画面上——分数这个信息很直观老师能看到 0.93、0.87 这种数值在跳动比只输出一个「打卡成功」可信得多。5.4 最后一个建议源码交付前把这两件事做了源码交付是毕设评分的直接依据但很多人在最后一天才想起整理。我会提前两天做两件事第一在项目根目录写一个README.md包含环境安装命令、启动命令、测试账号和目录结构说明——这既方便答辩老师当场跑通也能让你的源码从「能跑但没法给别人跑」变成「可复现项目」第二把核心函数的参数注释补齐每个函数的 docstring 说明输入、输出和处理逻辑这部分代码本身就是论文对应章节的素材。我当年做这个选题时首版源码的数据库用的 MySQL答辩现场老师让拷走源码回实验室跑结果对方没有 MySQL环境起不来。后面把数据库替换成 SQLite启动变成一行命令就没有再因为这个扣过分。这个习惯一直保留到现在交付任何项目保证环境最简化、启动最快速、资料最完整。希望这些经验帮到你。本文还有配套的精品资源点击获取