☰
目标检测+人脸识别的企业考勤系统:从算法到Django落地的完整闭环
2026/10/1 1:20:04 网站建设 项目流程

简介:这是一份基于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)、ArcFace(insightface)、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 Django+MySQL的数据流:检测、识别、记录不是三条单独的服务

整个系统的数据流可以用一条时序描述:摄像头采集帧 → 目标检测模型返回人脸框 → 对人脸框做裁剪、缩放、对齐 → 识别模型生成特征向量 → 和 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.11,Django 4.x,MySQL 8.0,OpenCV,YOLOv8,face_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 Tools,pymysql是纯 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): Can't 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_length=20, unique=True) name = models.CharField("姓名", max_length=50) department = models.CharField("部门", max_length=100, blank=True) status = models.BooleanField("在职状态", default=True) created_at = models.DateTimeField("注册时间", auto_now_add=True) class FaceFeature(models.Model): employee = models.OneToOneField(Employee, on_delete=models.CASCADE, related_name="face_feature") feature = models.TextField("人脸特征向量") photo_path = models.CharField("照片路径", max_length=255, blank=True) class AttendanceRecord(models.Model): CHECK_TYPES = ( (1, "上班"), (2, "下班"), ) employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="records") check_type = models.SmallIntegerField("打卡类型", choices=CHECK_TYPES) check_time = models.DateTimeField("打卡时间") source_image = models.CharField("原图路径", max_length=255, blank=True)

FaceFeature用OneToOneField,因为每个员工只需要一份特征向量,防止冗余数据。feature字段我用TextField而不是BinaryField,理由是方便导出和备份,也方便在 Django Admin 里肉眼检查数据是否写入成功。特征向量生成后以逗号分隔的字符串存进去,取出来时numpy一行就能恢复成数组。

AttendanceRecord的source_image保留原图路径,这是很多人忽略的。考勤记录要真正可用,不能只有时间、工号、姓名,还需要能回溯到现场抓拍画面,防止事后扯皮。这里只存路径,不把图片二进制放进 MySQL,否则数据库会快速膨胀。

3.3 用 Django 执行查询与删除对象:ORM 比原生 SQL 慢在哪,为什么还要用批量操作

后端开发中,增删改查里最常见的三个操作是“注册员工时插入记录”“识别成功时插入考勤”“离职时删除员工及其特征”。Django ORM 做这些事很直观,但有个效率问题。

# 删除单个员工及其人脸特征 emp = Employee.objects.get(emp_no="E001") emp.delete() # 外键级联,OneToOne 对应的 FaceFeature 会自动删除 # 批量删除离职员工,注意 values_list 拿到 id 再删子表 emp_ids = Employee.objects.filter(department="测试部", status=False).values_list("id", flat=True) FaceFeature.objects.filter(employee_id__in=list(emp_ids)).delete() Employee.objects.filter(id__in=list(emp_ids)).delete()

这段代码里emp.delete()最直观,但它会在数据库层面执行外键级联,如果后续AttendanceRecord也是它的外键,并且你没有设置on_delete=CASCADE,就会报ProtectedError。所以“删除对象”前先想清楚:考勤记录属于审计数据,不应该跟着员工删除一起没了,正确的做法是把Employee.status置为False,而不是真的删行。

批量考勤写入时,我推荐用bulk_create。人脸识别匹配成功后会短时间内产生大量打卡记录,比如早上九点到十点之间几十个人排队,如果每个人来一次都save(),Django 会对每一条都发起独立事务,在识别线程和 Web 请求线程共用连接时很容易造成连接中断。bulk_create可以在一次 SQL 里塞进多条记录,batch_size=500能控制单次提交的大小。示例:

from django.utils import timezone records = [ AttendanceRecord(employee_id=emp.id, check_type=1, check_time=timezone.now()) for emp in matched_employees ] AttendanceRecord.objects.bulk_create(records, batch_size=500)

这里注意一个细节:bulk_create不会触发模型里的save()方法,也不会自动填充有auto_now_add的字段之外的东西,所以check_time必须在构造对象时显式赋值,不能依赖save()里的默认值。用 ORM 不等于放弃思考,恰恰相反,你要比写原生 SQL 更清楚每个字段该在哪里赋值。

4. 把目标检测和人脸识别跑起来:YOLOv8 + face_recognition 整合编码

后端的表结构只是容器,真正让考勤系统活起来的是“看到画面→找到人脸→认出是谁”这段代码。整合时最忌讳把检测和识别全塞进同一个函数,看起来省事,调试时两眼一抹黑,连问题是出在检测框还是特征比对都分不清。

4.1 本地安装 YOLOv8 和 face_recognition:版本搭配与前置依赖

先解决依赖,再谈代码。YOLOv8 用ultralytics官方包,安装命令是:

pip install ultralytics

face_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_path="weights/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(","), dtype=np.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, conf=0.5, iou=0.45, distance_threshold=0.5): model = get_det_model() results = model(frame, conf=conf, iou=iou, verbose=False) 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] # 转成 RGB,dlib 使用的输入格式 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, "Can't connect to local MySQL server through socket '/tmp/mysql.sock'")。在 Windows 上表现是“Can't 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.1,PORT写3306。认证插件的问题,我一般直接创建专用用户并用mysql_native_password插件:CREATE USER 'django_user'@'127.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_timeout=300",延长连接空闲有效期。更稳的方案是把“识别”和“写库”异步分离:识别线程只负责把人脸特征和结果写到内存队列,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。希望你拿到的是一个能直接跑起来的系统,也希望这些踩坑记录能帮你少走几段冤枉路。

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

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

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

立即咨询