
简介这套基于深度学习的人脸识别考勤系统源码是面向计算机相关专业毕业设计的高分项目经导师指导并通过评审得分98分。资源适合正在完成毕业设计、课程设计或期末大作业的本科生也适合需要项目实战练习的Python学习者可用于快速搭建人脸识别考勤应用并理解完整实现流程。压缩包共25个文件约239MB内含Python核心程序与界面脚本、前端界面图片素材、字体图标文件、配置文件及配套说明手册覆盖源码、资源与文档三类内容便于对照学习。目前已有166人学习浏览。借助该资源读者可获得项目完整目录结构、人脸识别核心模块设计、UI界面与静态资源组织方式以及可直接运行或二次开发的基础代码能够节省从零搭建的时间帮助理解考勤业务流程与深度学习模型的工程化应用。1. 本科毕设答辩现场评委最想看的不是模型而是这一层闭环站在答辩讲台上被评委追问「这个深度学习模型是你自己训的吗」几乎是每个做系统类课题的人都会经历的一幕。基于深度学习的人脸识别考勤系统这个 python 毕业设计能拿到 98 分评审分靠的恰恰不是「重新发明人脸识别」而是把成熟的人脸特征提取能力完整地接进考勤业务注册人脸、摄像头实时识别、自动写签到记录、在界面里展示考勤结果这一整条业务链路是学生自己设计并实现的。这套项目适合正在做毕业设计、课程设计或期末大作业的计算机相关专业学生也适合想快速弄懂「深度学习模型怎么落地成业务系统」的一线开发拿来拆解。2. 整体架构拆解从摄像头到考勤数据库的完整闭环2.1 技术选型为什么是 dlib face_recognition Flask这套项目在 2018 年前后的本科毕设里是非常典型的一套组合拳。它没有从零训练卷积神经网络而是直接使用 dlib 自带的预训练 ResNet 模型提取人脸特征模型输出一个 128 维的特征向量。这个方案的好处很明显深度学习模型是现成的答辩时能讲清楚特征提取原理同时模型可以在 CPU 上推理不依赖 GPU演示环境不用讲究。OpenCV 负责读取摄像头画面、解码上传的图片、做人脸框的绘制face_recognition 库把人脸检测和人脸特征编码封装成两行调用大幅降低了业务开发的复杂度Flask 提供 HTTP 接口供桌面端和 Web 端共用SQLite 数据库保存用户白名单与每日考勤记录整个系统不依赖外部数据库服务拷贝就能迁移。桌面端用 tkinter 编写Web 端使用 static 目录下的静态资源两套界面共用同一个后端接口。这套选型在毕设场景里是经过权衡的。如果用 MTCNN FaceNet 自己搭推理链路模型转换和依赖安装就够折腾两周如果完全不用深度学习又过不了「基于深度学习」这个题目要求。dlib 的预训练模型让项目既有深度学习含量又能把主要精力放在考勤业务逻辑上这也是它能拿高分的原因之一。对想二次开发的读者来说这套结构还留了替换模型的余地。2.2 项目目录结构face-master 里每个模块负责什么拿到源码后先看目录结构比直接跑命令更重要。face-master 下的组织方式属于「后端接口 前端静态资源 打包产物」的经典分层face-master/ ├── api/ │ └── face.py # 人脸检测、特征提取、注册与签到接口 ├── static/ # Web 端静态资源背景、logo、用户列表 UI 素材 ├── Ui_test_01.py # 桌面端入口tkinter 编写的考勤演示界面 ├── config # 运行配置阈值、摄像头编号、存储路径 ├── msyh.ttf # 微软雅黑字体解决界面中文乱码 ├── 人脸识别-软件.zip # 打包好的演示版本答辩应急用 ├── 手册.docx # 使用手册对应论文与操作说明 └── .gitignoreapi/face.py 是核心后端所有识别逻辑都收口在这里static 里那一堆 png 前景图、用户列表背景图、logo 素材是给 Web 界面摆放人脸框和用户信息用的不是无用文件。Ui_test_01.py 是桌面端测试界面负责调用 api 层的能力本质上是一个客户端壳子。值得注意的还有 msyh.ttf 字体文件和「人脸识别-软件.zip」。前者说明项目早期一定踩过中文显示成方块的问题后者说明做完以后用 PyInstaller 打过包。这两样对答辩演示非常实用后面第 5 章的踩坑记录里我会专门展开讲。2.3 数据流与表结构设计人脸特征怎么存、怎么比整个系统的核心链路只有两条。注册链路是摄像头拍一张照片或者上传一张图片后端先做人脸检测确认画面里只有一张脸再提取这张脸的 128 维特征向量连同 user_id、姓名一起存入数据库。签到链路是摄像头持续取帧每隔几帧做一次人脸检测检出人脸后提取特征与库里所有已注册特征逐一计算欧氏距离距离小于阈值就判定为同一个人写入一条签到记录同时把用户信息返回给前端展示。数据库表结构也围绕这两个链路设计。源码里一般情况下会建两张核心表user_info 保存用户白名单和人脸特征attendance_record 保存每日签到流水CREATE TABLE user_info ( user_id TEXT PRIMARY KEY, name TEXT NOT NULL, face_feature BLOB, register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, checkin_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );face_feature 字段用 BLOB 存储常见做法是把 128 维 numpy 数组通过 tobytes() 序列化后写入识别时再读到内存用 np.frombuffer() 还原。这种做法的好处是特征保存在业务库里导出、备份都随库走缺点是换模型后旧特征维度不匹配需要重新注册一次。第 6 章改造部分我会给出一个更稳的存储方案。3. 核心模块拆解注册、识别、防重复签到的完整实现3.1 注册接口为什么强制要求画面里只能有一张人脸api/face.py 里的注册接口是整套系统的第一个关键节点。它的输入是 user_id、姓名和一张人脸图片输出是注册成功或失败。核心逻辑拆出来大概是这样的# api/face.py - 用户注册接口简化版 app.route(/api/register, methods[POST]) def register(): user_id request.form.get(user_id) name request.form.get(name) file request.files.get(face_image) # 从二进制解码成 OpenCV 图像 img cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) # 人脸检测boxes 是所有人脸框坐标列表 boxes face_recognition.face_locations(img) # 强制要求只能有一张人脸 if len(boxes) ! 1: return jsonify({code: 400, msg: 画面中必须只有一张人脸}) # 提取第一张人脸的 128 维特征向量 encoding face_recognition.face_encodings(img, boxes)[0] # 将特征与用户信息写入数据库 save_face_feature(user_id, name, encoding) return jsonify({code: 0, msg: 注册成功})face_recognition.face_locations() 返回的是图片中人脸框的 (top, right, bottom, left) 坐标列表默认使用 HOG 检测器CPU 上单张图耗时约几十毫秒作为注册接口完全够用。face_encodings() 会检测到的人脸裁剪后送入预训练模型输出 128 维 float 数组。强制「必须只有一张人脸」不是故意刁难用户而是为了保证特征入库的正确性如果画面里有两张脸采集到的特征就无法确定归属于哪个 user_id后期识别时还会出现特征混淆。我在实际测试中还发现注册照片如果太模糊或光线不足face_locations 会检测不到人脸这时候接口应该返回「未检测到人脸请重新拍摄」而不是让前端转圈。3.2 签到接口特征比对和阈值设置的取舍签到接口是识别链路的核心逻辑上比注册多两步先比对库里所有人脸特征找到最近的那一个再判断这个人今天是否已经打过卡。核心代码框架如下# api/face.py - 考勤识别接口简化版 app.route(/api/checkin, methods[POST]) def checkin(): # 从摄像头取一帧 frame get_frame_from_camera() # 检测当前帧中所有人脸 boxes face_recognition.face_locations(frame) for box in boxes: encoding face_recognition.face_encodings(frame, [box])[0] # 与库中所有特征比较返回距离最小的用户 user find_nearest_user(encoding, threshold0.55) # 匹配成功且当天未签到则写入记录 if user and not has_checked_in_today(user[user_id]): insert_attendance(user[user_id]) return jsonify({code: 0, user: user[name]}) return jsonify({code: 404, msg: 未识别到有效人员})find_nearest_user 内部做的事情是遍历 user_info 表中所有人脸特征用 np.linalg.norm 计算欧氏距离取最小值对应的用户同时判断这个最小值是否小于阈值。face_recognition 库自带的 compare_faces 也能完成这件事但返回的是布尔列表不方便拿到具体距离值所以我更推荐自己写距离计算方便调参。阈值这东西很玄学定得太大容易误识别定得太小又导致真人签不上。常见范围是 0.5 到 0.6项目里默认 0.55 是个折中值。如果现场灯光不好或者摄像头离得远我一般会把阈值放宽到 0.58再配合多点采样取平均来降低误判概率。3.3 两套界面共用一套 APItkinter 桌面端与 Web 前端的协作方式这套项目很有意思的一点是同时存在桌面端和 Web 端两套展示界面。Ui_test_01.py 是用 tkinter 写的桌面窗口static 目录则是给 Web 页面准备的资源。两套界面最终都通过 requests 或 ajax 调用同一个 Flask 接口这种设计在答辩时特别加分因为能同时展示桌面开发和前端知识而且后端代码一套搞定。桌面端按钮回调的核心逻辑大致是这样的# Ui_test_01.py 中签到按钮回调节选 def on_checkin_click(): resp requests.post( http://127.0.0.1:5000/api/checkin, files{frame: capture_photo()} ).json() if resp[code] 0: label_result.config(textf签到成功{resp[user]}) else: label_result.config(textresp[msg])capture_photo() 负责从本地摄像头采集一帧图片然后通过 multipart/form-data 上传给后端。Web 端则用 ajax 做同样的事情只是把摄像头采集换成了 video 标签截图。这里有一个容易犯的错误如果直接打开 static 下的 HTML 文件而不是通过 Flask 路由访问浏览器跨域会直接拦截请求。正确做法是先启动后端从后端渲染页面再在页面里调用同源 API。4. 运行部署从依赖安装到联调签到的完整流程4.1 环境准备版本锁定是第一件正经事这套项目的时间点在 2018 年依赖库版本和现在的 Python 环境有不小差异。直接 pip install 最新版容易出现 dlib 编译失败、face_recognition 与新版 numpy 不兼容这类问题。我建议先在一个干净的 Python 3.6 或 3.7 环境里安装依赖尽量锁住版本pip install -r requirements.txt一般 requirements.txt 里会包含这些核心库numpy1.18.5 opencv-python4.4.0.46 dlib19.22.0 face_recognition1.3.0 Flask1.1.2 pandas1.0.5 pillow7.2.0这里最挑剔的是 dlib。在 Windows 上默认源码编译需要 Visual Studio 环境很多人卡在这一步。常见做法是直接下载预编译的 dlib wheel 文件安装省去编译过程。face_recognition 依赖 dlib必须等 dlib 装好以后再装。如果用的是 Python 3.8 以上版本dlib 的预编译包不太好找这也是我建议锁 Python 3.6/3.7 的原因。4.2 config 文件摄像头编号、阈值这些参数去哪改项目里有一个 config 文件负责集中管理运行参数。它类似于 ini 配置里面一般会写摄像头编号、模型路径、数据库路径、识别阈值这些关键配置[face] model_path models/dlib_face_recognition_resnet_model_v1.dat threshold 0.55 camera_index 0 frame_interval 3 [db] path attendance.db几个参数的含义和调法我列一下。camera_index 是摄像头编号通常笔记本自带摄像头是 0外接 USB 摄像头可能是 1黑屏时一般先改这里。threshold 是前面说过的欧氏距离阈值0.5 到 0.6 之间调。frame_interval 是抽帧间隔每 3 帧处理一次降低 CPU 压力。model_path 指向 dlib 的预训练模型文件这个文件解压后约 99MB路径不能写错。config 文件的意义在于答辩演示时不改代码就能切换摄像头或调整识别灵敏度。有些拿到源码的同学不知道有这个文件把没找到模型的问题当成代码 bug 去排查其实只是 model_path 没对上。4.3 启动顺序先起后端再开界面最后联调正确启动步骤应该是固定的不然会出现界面开了但接口连不上的尴尬情况# 第一步启动 Flask 后端服务 python api/face.py # 第二步另开一个终端启动桌面端 python Ui_test_01.py后端正常启动后Flask 默认在 5000 端口监听。Ui_test_01.py 里写的请求地址是 http://127.0.0.1:5000两者必须保持一致。如果 5000 端口被占用启动时 Flask 会报错常见做法是在 face.py 末尾修改 app.run(port5001)同时把 Ui_test_01.py 里的请求地址同步改掉。联调的时候建议先用注册功能录一个正面照再用同一张脸走签到流程。第一次运行如果摄像头一直黑屏先确认 config 里的 camera_index如果提示「未识别到有效人员」先确认注册时的照片是不是正脸、光线是否足够。整体流程跑通后再尝试打包或调整界面样式。注意摄像头是独占设备桌面端打开摄像头以后Web 页面再调摄像头会取不到画面。演示时最好只保留一个界面占用摄像头。5. 避坑排查跑这个系统最常见的五个翻车点5.1 摄像头黑屏提示无法获取画面现象启动 Ui_test_01.py 后窗口能打开但视频区域是全黑的控制台报错 vidio capture error 或者 IndexError。原因绝大多数情况是摄像头编号不对。笔记本自带摄像头在 0 号外接 USB 摄像头可能占用了 1 号或更高编号也有可能是另一个程序已经占用了摄像头导致 OpenCV 拿不到画面。解决先打开任务管理器看有没有其他应用占用摄像头关掉以后把 config 里的 camera_index 改成 1 再试。如果还是黑写一行临时代码打印摄像头可用编号逐个测试。5.2 同一个人签到失败或者把另一个人识别成自己现象注册时用的是正面照签到时换了个角度或者灯光暗一点就提示「未识别到有效人员」更头疼的是两个长得像的人互相误识别。原因阈值设置不合理是最直接的原因。阈值太严同一个人稍有差异就匹配不上阈值太松不同人特征距离也可能小于阈值。另一个常见原因是注册时的照片质量太差特征本身不具代表性。解决先把 config 里的 threshold 从 0.55 放宽到 0.58观察误识别率如果误识别变多再降回 0.53。同时把注册照片换成光线均匀的正面照。距离阈值这种事换了环境就得重新标定一次别指望一个参数吃遍所有场景。5.3 界面上的中文变成方块或乱码现象姓名、签到提示在 tkinter 界面里显示成一个个方框英文正常中文全是菱形问号。原因这是 2018 年前后做 tkinter 中文应用踩得最频繁的坑。tkinter 在非中文系统下默认字体不含中文字形需要指定中文字体文件。项目里特意放了一个 msyh.ttf就是为了兜底这个场景。解决在 Ui_test_01.py 里创建 Label 或 按钮时显式指定字体常见做法是 font(微软雅黑, 14)或者用 msyh.ttf 文件路径加载字体。遇到用 Pillow 绘制中文时也需要先加载 msyh.ttf 再 ImageFont.truetype 调用。5.4 SQLite 报 database is locked现象注册和签到操作偶尔抛出 OperationalError: database is locked尤其在连续快速操作时出现频率更高。原因Flask 默认是多线程处理请求两个请求同时往 SQLite 写数据时一个操作没释放写锁另一个就报 locked。SQLite 适合低并发场景但架不住多个请求同时写。解决常见做法是把数据库连接设成 check_same_threadFalse并给写操作加一个线程锁让签到写入串行化。我一般还会把 sqlite3.connect 的超时参数设成 10 秒减少偶发锁冲突。如果并发量真的上去了再考虑换成 MySQL那就是另一个课题了。5.5 PyInstaller 打包后双击闪退现象本机运行一切正常用 PyInstaller 打包成 exe 之后双击秒退命令行方式启动才能看到报错信息。原因两个经典问题。第一dlib 的动态链接库没有被打包进去第二模型文件路径用的是相对路径解压后 exe 和 models 目录不在一个位置。PyInstaller 打包的依赖分析经常漏掉 dlib 的 DLL。解决打包命令里用 --add-data 把模型文件和 msyh.ttf 一并打进去然后在代码里判断是否运行在打包环境有 sys._MEIPASS 就用临时解压目录否则用当前目录。项目里那份「人脸识别-软件.zip」演示版其实就是按照这个思路打包的答辩时拿到别人的电脑上也能跑。6. 二次开发换模型、加报表把项目改成你自己的课题6.1 先抽离特征提取器给换模型留好接口如果想把「基于深度学习」这个点讲得更有分量最值得改的是把 dlib 特征替换成 MTCNN 或 FaceNet 这类模型。改造时不用动签到流程只需要把特征存储结构改成「模型名 维度 向量」三件套# 特征存储建议统一成如下结构 { user_id: 2024001, name: A同学, model: facenet_v1, dim: 512, feature: [0.012, -0.034, ...] }这样替换模型时只需改特征提取和距离计算逻辑比对函数仍然是「向量 阈值」的无状态函数。注意不同模型的输出维度不一样dlib 是 128 维FaceNet 是 512 维切换模型后旧注册数据全部作废需要重新入库。这个坑一定要提前跟答辩评委讲清楚反而能体现你理解模型层面的事。6.2 加一个考勤统计报表顺便复习 pandas考勤系统光有签到记录还不够导师很可能会问「怎么按月统计出勤率」。给项目加一个报表导出功能其实很快用 pandas 读 SQLite 再导出 CSV 就行def export_daily_report(date): df pd.read_sql( SELECT user_id, name, checkin_time FROM attendance_record WHERE date(checkin_time)?, conn, params(date,) ) df.to_csv(fdaily_{date}.csv, indexFalse, encodingutf-8-sig)encoding 用 utf-8-sig 是因为 Excel 直接打开 UTF-8 的 CSV 会乱码这个细节在答辩演示时很加分。改造完成后建议用两个用例验证自己注册一张正面照并签到确认能正确写考勤找一个未注册的人测试误识别拦截确认不会漏签到。从那以后我每次接这类毕设项目都会先确认特征维度、数据库字段、前端接口三者保持一致再继续改业务代码希望帮到你。本文还有配套的精品资源点击获取