简介一份面向计算机相关专业毕业设计的高分参考项目整合Python、Django、OpenCV与dlib技术实现员工人脸识别考勤系统适合软件工程、人工智能等方向的学生用于毕设、课程设计、初期立项演示。压缩包内共659个文件主要包含501个Python源码、44个pyd编译模块、22个exe辅助程序、4个pth模型权重以及XML配置、CSV特征记录和详细说明文档整体约196MB覆盖人脸检测、特征提取、员工管理、考勤打卡到Django后台展示的完整链路。目前已有251人学习下载。内容上除可直接运行的考勤应用外还附带特征文件、日志数据和建模配置便于二次开发时调试识别流程、优化打卡记录或替换数据集配套文档则可辅助快速理解目录结构与部署方式帮助使用者从零搭建到功能验证节省大量时间是开展毕业设计或课程报告的有力参考。1. 人脸识别考勤系统为什么是 Django OpenCV dlib员工考勤是每家公司都绕不开的管理环节指纹机怕磨损IC卡怕代刷手机打卡怕定位造假而人脸识别考勤把摄像头变成一台全天候的签到终端员工只需在镜头前停留一秒即可完成身份确认。要落地这样的系统技术选型并不像看起来那么随意Django 负责 Web 页面、接口和数据库OpenCV 负责从摄像头或照片中读取像素并做预处理dlib 则承担人脸检测、关键点定位和 128 维特征编码。这套组合之所以常年出现在毕业设计和中小型内部项目中是因为它避开了“自己训练人脸模型”这个高门槛dlib 提供了预训练模型开箱即用而 Django 的后台管理又能把考勤数据可视化成本压到几乎为零。接下来的章节我会按“原理→搭建→业务→调参”的顺序把一个可运行的人脸识别考勤系统拆给你看。2. 核心选型与识别原理dlib 的检测、对齐与特征编码2.1 为什么是 dlib 而不是纯 OpenCVOpenCV 自带的人脸识别模块LBPH、EigenFace、FisherFace在固定人数、固定光线的环境下勉强可用但员工一多、现场光线一乱LBPH 的识别率就会明显下滑。dlib 的 pipeline 完全不同先用 HOG 特征检测人脸再用 68 个关键点对齐人脸最后通过 ResNet 模型把对齐后的脸编码成 128 维浮点向量。这个特征向量对姿态和光线有较强的鲁棒性识别时只需计算两个向量之间的欧氏距离距离小于阈值就判定为同一个人。这里有一个常被误解的点dlib 的模型是“通用人脸识别模型”不是针对某个公司员工训练的。它学习的是“所有人脸在语义空间的分布”所以新员工注册时不需要重新训练模型只需要用现成模型提取一次特征存入数据库。这意味着系统上线初期可以快速添加员工但也意味着模型的底限精度由预训练权重决定。如果员工中有双胞胎或高度相似的亲戚dlib 也可能会犯错这时候需要靠提高注册照片质量和调整阈值来控制。2.2 Django 在系统中的角色请求、识别与数据流Django 在这里不是做图像算法的而是做业务编排。一个典型的识别请求流程是前端摄像头抓拍一帧图像通过 HTTP POST 上传到后端Django 视图函数接收文件后先保存到内存或临时目录再调用 dlib 提取特征接着与数据库里所有员工的特征向量做距离比对最后把匹配结果插入考勤记录表。整个过程可以拆成三个接口注册接口、识别接口、查询接口。这里需要提醒一个性能问题dlib 的特征提取是 CPU 密集型操作在普通笔记本上单次约耗时 200 毫秒如果使用 Django 的同步接口多个并发请求会阻塞 worker 线程。对于毕业设计和内部小规模使用可以接受若要做生产级并发建议把识别任务放到 Celery 队列中或使用 Redis 做结果缓存。我在实际项目中通常会让“识别”接口只接收图片并立即返回识别结果后续的写库操作放在数据库事务里完成。2.3 最小依赖清单与版本坑以下是经过验证的基础依赖版本组合。dlib 在 Windows 上很难编译建议直接安装官方提供的 wheel 文件或使用 Anaconda 进行安装。OpenCV 选择 opencv-python 就足够了不要装 opencv-contrib-python除非你要用 SIFT 等 extra modules它体积更大且可能和 dlib 产生符号冲突。包名推荐版本作用Python3.8 ~ 3.11运行环境3.11 对 dlib 编译更友好Django3.2 或 4.2Web 框架、ORM、Admin 后台opencv-python4.5.5 或 4.8.x图像读取、缩放、颜色转换dlib19.23.x 或 19.24.x人脸检测、关键点、特征向量numpy1.21特征向量的数学运算安装完成后先执行一段检查代码确认库之间没有相互冲突import dlib, cv2 print(dlib, dlib.__version__) print(opencv, cv2.__version__)能打印出版本号说明依赖安装成功。如果import cv2报错检查自己是不是把包安装到了另一个 Python 环境如果import dlib报ModuleNotFoundError大概率是 Dlib 编译失败而不是路径问题。Linux 安装 dlib 前需要先安装 build-essential、cmake、libboost-all-dev再执行pip install dlib。另外dlib 还需要两个模型文件shape_predictor_68_face_landmarks.dat和dlib_face_recognition_resnet_model_v1.dat前者输出人脸上的 68 个关键点后者把约 150×150 的对齐人脸编码成 128 维向量下载后统一放在项目的models/目录。注意dlib 和 OpenCV 默认使用 BGR 颜色空间但 dlib 内部模型期望 RGB 输入。如果你直接用 OpenCV 读图后交给 dlib识别效果会异常。记得在传递前做一次cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。3. 搭建项目骨架并跑通人脸识别接口3.1 创建 Django 应用和目录结构先动手创建虚拟环境和项目。假设你已经安装好 Python 3.10以下命令在 Linux/macOS 上通用Windows 用户只需把source venv/bin/activate换成venv\Scripts\activate。python -m venv venv source venv/bin/activate pip install django opencv-python dlib numpy django-admin startproject attendance cd attendance python manage.py startapp face项目结构建议保持扁平方便答辩讲解attendance/ ├── attendance/ # Django 项目配置 ├── face/ # 人脸识别核心应用 │ ├── utils.py # 人脸编码工具 │ ├── views.py # 注册/识别接口 │ ├── models.py # 员工与考勤记录模型 │ └── urls.py # 路由 ├── models/ # dlib 模型文件 │ ├── shape_predictor_68_face_landmarks.dat │ └── dlib_face_recognition_resnet_model_v1.dat └── manage.py不要把 dlib 模型文件提交到 Git 仓库它们的体积加起来超过 120MB。可以在 README 里写下载地址或者用 Git LFS 管理。face/utils.py是核心工具模块后面的识别逻辑都会调用它。3.2 人脸特征编码工具模块在face/utils.py中编写一个人脸编码函数。这个函数会被注册和识别两个功能共用所以要把输入处理、异常处理都写干净。import dlib import cv2 import numpy as np detector dlib.get_frontal_face_detector() sp dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) facerec dlib.face_recognition_model_v1(models/dlib_face_recognition_resnet_model_v1.dat) def get_face_encoding(image_path): # dlib 要求 RGBOpenCV 默认 BGR bgr cv2.imread(image_path) if bgr is None: raise ValueError(f无法读取图片: {image_path}) rgb cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB) # 第二个参数 1 表示对图像上采样一次能提高小脸检测率但会慢一点 faces detector(rgb, 1) if len(faces) ! 1: raise ValueError(f检测到 {len(faces)} 张人脸需要单人正面照) shape sp(rgb, faces[0]) encoding np.array(facerec.compute_face_descriptor(rgb, shape)) return encoding代码逻辑说明detector返回人脸矩形列表upsample参数为 1 时会在检测前将图像放大一倍这样远处的小脸也能被找到代价是单帧耗时增加。sp用检测到的人脸矩形计算 68 个关键点compute_face_descriptor会根据关键点对人脸进行归一化和对齐最终输出一个 128 维向量。注册时要求图片中只有一张人脸否则无法确定你要给谁注册识别时则允许多张人脸可以逐一比对。3.3 注册与识别接口实现在face/views.py中实现注册接口。为了简化 CSRF 处理我们使用csrf_exempt但在真实项目中应该配合 Token 或 Session 验证。注册接口接收工号、姓名和一张照片提取特征后存入数据库。接口约定如下表接口方法请求参数返回 JSON/api/register/POSTname, employee_id, imagemessage, employee_id, is_new/api/recognize/POSTimagestatus, employee_id, distanceimport numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .models import Employee from .utils import get_face_encoding csrf_exempt def register_employee(request): if request.method ! POST: return JsonResponse({error: method not allowed}, status405) name request.POST.get(name) employee_id request.POST.get(employee_id) image_file request.FILES.get(image) if not name or not employee_id or not image_file: return JsonResponse({error: 缺少参数: name/employee_id/image}, status400) temp_path f/tmp/{employee_id}_{image_file.name} with open(temp_path, wb) as f: for chunk in image_file.chunks(): f.write(chunk) try: encoding get_face_encoding(temp_path) except ValueError as e: return JsonResponse({error: str(e)}, status400) # BinaryField 存储 bytes encoding_bytes encoding.astype(np.float32).tobytes() employee, created Employee.objects.update_or_create( employee_idemployee_id, defaults{name: name, face_encoding: encoding_bytes} ) return JsonResponse({message: ok, employee_id: employee_id, is_new: created})参数说明update_or_create允许重复注册时覆盖旧特征这样员工换发型或者佩戴眼镜后可以重新录入。face_encoding字段我们使用BinaryField存入的是 128 个 float32 的原始字节比写入 Base64 字符串节省约 25% 空间读取时用np.frombuffer还原。识别接口的核心逻辑是遍历所有员工特征用欧氏距离找最近邻。如果最近距离小于阈值FACE_THRESHOLD就认为匹配成功。FACE_THRESHOLD 0.5 def recognize_face(image_file): temp_path /tmp/latest_checkin.jpg with open(temp_path, wb) as f: for chunk in image_file.chunks(): f.write(chunk) encoding get_face_encoding(temp_path) employees Employee.objects.all() best_emp, best_dist None, float(inf) for emp in employees: emp_encoding np.frombuffer(emp.face_encoding, dtypenp.float32) dist np.linalg.norm(encoding - emp_encoding) if dist best_dist: best_dist dist best_emp emp if best_emp is not None and best_dist FACE_THRESHOLD: return best_emp, best_dist return None, best_dist这里要注意np.frombuffer返回的数组默认是只读的如果你后续需要修改可以.copy()。欧氏距离的取值通常在 0.4 到 0.8 之间阈值设得越小越严格设得过大容易把两个人认成同一个。我一般从 0.5 开始调用一批测试照片计算“类内距离”和“类间距离”从中取一个分界点。3.4 摄像头实时识别的两种集成方式考勤机需要摄像头但摄像头数据怎么进 Django 是很多新手卡住的地方。第一种方式前端用getUserMedia获得视频流在 canvas 上定格一帧转成 JPEG Blob 后通过 fetch 上传到 Django 接口。这种方式部署方便不需要在服务器上接摄像头适合浏览器端访问。第二种方式在服务器上使用 OpenCVcv2.VideoCapture(0)直接读取摄像头然后循环调用识别函数。这种方式适合实验室或机房里的固定考勤机。我通常建议毕业设计采用第一种因为答辩时可以用普通的笔记本摄像头演示而且代码更短。第二种方式的代码无非是import cv2 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue # 将 frame 保存为临时文件或直接编码成 numpy 数组 # 调用 get_face_encoding 并执行比对无论哪种方式都要考虑摄像头被占用时的异常释放。cap.release()一定要执行否则下次打开摄像机会失败。4. 考勤数据模型与业务逻辑从打卡到迟到早退判断4.1 员工与考勤记录的 Django 模型模型设计直接决定业务扩展空间。员工表除了基本字段还要存人脸特征向量考勤记录表则通过外键关联员工记录每次打卡的时间。from django.db import models class Employee(models.Model): employee_id models.CharField(max_length20, uniqueTrue, verbose_name工号) name models.CharField(max_length50, verbose_name姓名) face_encoding models.BinaryField(verbose_name人脸特征向量) created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return f{self.employee_id}-{self.name} class AttendanceRecord(models.Model): employee models.ForeignKey(Employee, on_deletemodels.CASCADE, verbose_name员工) check_in_time models.DateTimeField(verbose_name打卡时间) is_late models.BooleanField(defaultFalse, verbose_name是否迟到) note models.CharField(max_length100, blankTrue, verbose_name备注) class Meta: ordering [-check_in_time]face_encoding用BinaryField而不是TextField是因为人脸特征本质上是一个 512 字节的数组二进制存储避免字符编码转换的开销。on_deletemodels.CASCADE表示员工被删除后其考勤记录也一并删除但如果公司要求保留历史考勤应该改成PROTECT或SET_NULL。这里我用的是CASCADE是为了演示实际项目要按合规要求决定。字段类型对比字段类型用途注意点employee_idCharField工号加 uniqueTrue 防重复face_encodingBinaryField128 维特征不要直接可视化check_in_timeDateTimeField打卡时间需处理时区is_lateBooleanField迟到标记根据公司规则计算4.2 识别打卡主流程防止重复打卡与迟到判定现在把第 3 章的识别函数和考勤记录串起来。一个完整的打卡动作分四步提取特征、全局匹配、去重、写记录。去重逻辑很关键否则员工站在摄像头前 3 秒可能被记录 10 次打卡。from datetime import datetime from django.utils import timezone def check_in_employee(image_file): emp, dist recognize_face(image_file) if emp is None: return {status: unknown, distance: dist} today timezone.localdate() exist_record AttendanceRecord.objects.filter( employeeemp, check_in_time__datetoday, note上班 ).first() if exist_record: return {status: already, employee: emp} work_start timezone.make_aware(datetime.strptime(09:00, %H:%M).replace( yeartoday.year, monthtoday.month, daytoday.day )) now timezone.now() record AttendanceRecord.objects.create( employeeemp, check_in_timenow, is_latenow work_start, note上班 ) return {status: ok, employee: emp, record: record}这个函数有几个边界情况需要展开。timezone.make_aware依赖 Django 的USE_TZ True如果你在settings.py里关闭了 USE_TZ那么这里应该直接用datetime.now()。判定迟到用的是check_in_time__date它会在数据库层把时间转换为当天日期因此跨年也不会出错。防止重复打卡的逻辑限定在当天且只过滤note上班如果以后要增加下班打卡只需把note改为下班并检查当天是否已经有上班记录。4.3 Admin 后台与统计查询将模型注册到 Django Admin 后不需要写任何前端代码就能拥有增删改查页面。为了让考勤记录容易被筛选我在face/admin.py里做了如下配置from django.contrib import admin from .models import Employee, AttendanceRecord admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): list_display (employee_id, name, created_at) search_fields (employee_id, name) admin.register(AttendanceRecord) class AttendanceRecordAdmin(admin.ModelAdmin): list_display (employee, check_in_time, is_late) list_filter (is_late, check_in_time) date_hierarchy check_in_timedate_hierarchy会在后台列表页顶部生成一个按年、月、日逐级筛选的导航查看某一天的考勤记录非常方便。除了后台考勤统计常见需要“今日迟到人数”和“某人最近一周的打卡情况”。这两个查询用 Django ORM 写起来很直接from django.db.models import Count from django.utils import timezone late_count AttendanceRecord.objects.filter( is_lateTrue, check_in_time__datetimezone.localdate() ).count() weekly AttendanceRecord.objects.filter( employeeemp, check_in_time__gtetimezone.now() - timezone.timedelta(days7) ).values(check_in_time__date).annotate( totalCount(id) )第一个查询返回整型第二个查询返回按日期分组的字典列表。如果你需要把这些数据给前端做图表直接从视图里以 JsonResponse 返回即可。5. 数据集组织、阈值调参与上线前验证技巧5.1 员工数据集的目录规范与采集建议现实中所谓的“人脸识别考勤系统”一定会涉及数据集。不要把所有照片堆在一个目录里我建议按员工工号划分子目录并准备一份 CSV 元数据dataset/ ├── employees/ │ ├── A001_张三/ │ │ ├── frontal.jpg │ │ ├── left.jpg │ │ └── right.jpg │ └── A002_李四/ │ └── ... ├── negative/ │ └── random_people/ └── metadata.csvnegative目录放一些和员工无关的人脸照片用于测试系统会不会误识别。metadata.csv 至少包含employee_id, name, image_path三列。在注册阶段可以写一个脚本循环读取每张图片调用get_face_encoding并写入数据库。这样新增员工时就不再需要手动通过网页上传批量导入效率高很多。5.2 三个最容易影响识别效果的参数第一个是detector的upsample重复次数。它控制检测前图像放大的倍数主流取值是 0、1、2。0 最快但小脸漏检率高2 最慢但几乎不会漏掉远景人脸。考勤场景下摄像头离人一到两米我建议用 1。第二个是特征比对阈值。不要迷信网上说的默认 0.6。你要在自己的数据集上做一次“识别距离扫描”取每个员工的注册照和测试照计算同类人两两距离也计算不同员工之间的距离然后把阈值设在两类距离的分离带上。比如同类最大距离 0.4异类最小距离 0.6那阈值选 0.5 就非常安全。第三个是输入图像的尺寸。dlib 的 HOG 检测器内部使用 80x80 的滑动窗口如果人脸区域的像素小于 80x80再怎么 upsample 也可能无效。所以当你发现离得远就识别不了时优先把摄像头分辨率从 640x480 提高到 1280x720而不是盲目调阈值。5.3 用视频与 Django Test Client 做回归验证上线前除了用摄像头实测更可靠的办法是用一段录好的视频离线跑一遍识别流程。这可以帮你统计准确率和单帧耗时还能避免反复缩放窗口的干扰。下面这段脚本读取 test.mp4每隔 5 帧调用一次识别函数并记录结果python -c import cv2, time from face.utils import get_face_encoding from face.views import recognize_face cap cv2.VideoCapture(test.mp4) fps cap.get(cv2.CAP_PROP_FPS) frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(fps:, fps, frames:, frames) 更完整的测试可以直接用 Django 的 Test Client 模拟上传图片这样连接口都能一起验证from django.test import TestCase from django.core.files.uploadedfile import SimpleUploadedFile class FaceApiTest(TestCase): def test_register_and_recognize(self): with open(dataset/employees/A001_张三/frontal.jpg, rb) as f: img SimpleUploadedFile(frontal.jpg, f.read(), content_typeimage/jpeg) resp self.client.post(/api/register/, { employee_id: A001, name: 张三, image: img, }) self.assertEqual(resp.status_code, 200)这段用例在python manage.py test时执行确保你的接口在改动后依然返回正确状态。考勤系统最容易出问题的不是算法而是“识别成功但没写库”这类链路断裂所以务必用自动化测试覆盖注册、识别、去重三个步骤。阈值调好后把它写进配置文件而不是代码常量这样后期根据不同考勤点的光线环境做微调时不用改代码。本文还有配套的精品资源点击获取