
简介这是一份基于Python Django框架、融合目标检测与人脸识别的企业考勤系统毕业设计源码定位清晰面向计算机专业学生、毕业设计选题者以及需要快速搭建智能考勤系统的开发者。系统核心功能覆盖员工自动注册、人脸打卡、实时监控、迟到早退异常处理与考勤数据分析报告可直接作为毕设项目交付或企业考勤原型参考。资源共713个文件压缩包大小267.69MB其中307个py后端文件实现Django业务逻辑与人脸识别接口36个js、28个css和13个html构成前端页面与交互sql脚本提供数据库初始化结构另含docx说明文档与pptx答辩PPT便于撰写论文与汇报演示。目前已有92人学习下载。部署环境基于Python3.7与MySQL5.7配合Navicat与PyCharm完整前后端源码可直接运行调试适合借此掌握Django框架、目标检测与人脸识别在真实考勤场景中的整合落地方法。1. 目标检测人脸识别的企业考勤系统毕业设计不是玩具是能跑的闭环提到“目标检测人脸识别”的企业考勤系统很多做毕业设计的同学第一反应是“打开摄像头能认出我是谁”。但标题里三个关键词其实是一条完整链路目标检测负责从画面中找到人的位置和人脸框人脸识别负责把框出来的脸和库里已注册员工特征比对Django和MySQL负责把“谁在几点几分出现在摄像头前”固化成一条可查询、可统计的考勤记录。适合正在做计算机视觉方向毕设、想同时覆盖深度学习落地和 Web 后端开发的人也适合想把它从演示变成一个内网可用产品的研究者。说实话这类项目翻车的原因通常不是模型精度差而是“检测到人脸”之后没人管“识别出来是谁”和“记录有没有写入”所以后面我所有内容都按这个完整闭环来拆。2. 目标检测和人脸识别的边界为什么先“找脸”再“认脸”而不是只调一个接口考勤系统里把目标检测和人脸识别同时写进标题说明这两件事必须分开做而且还得分清楚谁先谁后。很多同学误以为装了face_recognition或insightface就能一条龙搞定实际在固定摄像头、复杂背景、多人同时经过的考勤场景里直接用识别模型从整幅画面里找人脸漏检率会高得离谱。正确思路是用目标检测模型做“人脸定位器”用识别模型做“人脸编码器”两者串成流水线。2.1 企业考勤里的目标检测它解决的是“人在哪里”的问题目标检测在考勤里的职责是定位人脸而不是直接告诉你是谁。摄像头装在门禁或工位旁边画面里可能有桌面、绿植、背景墙、路过的人识别模型直接处理全画面计算量大且容易被环境干扰。用 YOLOv8 这类目标检测模型先跑一遍输出若干个人脸框每个框对应一个人考勤系统需要的正是在这个框上做后续处理。常见做法是使用专门的人脸检测权重比如yolov8n-face.pt这类权重是拿人脸数据集微调过的对侧脸、小脸、遮挡的反馈比通用目标检测权重更友好。实际部署时目标检测框不会直接用原始框。YOLO 输出的框是模型内部归一化后的坐标你需要转回原图尺寸再往外扩至少 10%~20% 的边距因为额头和下巴是面部特征的一部分严格贴着检测框裁剪后续人脸识别会丢失关键特征。检测置信度conf建议设在 0.45~0.55 之间太高会把模糊可辨认的脸漏掉太低会把门把手或海报上的假脸识别成人脸。NMS 的iou参数一般保持默认 0.45~0.5主要目的是去掉重复框人脸间距本身不大iou设成 0.7 反而容易把两张接近的脸合并成一个框。不少考勤场景还有一个额外需求统计画面里同时出现的人数这也能用目标检测完成。在出入口装摄像头时系统可以先用人体检测判断“有没有人走到门口”再做人脸检测判断“是不是已注册员工”两级检测能过滤掉大量无效帧。这个过程用 YOLO 的results对象就能拿到两类数据先跑人体检测命中后再跑人脸检测避免每一帧都做全图人脸识别CPU 占用能降一半。2.2 人脸识别模型选型face_recognition、ArcFace 还是 Facenet人脸识别解决方案很多考勤系统最怕的是模型选错后面白调。我常用三类模型做对比face_recognition底层 dlib、ArcFaceinsightface、Facenet。三者的核心差异在特征向量维度、推理速度和工程接入难度。方案特征维度CPU 推理耗时单人脸依赖复杂度适合场景dlib / face_recognition128 维30~80ms需要 dlib 编译毕设、小型企业考勤InsightFace ArcFace512 维50~120msonnxruntime模型包较大门禁机、生产级Facenet128 维60~150msTensorFlow模型转换麻烦老项目、研究对比毕业设计优先推荐face_recognition原因是它做识别时把“检测、对齐、编码”封装在一起注册员工时只需一张正面照片系统就能生成稳定的 128 维特征向量。身份判断用欧氏距离距离小于阈值判定为同一个人。考勤现场如果对精度要求更高可以考虑 ArcFace但你需要额外做人脸对齐否则 512 维特征的优势发挥不出来。这里有个非常容易踩的坑很多人把“人脸识别”理解为“目标检测的扩展”于是直接拿face_recognition.face_locations()在整张图片上找人脸完全抛弃目标检测环节。这样在人少、光线好、摄像头近距离时能跑通但在真实考勤数据里超过两米距离、侧脸、逆光场景下 HOG 检测器会漏掉大量人脸。正确做法是目标检测负责找框人脸识别只负责对框内的脸做编码和比对二者各干各的互不干扰。2.3 DjangoMySQL的数据流检测、识别、记录不是三条单独的服务整个系统的数据流可以用一条时序描述摄像头采集帧 → 目标检测模型返回人脸框 → 对人脸框做裁剪、缩放、对齐 → 识别模型生成特征向量 → 和 MySQL 中已注册员工的特征向量比对 → 匹配成功后写入考勤记录表。这个流程里MySQL 只负责存数据和查数据“人脸特征比对”不应该放到 SQL 里去算。我知道有人尝试把特征向量存成BinaryField然后使用数据库的欧氏距离函数计算相似度这对数据量小的单机项目是能跑的但每次比对都要全表扫描性能极差。我在项目里常用的做法是系统启动时从FaceFeature表一次性加载所有已注册员工的特征到内存后续请求全部在内存中做numpy向量计算数据库只在注册、注销、查询考勤记录时才介入。到 Django 层面这条链路不是一个视图函数能装下的。我会拆成三层detection_service.py负责加载模型和目标检测recognition_service.py负责特征提取和身份匹配attendance_views.py负责接收前端请求、调用服务、访问 ORM。这样哪怕以后要把识别模型从 face_recognition 换成 ArcFace只需要改中间的一层后端和前端不受影响。3. 用 Django 搭建考勤后端从空项目到 MySQL 落库完整可复现步骤决定开工前先把环境列一遍Python 3.10 或 3.11Django 4.xMySQL 8.0OpenCVYOLOv8face_recognition。如果你看到的是“完整项目源码.zip”里面大概率已经帮你把这些依赖列在requirements.txt里但我的建议是不要直接信拿到源码第一件事是先手动创建一个干净虚拟环境然后根据实际的 Python 开一个能独立安装的依赖清单避免在一台机器上跑通、换个机器就心态爆炸。3.1 初始化 Django 工程和 app配置 MySQL 连接第一步是创建虚拟环境和项目骨架。用python -m venv而不是直接pip install到全局环境后面打包给答辩机器时才有后悔药。python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate pip install django mysqlclient pymysql opencv-python ultralytics face_recognition django-admin startproject attendance_sys cd attendance_sys python manage.py startapp attendance这里有个选型问题Django 连接 MySQL 有两种常见驱动mysqlclient编译安装稳定但 Windows 上需要预装 Visual C Build Toolspymysql是纯 Python 实现、安装快但性能略低。如果只是毕设和演示pymysql足够只需要在attendance_sys/__init__.py里写上import pymysql; pymysql.install_as_MySQLdb()。改settings.py里的数据库配置是关键我一般这样写DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: attendance_db, USER: root, PASSWORD: your_mysql_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: { charset: utf8mb4, autocommit: True, }, } }注意HOST一定要写127.0.0.1不要写localhost。Django 和 MySQL 客户端遇到localhost时默认走 Unix socket 或 Windows 命名管道如果你只启动了 TCP 监听就会报ERROR 2002 (HY000): Cant connect to local MySQL server through socket。写127.0.0.1强制走 TCP 协议排错最容易。charset用utf8mb4因为人脸特征向量保存成文本后可能包含空格、引号这些字符utf8存emoji也是问题虽然考勤数据里用不到表情但 demo 的“备注”字段可能有人会粘贴特殊符号。数据库建表语句也要一致在前面先执行CREATE DATABASE attendance_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这步不做后面写入中文姓名时会出现Incorrect string value报错。3.2 考勤核心数据表设计员工、人脸特征、打卡记录后端要能支撑“录入员工—注册人脸—记录考勤—统计报表”整个闭环至少需要三张表。下面是attendance/models.py里最常用的设计from django.db import models class Employee(models.Model): emp_no models.CharField(工号, max_length20, uniqueTrue) name models.CharField(姓名, max_length50) department models.CharField(部门, max_length100, blankTrue) status models.BooleanField(在职状态, defaultTrue) created_at models.DateTimeField(注册时间, auto_now_addTrue) class FaceFeature(models.Model): employee models.OneToOneField(Employee, on_deletemodels.CASCADE, related_nameface_feature) feature models.TextField(人脸特征向量) photo_path models.CharField(照片路径, max_length255, blankTrue) class AttendanceRecord(models.Model): CHECK_TYPES ( (1, 上班), (2, 下班), ) employee models.ForeignKey(Employee, on_deletemodels.CASCADE, related_namerecords) check_type models.SmallIntegerField(打卡类型, choicesCHECK_TYPES) check_time models.DateTimeField(打卡时间) source_image models.CharField(原图路径, max_length255, blankTrue)FaceFeature用OneToOneField因为每个员工只需要一份特征向量防止冗余数据。feature字段我用TextField而不是BinaryField理由是方便导出和备份也方便在 Django Admin 里肉眼检查数据是否写入成功。特征向量生成后以逗号分隔的字符串存进去取出来时numpy一行就能恢复成数组。AttendanceRecord的source_image保留原图路径这是很多人忽略的。考勤记录要真正可用不能只有时间、工号、姓名还需要能回溯到现场抓拍画面防止事后扯皮。这里只存路径不把图片二进制放进 MySQL否则数据库会快速膨胀。3.3 用 Django 执行查询与删除对象ORM 比原生 SQL 慢在哪为什么还要用批量操作后端开发中增删改查里最常见的三个操作是“注册员工时插入记录”“识别成功时插入考勤”“离职时删除员工及其特征”。Django ORM 做这些事很直观但有个效率问题。# 删除单个员工及其人脸特征 emp Employee.objects.get(emp_noE001) emp.delete() # 外键级联OneToOne 对应的 FaceFeature 会自动删除 # 批量删除离职员工注意 values_list 拿到 id 再删子表 emp_ids Employee.objects.filter(department测试部, statusFalse).values_list(id, flatTrue) FaceFeature.objects.filter(employee_id__inlist(emp_ids)).delete() Employee.objects.filter(id__inlist(emp_ids)).delete()这段代码里emp.delete()最直观但它会在数据库层面执行外键级联如果后续AttendanceRecord也是它的外键并且你没有设置on_deleteCASCADE就会报ProtectedError。所以“删除对象”前先想清楚考勤记录属于审计数据不应该跟着员工删除一起没了正确的做法是把Employee.status置为False而不是真的删行。批量考勤写入时我推荐用bulk_create。人脸识别匹配成功后会短时间内产生大量打卡记录比如早上九点到十点之间几十个人排队如果每个人来一次都save()Django 会对每一条都发起独立事务在识别线程和 Web 请求线程共用连接时很容易造成连接中断。bulk_create可以在一次 SQL 里塞进多条记录batch_size500能控制单次提交的大小。示例from django.utils import timezone records [ AttendanceRecord(employee_idemp.id, check_type1, check_timetimezone.now()) for emp in matched_employees ] AttendanceRecord.objects.bulk_create(records, batch_size500)这里注意一个细节bulk_create不会触发模型里的save()方法也不会自动填充有auto_now_add的字段之外的东西所以check_time必须在构造对象时显式赋值不能依赖save()里的默认值。用 ORM 不等于放弃思考恰恰相反你要比写原生 SQL 更清楚每个字段该在哪里赋值。4. 把目标检测和人脸识别跑起来YOLOv8 face_recognition 整合编码后端的表结构只是容器真正让考勤系统活起来的是“看到画面→找到人脸→认出是谁”这段代码。整合时最忌讳把检测和识别全塞进同一个函数看起来省事调试时两眼一抹黑连问题是出在检测框还是特征比对都分不清。4.1 本地安装 YOLOv8 和 face_recognition版本搭配与前置依赖先解决依赖再谈代码。YOLOv8 用ultralytics官方包安装命令是pip install ultralyticsface_recognition在 Windows 上最大的坑是dlib安装需要编译。如果你用的 Python 3.10/3.11必须先安装 Visual Studio 的 C Build Tools再执行pip install dlib face_recognition如果编译失败还有一个备选路线在另一个已经能运行的机器上pip download dlib拿到对应的.whl拷贝到当前机器离线安装。face_recognition这个包本身不是模型它只是围绕dlib的封装模型文件和权重是安装包自带的一部分所以最终打包项目时一定要确认.venv里确实有dlib和face_recognition的库文件而不是只有requirements.txt。目标检测模型权重单独放目录建议结构是weights/yolov8n-face.pt不要放在项目根目录和其他代码混在一起。模型权重大概 6~10MB体积不算大但答辩现场如果没网络临时下载会非常狼狈所以一进场就把权重文件放在固定路径代码里用绝对路径或Path(__file__).resolve().parent.parent去定位。4.2 写一个“检测识别”服务从图片到考勤记录的处理流程下面这段代码是考勤识别的核心服务我简化为一个函数作用是输入一张图片返回识别到的员工工号和距离。import cv2 import numpy as np import face_recognition from ultralytics import YOLO _det_model None def get_det_model(weight_pathweights/yolov8n-face.pt): global _det_model if _det_model is None: _det_model YOLO(weight_path) return _det_model def load_known_faces(): from attendance.models import FaceFeature known_encodings [] known_emp_ids [] for feature_row in FaceFeature.objects.select_related(employee).all(): arr np.array(feature_row.feature.split(,), dtypenp.float64) known_encodings.append(arr) known_emp_ids.append(feature_row.employee_id) return known_encodings, known_emp_ids def detect_and_recognize(frame, known_encodings, known_emp_ids, conf0.5, iou0.45, distance_threshold0.5): model get_det_model() results model(frame, confconf, iouiou, verboseFalse) result_list [] for result in results: for box in result.boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 [int(v) for v in box] # 扩展边框保留额头和下颚 pad int(max(x2 - x1, y2 - y1) * 0.1) x1 max(0, x1 - pad) y1 max(0, y1 - pad) x2 min(frame.shape[1], x2 pad) y2 min(frame.shape[0], y2 pad) face_img frame[y1:y2, x1:x2] # 转成 RGBdlib 使用的输入格式 face_rgb cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB) face_encodings face_recognition.face_encodings(face_rgb) if not face_encodings: continue distances face_recognition.face_distance(known_encodings, face_encodings[0]) min_idx int(np.argmin(distances)) if distances[min_idx] distance_threshold: result_list.append({ emp_id: known_emp_ids[min_idx], distance: round(float(distances[min_idx]), 4), bbox: [x1, y1, x2, y2], }) return result_list代码逻辑分四步先加载 YOLO 模型再遍历results里的人脸框然后对每个框做边界扩展并裁剪最后用face_recognition生成当前人脸编码并与员工库比对。face_recognition.face_distance返回的是欧氏距离距离越小越像distance_threshold默认 0.5实际使用时建议先公测收集几十张正常打卡照片算一下“本人距离的均值和标准差”再按均值加两倍标准差去设。这里还有个细节face_recognition.face_encodings(face_rgb)内部会对裁剪后的人脸区域再次进行检测如果 YOLO 框裁得太紧或图片过于模糊这一步会返回空数组所以我在裁剪时加了一个 10% 的pad。不要被“两次检测”吓到矢量编码阶段开销远大于检测真正耗时的瓶颈反而是特征比对numpy一维数组距离计算在几百个员工规模下几乎可忽略。4.3 摄像头实时考勤帧率、分辨率和置信度的平衡摄像头实时运行不能直接while True读取视频流然后全帧送进模型那样在 1080p 分辨率下 CPU 根本扛不住。我的习惯是先设置相机分辨率到 640x480 或 1280x720识别精度够用检测速度能快一倍。接着在循环里做“跳帧处理”每三帧只处理一帧。import cv2 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) frame_interval 3 frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 if frame_count % frame_interval ! 0: continue # frame 是 BGR识别函数里已做颜色转换 matches detect_and_recognize(frame, known_encodings, known_emp_ids) if matches: for m in matches: # 这里调用 Django ORM 写入 AttendanceRecord # 注意写成独立函数便于事务控制 print(识别到工号:, m[emp_id], 距离:, m[distance])frame_interval设成 3意味着每秒实际只跑 10~15 次检测但足以应对正常速度的进出。摄像机离门 2 米内人脸尺寸大约占画面宽度的 1/10这个分辨率下识别没问题如果距离拉到 5 米人脸太小建议把输入图改成整帧检测因为 YOLO 对小目标的支持比抠图好得多。conf在实时模式可以降低到 0.4因为你可以用多帧连续判断来弥补单帧误检而不是死守单帧置信度。一个隐藏的性能瓶颈是模型加载。如果 Django 采用默认的开发服务器它会在代码变化时自动重启进程每次重启都会重新加载 YOLO 权重一个 6MB 的模型加载肉眼可见地卡一下。更糟的是自动重载会同时启动两个进程可能把摄像头设备变成“忙”状态。解决方法是启动命令里加--noreload或者在get_det_model里用全局变量缓存模型实例。识别线程和模型加载线程分离也是避免卡顿的好做法。5. 避坑指南目标检测人脸识别考勤系统最容易踩的五个翻车点这个项目横跨目标检测、人脸识别、Django、MySQL 四块任何一个部分的配置问题都能让整个系统崩掉。以下五个翻车点是我在实际项目中遇到频率最高的每条按“现象→原因→解决”写清楚。5.1 MySQL 连接报错ERROR 2002 (HY000) 和 Django migrate 直接失败现象执行python manage.py migrate或者启动服务后第一次访问报错django.db.utils.OperationalError: (2002, Cant connect to local MySQL server through socket /tmp/mysql.sock)。在 Windows 上表现是“Cant connect to MySQL server on localhost (10061)”。原因MySQL 服务没启动或者 Django 配置里的HOST用了localhost。localhost在 MySQL 客户端里被解析为 Unix socket 文件而 MySQL 默认监听的是 TCP 端口 3306两边没对齐。另外新建的 MySQL 8 默认认证插件是caching_sha2_password旧版pymysql不兼容会导致Authentication plugin caching_sha2_password is not supported。解决先用命令行确认 MySQL 真能启动systemctl start mysql或 Windows 服务里启动 MySQL 服务然后用mysql -uroot -p测试本地连接。Django 的settings.py里HOST一律写成127.0.0.1PORT写3306。认证插件的问题我一般直接创建专用用户并用mysql_native_password插件CREATE USER django_user127.0.0.1 IDENTIFIED WITH mysql_native_password BY 密码;避开兼容性问题。5.2 Django 开发服务器自动重载把摄像头和模型拖进两个进程现象开着python manage.py runserver每次修改views.py保存后控制台自动重载摄像头画面卡住或者cv2.VideoCapture(0)报[ WARN:0] Cannot open video stream。原因Django 开发服务器默认使用自动重载修改代码时会启动一个新的 Python 进程来替代旧进程旧进程占用的摄像头没有及时释放新进程再去打开同一个摄像头设备就会被拒绝。模型权重也被重复加载内存瞬间翻倍。解决在项目开发期使用python manage.py runserver --noreload创建一个稳定单进程。模型加载使用模块级全局变量或functools.lru_cache确保初始化只发生一次。如果一定要开着自动重载调试那就把摄像头视频流放到一个独立进程中比如开一个单独的camera_worker.py通过共享内存或本地 socket 向 Django 传画面帧这比在一个进程里跟 Django 的生命周期斗智斗勇要省心。5.3 人脸识别对不上注册时用一张照片现场怎么调都识别不准现象系统注册员工时上传一张自拍识别阶段无论怎么站识别结果要么是别人要么提示未知人脸甚至返回的“本人距离”比库里的其他员工还大。原因人脸识别不是“像素对比”而是“特征空间距离”的比对。自拍角度、光照、表情、眼镜与否都会让同一个人的特征向量偏移。更关键的是很多人直接拿目标检测的裁剪框送进face_recognition没有做对齐和归一化导致在人脸上方缺一块、下方缺一块特征提取严重失真。解决先用关键点对齐再做特征提取。常见做法是用face_recognition.face_landmarks()拿到左右眼坐标算旋转角度然后用仿射变换把眼睛对齐到水平位置再统一缩放到 160x160 或 256x256。考勤现场注册时也别只取一张照片我现在习惯对每个员工采集 2~3 张不同角度的照片分别生成特征向量比对时取最小距离。距离阈值不要拍脑袋先用 10~20 个真实员工各拍 5 张照片做测试统计类内距离和类间距离再选一个区分度最高的阈值通常市区在 0.35~0.5 之间浮动。5.4 大量考勤写入时连接中断报 “MySQL server has gone away”现象早上上班高峰期几十人连续打卡过一会儿考勤记录开始丢失Django 报错InterfaceError: (0, )或OperationalError: (2006, MySQL server has gone away)。原因考勤识别的推理过程耗时较长一次识别从检测到比对可能需要几百毫秒期间数据库连接空闲时间超过 MySQL 的wait_timeout连接被服务端断开。识别线程里如果还用同一个 Django ORM 连接并发写入更容易把连接池撑爆。解决先设置CONN_MAX_AGE 60让 Django 复用连接同时把OPTIONS里加init_command: SET SESSION wait_timeout300延长连接空闲有效期。更稳的方案是把“识别”和“写库”异步分离识别线程只负责把人脸特征和结果写到内存队列Django 的manage.py启动一个后台任务每隔几秒批量检查队列并bulk_create写入 MySQL。这样即使单次识别很慢也不会阻塞后续打卡操作。写入时记得加事务重试失败就丢回队列等下一轮。5.5 答辩演示现场没有网离线安装 dlib、YOLO 权重和依赖库现象演示前发现现场是封闭网络pip install ultralytics face_recognition全部超时YOLO 权重文件也下载失败现场一片尴尬。原因face_recognition的依赖dlib需要编译而且ultralytics首次运行会从 GitHub 拉取一些配置文件如果没做离线准备基本上必死。解决准备离线部署时把整个虚拟环境一起打包或者至少把所有.whl文件提前下载到 U 盘。具体操作是在联网机器上执行pip download -r requirements.txt -d ./offline_packages然后把offline_packages拷到答辩机器执行pip install --no-index --find-links./offline_packages -r requirements.txt。YOLO 权重文件直接和源码放同一个目录不要依赖运行时下载。dlib 的.whl如果你那个 Python 版本没有对应的预编译包就只能在联网机器先把dlib编译好再pip download dlib拿到本地二进制包。这个准备动作花了半小时能避免 99% 的现场事故。6. 把系统打磨到能演示、能答辩验证方法、性能调优和一些硬经验最后一步不是再写功能而是把零散模块捏成一个可信的系统演示。我自己的验证方法是建一个自建考勤测试集把员工照片分为“标准照”和“现场抓拍”两类现场抓拍再按正常光、逆光、侧脸分三个子目录。跑一个最简单的统计脚本计算正确识别率、误识别人数、漏检人数。test_items [ (data/test/zhangsan_normal.jpg, E001), (data/test/lisi_backlight.jpg, E002), ] correct total 0 for img_path, true_emp_id in test_items: frame cv2.imread(img_path) matches detect_and_recognize(frame, known_encodings, known_emp_ids) pred_id matches[0][emp_id] if matches else None total 1 if pred_id true_emp_id: correct 1 print(识别率: %.2f%% % (correct / total * 100))这个脚本比任何跑起来的画面都有说服力它能让你知道自己距离开题报告里的“98% 准确率”还差多少。如果识别率很低先回去查对齐和阈值别急着调模型。性能调优再提三个优先级最高的事第一模型常驻内存不要让每次请求都重新加载权重第二人脸特征向量启动时全量拉进内存不要在识别循环里查数据库第三用 Django Channels 或 Redis 加一个 WebSocket 通道让后台识别结果主动推送到网页端。比如用channels实现识别模块识别到员工后往 channel layer 发一个 JSON前端页面实时弹出“张三 09:00:12 打卡成功”这样演示效果比让考官盯着数据库表强得多。答辩演示前务必准备一套“断网可用”的冷门环境预案。我用 Docker 镜像把 Python 环境、MySQL、代码、模型全部打包每次评审前先在另一台空机器上跑一遍docker-compose up确保从零到能看见画面不超过五分钟。我现在的习惯是所有模型权重、离线依赖、测试样例照片都放同一个assets/目录里连同源码一起打压缩包并写一个run.sh脚本把手动步骤全部自动化。演示翻车大多不是因为算法不好而是有人忘了建虚拟环境或者没启动 MySQL。希望你拿到的是一个能直接跑起来的系统也希望这些踩坑记录能帮你少走几段冤枉路。本文还有配套的精品资源点击获取