☰
基于深度学习的人脸识别签到系统:从MTCNN到FaceNet完整实现
2026/10/17 6:58:33 网站建设 项目流程

简介:这是一套面向毕业设计场景的基于深度学习的人脸识别签到系统完整项目包,适合计算机相关专业学生借鉴人脸检测、身份比对、签到记录等模块的设计与实现。项目使用Flask作为Web后端,集成了人脸识别模型、前端页面模板和SQLite数据库,并包含数据库迁移、管理员初始化等可运行配置,支持从本地环境搭建到功能演示的完整流程,同时涵盖人脸注册、签到记录与用户管理等典型功能。资源共27个文件,主要类型包括Python源码、HTML模板、人脸模型数据、SQLite数据库、依赖清单与说明文档,压缩包约101.47MB,按模型、模板、静态资源、迁移脚本等模块组织目录。已有404人浏览学习,适合需要参考完整毕业设计结构、快速搭建同类签到系统或理解深度学习落地流程的读者。

1. 这门课设从“打开摄像头”开始:基于深度学习的人脸识别签到系统

第一次做人脸识别课设,最容易踩的坑不是模型不收敛,而是把摄像头对准教室门口时系统根本认不出人——这是某高校一门计算机视觉课程设计的典型诉求。所谓毕业设计,往往从“做一个能用的系统”变成“把深度学习流程走完再交一份文档”,而基于深度学习的人脸识别签到系统恰恰是这一类项目的代表:它同时要求你处理数据集、模型训练、后端接口和前端交互,是一个能把卷积神经网络、特征提取、人脸比对、签到记录落进同一个工程里的完整粒度的课设题目。这篇方案面向正在选型或已经开题的学生,目标是把“怎么搭结构、怎么选模型、怎么把MTCNN检测加FaceNet特征提取串成一条可用链路”讲清楚,同时给出能直接抄的数据库设计和接口约定,让读者拿到后可以照着改出自己的版本。

2. 需求分析与技术选型:为什么人脸签到不是“人脸识别”就完事

2.1 先拆功能需求:四个模块缺一个都显得不像毕设

在动手写代码前,我会建议先把需求拆成四个责任明确的模块:人脸采集与注册、人脸检测与识别、签到记录管理、后台统计与导出。很多同学把注意力全放在识别准确率上,结果答辩时被问“签到记录存在哪、怎么按课程筛选、怎么防止同一张脸重复签到”就卡住了。

四个模块的职责边界应该这样划分。人脸采集与注册负责在前端打开摄像头抓帧,把检测到的正脸裁切、对齐后存入人脸特征库,同时生成一个与学号绑定的身份ID;人脸检测与识别是核心,需要实时检测摄像头画面中的人脸,提取特征并与库中特征比对,返回身份Top-1及相似度;签到记录管理负责每次识别成功后写入一条带时间戳的签到记录,并处理重复签到、迟到早退的状态判定;后台统计与导出则是给管理员查看按课程、按日期聚合的出勤率,并支持导出Excel清单。这四个模块串起来,才算一套完整的签到系统,而不是一个人脸识别Demo。

2.2 技术选型:MTCNN检测加FaceNet特征,后端用轻量框架

技术选型上我推荐一套“检测与识别分离”的组合:检测用MTCNN,它输出人脸框、关键点坐标,方便你做对齐;识别用FaceNet预训练模型,直接提取512维特征向量,以欧氏距离度量相似度。之所以不选端到端的单模型方案,是因为毕设阶段自己从头训一个人脸识别模型成本太高,而FaceNet的预训练权重在公开数据集上效果稳定,换到自建的小规模签到场景下足够用。

后端我一般选Flask,原因有两个:一是它轻量,写REST接口快,适合跟PyTorch推理逻辑放同一个进程;二是答辩时要演示“摄像头画面传到后端、后端返回识别结果并写库”这条链路,Flask的路由和请求处理足够直观。数据库用SQLite起步,后期数据量大再换MySQL,表结构设计不变。前端部分,用浏览器打开页面调用摄像头,通过Canvas定时截帧上传识别,避免写原生桌面客户端,工作量能省掉三分之一。

注意:这类课设的系统边界要控制在“识别到人并写入签到记录”为止,不要过度设计成带复杂权限管理和消息通知的完整平台,否则中期答辩前很容易进度失控。

3. 总体架构设计:三层结构与四个模块的职责划分

3.1 目录结构与数据流向

依赖安装用requirements.txt统一管理。训练与评估脚本独立成文件,便于答辩时展示“模型怎么调、效果怎么量”。

3.2 数据库设计:学生表、人脸特征表、签到记录表三张表就够了

数据库是整个系统容易被忽略但答辩高频提问的部分。我设计三张核心表。学生表记录学号、姓名、班级;人脸特征表存储学号对应的512维特征向量,以BLOB或TEXT格式存储;签到记录表记录每次识别成功的学号、时间、课程ID和状态。三张表的关系是学生表与学生表一对多关联人脸特征表,签到记录表通过学号关联学生表。

字段上需要特别注意:人脸特征向量是固定512维的浮点数组,SQLite里可以用TEXT存JSON序列化结果,检索时读出后转成numpy数组再比对,不要直接存进数据库去跑距离计算。课程表可以并入签到记录表,用course_id字段表达,避免为了一个可选属性拆出太多表。状态字段建议用int:0代表正常,1代表迟到,2代表早退,3代表重复签到,这样统计时直接聚合,不用做字符串匹配。

3.3 识别链路设计:从帧到签到记录的完整流程

人脸识别签到的核心链路可以拆成四步:

摄像头帧 --> MTCNN检测人脸框 --> FaceNet提取512维特征 --> 与特征库比对取Top-1 --> 写签到记录

每一步都需要明确输入输出。摄像头帧来自前端Canvas截取的JPEG图像,服务端收到后先用MTCNN检测,若画面中有多张脸则逐张处理。检测到的脸需要先做alignment,也就是根据两只眼睛的关键点坐标做仿射变换,把脸摆正,否则同一人脸在不同角度下的特征差异会直接导致误拒。特征提取后与特征库做欧氏距离计算,距离小于阈值的最高分候选才判定为识别成功;没有候选人则返回未注册。

延迟和准确率之间存在天然矛盾,这也是答辩时的加分点:当画面中存在多人时,是逐张串行识别还是并行处理?我通常的做法是保持串行加线程池,因为签到场景下同时出现在摄像头前的通常不会超过三五个人,串行完全够用,且代码好调试。

提示:识别阈值的选择不要拍脑袋,建议在自建数据集上做一次距离分布统计,把“同一人距离均值”和“不同人距离均值”画出来,阈值取两者的分界点,这个分析过程放论文里很好用。

4. 核心实现:人脸注册、实时识别与签到链路落地

4.1 人脸采集与特征入库:抓帧、检测、抽取特征三步走

实现从注册接口开始。前端打开摄像头后连续截帧上传,后端对每帧做人脸检测,只要有可用的正脸就直接抽取特征入库,不需要像传统方案那样必须等到N帧都检测到才算成功——因为人脸注册阶段通常会让学生正对镜头保持两秒,单帧质量足够。

注册流程的代码如下,先看batch处理的部分,也就是对一批学生进行注册:

def register_student(student_id, frames): """ 注册:对传入的多帧图像做人脸检测 + 特征提取 frames: list of np.ndarray,前端上传的连续帧 return: 注册成功返回特征向量列表,失败返回错误码 """ detector = MTCNN() embedder = FaceNet() features = [] for frame in frames: boxes, landmarks = detector.detect(frame) if len(boxes) != 1: continue # 只能有一张脸,多张或零张都跳过 aligned = align_face(frame, landmarks[0]) # 关键点对齐 feat = embedder.embed(aligned) # 1x512 向量 features.append(feat.flatten()) if len(features) < 3: return {"code": 40001, "msg": "有效人脸帧不足,请重新采集"} avg_feature = np.mean(features, axis=0) # 取平均作为特征 save_feature_to_db(student_id, avg_feature) return {"code": 0, "msg": "注册成功", "feature_dim": len(avg_feature)}

这段代码里有一个容易被忽略的细节:特征要取多帧平均。单帧提取的特征可能因为眨眼、表情变化产生噪声,取三五帧的平均后再入库,比对时稳定性更好。MTCNN检测返回的landmarks包含了左眼、右眼、鼻尖、左嘴角、右嘴角五个关键点,对齐函数就是利用这些点计算仿射矩阵,把两眼连线旋转到水平方向。

注册成功后可以顺手打印一条日志,包含学号和特征向量前16维的数值,方便答辩时展示特征到底长什么样。很多同学到答辩时都说不清512维向量是什么,这个打印能帮你把“高维特征”落到一个具体可见的例子上。

4.2 实时识别:阈值、Top-1与签到状态判定

识别接口是整个系统的核心。前端每隔1秒传一帧JPEG过来,后端逐帧做检测和比对,返回识别结果。比对部分需要一个阈值控制:库里有上千人的时候,相似度最高的也可能是错的人,阈值就是最后一道防线。

错误率与阈值的关系需要预先在自建数据集上测出ROC曲线,通常的做法是取误识率低于1%时的距离值。例如,如果某数据集的等错误率点在0.8左右,那么距离阈值取0.9比较安全,高于0.9时宁可显示“未注册”也不要硬认。在签到场景下,误识带来的麻烦远大于拒识。

签到状态判定直接落到这三张表的联合更新:

def check_attendance(student_id, course_id, current_time, threshold=0.9): """ 判定本次签到是否有效,返回是否重复签到 """ student = get_student(student_id) if not student: return {"code": 404, "msg": "学生不存在"} last_record = get_last_record(student_id, course_id) if last_record: previous_time = datetime.strptime(last_record["created_at"], "%Y-%m-%d %H:%M:%S") if (current_time - previous_time).seconds < 60: return {"code": 30001, "msg": "重复签到,已忽略"} status = 0 if current_time.hour < 10 else 1 # 演示用,按具体课程时间调整 insert_attendance(student_id, course_id, current_time, status) return {"code": 0, "msg": "签到成功", "status": status}

这里限制60秒内同一课程同一学生不可重复签到,是防止有人连刷。实际场景中,如果你用的是教室固定摄像头,课程开始后前十分钟陆续进人,每人都要签到一次,这个60秒窗口仍然够用。

4.3 前端与后端接口约定:保持最小可用的原则

前端不用做得太重,三个接口串起整个过程就够了:注册接口完成学生信息与人脸特征绑定,识别接口上传一帧并换回身份信息,查询接口按课程ID返回签到统计列表。前端页面由三个视图组成:签到视图用来打开摄像头并连续识别,注册视图提供学号和姓名输入框加一个“开始采集”按钮,统计视图用一个简单表格展示签到记录。

接口异常返回码要统一,这是我自己带项目时反复强调的。例如40001表示有效人脸帧不足,404表示学生不存在,30001表示重复签到。评审老师会看你有没有考虑边界情况,统一的返回码结构本身就是设计文档里值得写一节的内容。

5. 避坑指南:人脸识别签到系统里最容易翻车的五个点

5.1 摄像头画面模糊导致检测率极低

现象是系统能跑起来,但前端连续传了十几帧后端都检测不到人脸,注册和识别都卡在第一步。原因通常有两个:一是教室光线不足,摄像头自动曝光导致画面偏暗;二是前端截帧时拿到的Canvas幅面过小,人脸在画面里只占很小一块区域。解决方法是先在前端加一个人脸框引导和光线提示,检测不到时提示“请正对镜头,保证面部受光”,同时截帧前把画面放大到宽高不小于640像素再上传。

5.2 特征库比对全部超阈值,谁都认不出

现象是注册后测试识别,返回结果总是“未注册”。这种情况我先检查MTCNN对齐是否生效,如果landmarks使用姿势不对,抽取的特征会在不同朝向间漂移,同一人不同帧的距离比阈值还大。通常的做法是在注册后立刻打印一条同一人自比的距离,如果自比距离已经超过0.7,说明注册阶段取帧质量太差或对齐没有生效,而不是阈值设小了。

5.3 SQLite多人并发写冲突

现象是局域网内多个前端同时调用接口时,偶尔会抛出数据库锁错误。原因很简单:SQLite在并发写场景下不适合高频写入。解决思路是给数据库连接加一个全局锁,或者把写入操作做成串行队列;更简单粗暴的做法是识别接口和签到接口分开连接,签到时创建新连接并把写操作包在事务里,写完立即释放。

5.4 同一个学生重复签到

刷脸签到和二维码签到有一个本质区别:人可以在几天内重复进入摄像头范围,所以一定要设计防重复逻辑。只靠前端判断不发起请求是完全不够的,后端必须在签到记录表里做一次重复校验,逻辑我已经在上面check_attendance里给出。如果在答辩时被问到“有人站在摄像头前不走,会不会重复签到”,这段逻辑就是你最好的回应。

5.5 答辩现场摄像头无法调用

这是最玄学的翻车点——在宿舍调试好的摄像头调用代码,换到教室投影仪的电脑上,浏览器提示找不到设备。原因基本是浏览器权限策略或非HTTPS环境不允许navigator.mediaDevices访问摄像头。解决办法是答辩前准备一套本地用OpenCV接摄像头的备选方案,后端直接用cv2.VideoCapture(0)读帧,前端只做界面展示,不做真实采集。这样即使浏览器环境下摄像头被禁用,系统也能通过本机摄像头完成演示。

6. 验收与答辩:从“跑通”到“讲清楚”的三个关键动作

验收环节对毕设类项目来说,本质是一次“用你的系统完成真实任务”的测试。建议你自己先走一遍完整流程:注册一个新学生,再在摄像头前识别签到,确认记录写入、统计视图出现该学生的出勤状态。这个流程走完只算及格,能把系统讲清楚才算优秀。

我会提前准备好一组自测问题,比方说:让你现场注册一个不存在的人,系统提示什么;重复签到会怎样;两张相似度较高的人脸是否能正确区分——这些问题在答辩现场被问到的概率很高。为了量化表现,建议准备一个小图集,包含注册学生、未注册学生、相似脸对三种样本,用脚本跑一遍识别,输出每张图的Top-1结果和距离值,形成一个简单的评估表。这个表放在论文里会很有说服力。

答辩时还有一个技巧:把“阈值选择”讲成一个决策过程,不要只说“我设了0.8”。你可以说,我统计了200对同一人和200对不同人的距离分布,阈值取两个分布交界处,得到一个可解释的边界。这样讲,比起单纯报一个准确率数字,更能体现你对问题的理解深度。

我习惯在做完这类项目后留一个固定动作:把代码从零开始重新部署一遍,记录每一步需要安装什么、修改什么路径、配置什么环境变量。我是从一次答辩前环境崩溃的血泪经验里养成这个习惯的——那次因为换了机器,依赖版本冲突导致Demo当场跑不起来,后来我强制自己每次交付前都走一遍“干净部署清单”。

从那以后每次做完项目都会把这份清单和阈值分析图一起放进附件里,答辩时不用临场翻代码也能从容讲完。希望这篇方案能帮你把毕设做到“能演示、能解释、能出报告”的水准。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询