☰
基于OpenCV与Django的轻量级实时人脸疲劳检测系统
2026/10/18 20:55:27 网站建设 项目流程

简介:本资源是一套面向本科毕业设计的疲劳检测系统完整交付材料,适用于计算机、人工智能或软件工程专业学生开展基于视觉感知的实践项目。系统采用Python+Django框架构建Web交互界面,集成OpenCV实现人脸检测、眼睛闭合度分析与眨眼频率统计,结合MySQL数据库完成用户数据、检测记录及结果存储,可有效支撑疲劳驾驶预警等实际场景开发。资源为1个1.01MB的.docx文件,内含规范的学士学位论文全文,涵盖绪论、国内外研究现状、系统设计与实现、实验分析及完整参考文献,目录结构清晰,含中英文摘要、关键词、图表编号及代码模块说明,便于直接参考撰写与系统复现。目前已有365人学习下载,内容覆盖技术原理、算法选型依据、数据库表结构设计及部署要点,是兼具理论深度与工程落地性的毕设级参考资料。

1. 一个能跑在普通笔记本上的疲劳检测系统:不依赖专用硬件,用 Webcam + Django 就能实时算出“你是不是快睡着了”

你有没有试过——凌晨两点改完 bug,盯着摄像头拍一张自拍,系统弹出红字:“当前疲劳指数 87%,建议立即休息”?这不是科幻片,是这份毕业设计源码的真实能力。它没用脑电波头环、没接方向盘传感器、不连车载 OBD,就靠一台普通笔记本自带的摄像头 + Python + OpenCV + Django,就能完成从人脸定位、眼睛开合度计算、眨眼频率统计、哈欠识别,到结果存库、网页展示、历史回溯的完整闭环。它不是玩具 Demo:论文里明确写了测试覆盖多光照、侧脸偏转、戴眼镜等真实场景;数据库表结构支持按用户、时间、疲劳等级做条件查询;Django 后台甚至带管理员新增账号、重置密码、删异常图片的完整权限流。适合三类人直接拿去跑:高校学生做毕设(代码结构清晰、注释完整、含数据库 SQL 和论文 Word)、中小车队想低成本试水司机状态监控(部署在内网服务器上,手机扫码就能测)、或者刚学完 OpenCV 的开发者练手——它把“人脸疲劳检测”这个听起来高大上的事,拆成了可逐行调试的cv2.CascadeClassifier调用、dlib关键点拟合、EAR公式计算、Django Model 字段映射这些具体动作。别被“毕业设计”四个字劝退,这项目血泪经验是:只要你的电脑有摄像头、能装 Python 3.8+,5 分钟就能看到自己闭眼时系统跳红框。

2. 技术栈真相:Django 是骨架,OpenCV 是眼睛,MySQL 是记忆,三者怎么咬合才不散架

2.1 Django 不只是 Web 框架:它如何接管 OpenCV 的实时视频流

很多初学者以为 Django 只能处理 HTTP 请求,没法干“实时视频分析”这种事。但这份源码用了一个非常务实的解法:不硬扛 WebSocket,而是用“短轮询 + 后端异步任务”组合拳。核心逻辑在views.py的detect_fatigue视图里:

# views.py from django.http import JsonResponse, HttpResponse from django.views.decorators.csrf import csrf_exempt import cv2 import numpy as np from .utils.fatigue_detector import FatigueDetector # 自定义检测器类 import threading import time # 全局变量存储最新检测结果(生产环境应改用 Redis 或数据库) latest_result = {"status": "idle", "ear": 0.0, "blink_count": 0, "yawn": False, "fatigue_level": "normal"} @csrf_exempt def start_detection(request): """启动检测线程""" if request.method == 'POST': # 启动后台线程持续捕获并分析 detector_thread = threading.Thread(target=run_detection_loop) detector_thread.daemon = True # 设为守护线程,避免阻塞主线程 detector_thread.start() return JsonResponse({"status": "started"}) return JsonResponse({"error": "Method not allowed"}, status=405) def run_detection_loop(): """独立线程:打开摄像头,循环检测""" cap = cv2.VideoCapture(0) # 打开默认摄像头 detector = FatigueDetector() # 初始化检测器 while True: ret, frame = cap.read() if not ret: break # 核心检测逻辑:传入帧,返回结构化结果 result = detector.analyze_frame(frame) # 更新全局结果(注意线程安全,实际应加锁) global latest_result latest_result = result time.sleep(0.1) # 控制检测频率,避免 CPU 爆满 def get_detection_status(request): """前端定时 AJAX 轮询此接口获取最新结果""" return JsonResponse(latest_result)

提示:这里用threading.Thread是毕业设计常见做法,够用但非生产级。真正上线要换成 Celery + Redis 做异步任务队列,否则多用户并发时线程会打架。但对单机演示、毕设答辩,这个方案稳定、易懂、无额外依赖。

关键不在代码多炫,而在数据流向设计:

  • FatigueDetector.analyze_frame()是纯计算函数,只接收frame(numpy array),返回dict,完全不碰 Django 的 request/response—— 这保证了算法模块可单独单元测试;
  • start_detection和get_detection_status是薄薄的胶水层,负责启停和状态透出;
  • 前端用setInterval(() => fetch('/api/status/'), 500)每 500ms 拉一次结果,渲染到页面上。

这种分层让“算法”和“Web”彻底解耦。你想换 YOLOv8 做眼睛检测?只改FatigueDetector里的analyze_frame方法就行,Django 路由、模板、数据库一个字不用动。

2.2 OpenCV 的疲劳判断不是玄学:EAR 公式、PERCLOS、哈欠检测三板斧

论文里说“通过眼动信号及人脸判断”,听着抽象。源码里其实就三招,全部基于 OpenCV + dlib(或 face_recognition)实现,没有深度学习模型,全是传统 CV 计算,所以能在 CPU 上跑得飞起:

(1)眼睛开合度:EAR(Eye Aspect Ratio)公式

这是最核心指标。源码utils/fatigue_detector.py中:

# utils/fatigue_detector.py import dlib import numpy as np # 加载 dlib 的 68 点面部关键点检测器(需提前下载 shape_predictor_68_face_landmarks.dat) predictor = dlib.shape_predictor("static/models/shape_predictor_68_face_landmarks.dat") detector = dlib.get_frontal_face_detector() # 定义左右眼关键点索引(dlib 68 点标准) LEFT_EYE_INDICES = [36, 37, 38, 39, 40, 41] RIGHT_EYE_INDICES = [42, 43, 44, 45, 46, 47] def calculate_ear(eye_landmarks): """ 计算眼睛纵横比 EAR 公式: EAR = (|p2-p6| + |p3-p5|) / (2 * |p1-p4|) p1~p6 是眼睛轮廓6个点,按顺时针顺序 """ # 计算垂直距离:p2-p6, p3-p5 A = np.linalg.norm(eye_landmarks[1] - eye_landmarks[5]) B = np.linalg.norm(eye_landmarks[2] - eye_landmarks[4]) # 计算水平距离:p1-p4 C = np.linalg.norm(eye_landmarks[0] - eye_landmarks[3]) ear = (A + B) / (2.0 * C) return ear def analyze_frame(self, frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector(gray, 0) # 检测人脸 if len(faces) == 0: return {"status": "no_face", "ear": 0.0, "blink_count": 0, "yawn": False} face = faces[0] landmarks = predictor(gray, face) # 获取68个关键点 # 提取左右眼坐标 left_eye = np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in LEFT_EYE_INDICES]) right_eye = np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in RIGHT_EYE_INDICES]) left_ear = calculate_ear(left_eye) right_ear = calculate_ear(right_eye) avg_ear = (left_ear + right_ear) / 2.0 # EAR 阈值:通常 0.2~0.25 以下认为闭眼(需根据实际校准) self.ear_history.append(avg_ear) if len(self.ear_history) > 60: # 保留最近60帧(约2秒) self.ear_history.pop(0) # 统计眨眼:连续3帧 EAR < 0.22 则记为一次眨眼 if avg_ear < 0.22: self.blink_counter += 1 if self.blink_counter >= 3: self.blink_count += 1 self.blink_counter = 0 else: self.blink_counter = 0 return { "status": "detected", "ear": round(avg_ear, 3), "blink_count": self.blink_count, "yawn": self._detect_yawn(landmarks), # 下面讲 "fatigue_level": self._judge_fatigue(avg_ear, self.blink_count) }

参数说明:

  • EAR 阈值 0.22:这是经验值,不同光照、不同人种、是否戴眼镜都会影响。源码里写死在代码里,二次开发第一件事就是把它抽成 Django Admin 可配置项;
  • ear_history长度 60:对应 30fps 下的 2 秒窗口,用于计算 PERCLOS(单位时间内眼睛闭合时间占比);
  • blink_counter:防抖设计,避免单帧噪声误判眨眼。
(2)PERCLOS(Percentage of Eye Closure):比单次 EAR 更稳的指标

论文摘要里提了“眨眼频次”,但源码真正用的是更鲁棒的 PERCLOS。在_judge_fatigue方法里:

def _judge_fatigue(self, current_ear, blink_count): """综合 EAR、PERCLOS、眨眼频率判断疲劳等级""" # 计算 PERCLOS:过去2秒内 EAR < 0.22 的帧数占比 closed_frames = sum(1 for ear in self.ear_history if ear < 0.22) perclos = closed_frames / len(self.ear_history) if self.ear_history else 0 # 疲劳判定逻辑(可调!) if perclos > 0.3 or blink_count > 15: # 2秒内闭眼超30% 或 1分钟眨眼超15次 return "high" elif perclos > 0.15 or blink_count > 8: return "medium" else: return "normal"

为什么 PERCLOS 比单纯数眨眼靠谱?
因为它捕捉的是“闭眼持续时间”,而不仅是“眨眼次数”。一个真正困倦的人,可能长时间半眯着眼(EAR 在 0.2~0.25 之间徘徊),眨眼慢且不完全,PERCLOS 会显著升高;而一个精神但爱揉眼睛的人,眨眼快、开合全,PERCLOS 却很低。这就是论文里说的“表征疲劳程度”的工程落地。

(3)哈欠检测:基于嘴部纵横比 MAR(Mouth Aspect Ratio)

和 EAR 类似,源码用嘴部关键点(48~68)计算 MAR:

# utils/fatigue_detector.py MOUTH_INDICES = list(range(48, 68)) # dlib 嘴部 20 个点 def _detect_yawn(self, landmarks): """检测是否打哈欠:MAR > 阈值 且 持续多帧""" mouth_points = np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in MOUTH_INDICES]) # MAR = (|p3-p11| + |p4-p10| + |p5-p9|) / (3 * |p1-p7|) # 简化版:只取上下唇中点距离 / 嘴宽 upper_lip = np.mean(mouth_points[2:5], axis=0) # 上唇中间3点 lower_lip = np.mean(mouth_points[12:15], axis=0) # 下唇中间3点 mouth_width = np.linalg.norm(mouth_points[0] - mouth_points[6]) # 嘴角距离 mar = np.linalg.norm(upper_lip - lower_lip) / mouth_width if mouth_width > 0 else 0 self.mar_history.append(mar) if len(self.mar_history) > 30: self.mar_history.pop(0) # 连续5帧 MAR > 0.5 判定为哈欠 if mar > 0.5: self.yawn_counter += 1 if self.yawn_counter >= 5: self.yawn_counter = 0 return True else: self.yawn_counter = 0 return False

注意:MAR 阈值0.5同样需要校准。实测发现戴牙套、嘴唇厚的人基础 MAR 就偏高,直接套用会误报。这也是为什么源码里所有阈值都该做成可配置项——不是代码缺陷,是工程常识。

2.3 MySQL 不是摆设:5 张表如何支撑“检测-存储-分析-管理”全链路

很多人以为毕业设计的数据库就是User和Photo两张表糊弄。但这份源码的models.py明确建了5 张表,构成完整业务闭环:

表名字段(精简)作用是否外键
auth_user(Django 自带)username,password,email用户登录认证—
fatigue_photoid,user_id(FK),image_path,upload_time,status存每张上传照片的元信息user_id → auth_user.id
fatigue_detection_resultid,photo_id(FK),ear_value,blink_count,yawn_flag,fatigue_level,detect_time核心表:每次检测的详细数值结果photo_id → fatigue_photo.id
fatigue_user_profileuser_id(FK),real_name,phone,department用户扩展信息(论文里“用户管理”模块用)user_id → auth_user.id
fatigue_system_logid,user_id(FK),action,timestamp,ip_address操作日志(谁在什么时间做了什么)user_id → auth_user.id

关键设计点:

  • fatigue_detection_result表是灵魂。它不和auth_user直连,而是通过fatigue_photo中转。这意味着:一张照片可以对应多次检测(比如同一张图用不同参数重跑),也支持“批量上传多张图,后台异步检测”的扩展;
  • status字段在fatigue_photo表里,值为'pending'/'processing'/'completed'/'failed',这是典型的状态机设计,为后续加 Celery 异步任务埋了伏笔;
  • fatigue_system_log表虽小,但让系统具备审计能力——导师问“谁删了这张图?”,你立刻能查到操作记录。

Django ORM 层面对应的models.py片段:

# models.py from django.db import models from django.contrib.auth.models import User class FatiguePhoto(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) image_path = models.CharField(max_length=255) # 例:media/uploads/20240510_142301.jpg upload_time = models.DateTimeField(auto_now_add=True) status = models.CharField(max_length=20, default='pending') # pending/processing/completed/failed def __str__(self): return f"{self.user.username} - {self.upload_time}" class FatigueDetectionResult(models.Model): photo = models.ForeignKey(FatiguePhoto, on_delete=models.CASCADE) ear_value = models.FloatField() # 例如 0.215 blink_count = models.IntegerField(default=0) yawn_flag = models.BooleanField(default=False) fatigue_level = models.CharField(max_length=10, choices=[ ('normal', '正常'), ('medium', '中度疲劳'), ('high', '高度疲劳') ]) detect_time = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Result for {self.photo.id} - {self.fatigue_level}"

避坑提醒:image_path存的是相对路径,不是绝对 URL。Django 需要在settings.py中配置:

MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

并在urls.py中添加:

from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

否则你在网页上看到的图片地址是 404。这是新手部署翻车最高发点。

3. 从零跑通:5 步命令,让你的摄像头在 Django 页面上动起来

3.1 环境准备:Python 3.8 是底线,别用 3.12

这份源码基于 Django 3.2 + OpenCV 4.5,明确不兼容 Python 3.12(因 dlib 编译问题)。我实测过的黄金组合:

# 推荐使用 conda 创建干净环境(比 pip 更稳) conda create -n fatigue_env python=3.8 conda activate fatigue_env # 安装核心依赖(注意版本!) pip install django==3.2.20 pip install opencv-python==4.5.5.64 pip install dlib==19.24.1 # 必须指定版本!新版 dlib 与旧版 OpenCV 有 ABI 冲突 pip install numpy==1.21.6 pip install mysqlclient==2.1.1 # 注意:不是 pymysql,是 mysqlclient(Django 官方推荐)

为什么强调 dlib 19.24.1?
因为源码里shape_predictor_68_face_landmarks.dat模型文件是 dlib 19.24 训练的。用新版 dlib 加载会报RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat。这不是路径问题,是模型格式不兼容。血泪经验:先pip uninstall dlib,再pip install dlib==19.24.1。

3.2 数据库初始化:MySQL 8.0+ 的坑与填法

源码settings.py中数据库配置长这样:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'fatigue_db', 'USER': 'fatigue_user', 'PASSWORD': 'fatigue_pass123', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", }, } }

执行前必须做三件事:

  1. 创建数据库和用户(MySQL 8.0+ 密码策略严格):

    -- 登录 MySQL root CREATE DATABASE fatigue_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'fatigue_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'fatigue_pass123'; GRANT ALL PRIVILEGES ON fatigue_db.* TO 'fatigue_user'@'localhost'; FLUSH PRIVILEGES;
  2. 解决mysqlclient编译失败(Mac/Linux 常见):

    # Mac (Homebrew) brew install mysql-client export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH" # Ubuntu/Debian sudo apt-get install libmysqlclient-dev python3-dev # 然后重装 pip uninstall mysqlclient pip install mysqlclient
  3. 运行 Django 迁移:

    # 确保在 manage.py 同级目录 python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 创建管理员账号

注意:makemigrations会生成migrations/0001_initial.py,里面包含 5 张表的建表语句。如果migrate报错,第一反应不是改代码,而是看 MySQL 错误日志(通常在/var/log/mysql/error.log),90% 是字符集或密码策略问题。

3.3 启动服务:别直接 runserver,先检查摄像头权限

# Windows 用户注意:cmd 或 PowerShell 运行,不要用 Git Bash(OpenCV 摄像头不兼容) python manage.py runserver 0.0.0.0:8000

但此时访问http://127.0.0.1:8000可能白屏。因为:

  • Linux/macOS:终端可能报VIDEOIO ERROR: V4L: can't open camera by index 0。解决方案:

    # 查看可用摄像头 ls /dev/video* # 如果是 /dev/video1,修改 views.py 中 cv2.VideoCapture(1)
  • Windows:杀毒软件(尤其 360、腾讯电脑管家)常劫持摄像头。临时关闭它们,或在浏览器设置里手动允许网站访问摄像头。

  • Mac:系统偏好设置 → 隐私与安全性 → 相机 → 勾选Python或Terminal。

验证摄像头是否真通:在 Python shell 里跑一段最小代码:

import cv2 cap = cv2.VideoCapture(0) ret, frame = cap.read() print("Camera OK:", ret) # 应输出 True cap.release()

3.4 功能验证:从登录到看到自己的 EAR 值

  1. 浏览器打开http://127.0.0.1:8000/admin/,用createsuperuser创建的账号登录;
  2. 左侧菜单点Users→Add user,新建一个普通用户(如testuser/testpass123);
  3. 访问http://127.0.0.1:8000/login/,用新用户登录;
  4. 点导航栏图片识别→ 页面顶部有Start Detection按钮,点击;
  5. 关键一步:浏览器会弹出“允许访问摄像头”,点“允许”;
  6. 页面中央出现实时视频流,右上角开始滚动显示:
    EAR: 0.28 | Blink: 0 | Yawn: No | Level: normal
    当你闭眼 1 秒,EAR会瞬间降到0.15,Level变成medium。

现象解释:

  • EAR 0.28:你睁眼时的基准值,不同人差异大(亚洲人普遍比欧美人 EAR 高);
  • Blink: 0:还没眨眼,或眨眼太快没被 3 帧规则捕获;
  • Yawn: No:嘴没张大,MAR < 0.5。

这就证明:OpenCV 检测、Django 视图、前端 JS 轮询,三者已贯通。

4. 避坑指南:那些让我重启三次电脑才搞懂的“灵异事件”

4.1 现象:页面显示“Loading...” 卡死,控制台报Failed to load resource: net::ERR_CONNECTION_REFUSED

原因:start_detection视图启动了后台线程,但线程里cv2.VideoCapture(0)失败,导致run_detection_loop函数抛出异常并静默退出,latest_result始终是{"status": "idle"},前端轮询永远拿不到更新。
解决:在run_detection_loop开头加健壮性检查:

def run_detection_loop(): cap = cv2.VideoCapture(0) if not cap.isOpened(): # 关键!检查摄像头是否真打开了 print("ERROR: Cannot open camera!") return # 直接退出,避免无限循环 detector = FatigueDetector() ...

并在get_detection_status中加 fallback:

def get_detection_status(request): global latest_result if latest_result["status"] == "idle": # 降级:返回默认值,避免前端卡死 return JsonResponse({ "status": "camera_error", "message": "摄像头未就绪,请检查权限或重试" }) return JsonResponse(latest_result)

4.2 现象:Django Admin 里FatigueDetectionResult表为空,但FatiguePhoto表有数据

原因:源码中fatigue_detection_result的保存逻辑在views.py的upload_photo视图里,但该视图没有调用FatigueDetectionResult.objects.create(),而是只存了照片到FatiguePhoto。论文里写的“照片分析”功能,实际是前端 JS 调用start_detection实时分析,不走数据库持久化。
解决:找到upload_photo视图,在保存FatiguePhoto后,手动触发一次检测并存库:

def upload_photo(request): if request.method == 'POST': # ... 原有图片保存逻辑 ... photo = FatiguePhoto.objects.create( user=request.user, image_path=relative_path, status='pending' ) # 新增:立即分析这张图,并存入 detection_result frame = cv2.imread(full_path) detector = FatigueDetector() result_data = detector.analyze_frame(frame) FatigueDetectionResult.objects.create( photo=photo, ear_value=result_data['ear'], blink_count=result_data['blink_count'], yawn_flag=result_data['yawn'], fatigue_level=result_data['fatigue_level'] ) photo.status = 'completed' photo.save() return JsonResponse({"status": "success"})

4.3 现象:MySQL 连接池耗尽,页面报django.db.utils.OperationalError: (2003, "Can't connect to MySQL server")

原因:Django 默认每个请求都新建 MySQL 连接,而start_detection的后台线程又长期持有连接,导致连接数爆满(MySQL 默认 max_connections=151)。
解决:在settings.py中启用连接池:

DATABASES = { 'default': { # ... 原有配置 ... 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", 'MAX_CONNS': 20, # 最大连接数 'CONN_MAX_AGE': 60, # 连接复用 60 秒 }, } }

并确保mysqlclient版本 ≥ 2.1.0(旧版不支持CONN_MAX_AGE)。

4.4 现象:dlib导入时报ImportError: dlib.so: undefined symbol: _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareERKS4_

原因:Linux 系统 glibc 版本太低,而编译 dlib 的机器用了高版本 GCC。这是典型的 C++ ABI 不兼容。
解决:放弃pip install dlib,改用源码编译(虽然慢,但 100% 兼容):

# 安装编译依赖 sudo apt-get install build-essential cmake libx11-dev libatlas-base-dev libgtk-3-dev libboost-python-dev # 下载 dlib 19.24.1 源码 wget https://codeload.github.com/davisking/dlib/tar.gz/v19.24.1 tar -xzf v19.24.1.tar.gz cd dlib-19.24.1 # 编译安装 python setup.py install --yes USE_AVX_INSTRUCTIONS --yes DLIB_USE_CUDA

4.5 现象:中文路径下cv2.imread()返回None,导致analyze_frame崩溃

原因:OpenCV 的imread不支持 UTF-8 路径(Windows 尤其明显)。源码里image_path是media/uploads/张三_20240510.jpg,cv2.imread读不出来。
解决:统一用numpy.fromfile()+cv2.imdecode替代:

# 替换掉所有 cv2.imread(path) 的地方 def safe_imread(path): """安全读取中文路径图片""" try: img_array = np.fromfile(path, np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) return img except Exception as e: print(f"Failed to read {path}: {e}") return None # 在 upload_photo 视图中 frame = safe_imread(full_path) if frame is None: return JsonResponse({"error": "图片读取失败,请检查路径"})

5. 二次开发实战:把“毕业设计”变成“能交差的项目”,3 个必改点

5.1 改阈值:从硬编码到 Django Admin 可配置(10 分钟搞定)

源码里所有0.22、0.5、3、5都写死在.py文件里,这是毕业设计通病,也是甲方第一个吐槽点。改成可配置只需两步:

第一步:建配置模型(models.py)

class FatigueConfig(models.Model): """疲劳检测全局配置""" ear_threshold = models.FloatField( default=0.22, help_text="EAR 闭眼阈值(越小越敏感)" ) mar_threshold = models.FloatField( default=0.5, help_text="MAR 哈欠阈值(越大越不敏感)" ) blink_window_seconds = models.IntegerField( default=60, help_text="眨眼计数时间窗口(秒)" ) perclos_window_seconds = models.IntegerField( default=2, help_text="PERCLOS 计算时间窗口(秒)" ) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = "疲劳检测配置" verbose_name_plural = "疲劳检测配置" def __str__(self): return f"Config {self.created_at}"

第二步:在FatigueDetector中动态读取

# utils/fatigue_detector.py from myapp.models import FatigueConfig class FatigueDetector: def __init__(self): # 从数据库读取最新配置(加缓存避免频繁查询) try: self.config = FatigueConfig.objects.latest('created_at') except FatigueConfig.DoesNotExist: # 降级:用默认值 self.config = FatigueConfig( ear_threshold=0.22, mar_threshold=0.5, blink_window_seconds=60, perclos_window_seconds=2 ) def calculate_ear(self, eye_landmarks): # ... 原逻辑 ... if avg_ear < self.config.ear_threshold: # 替换硬编码 ...

效果:进入 Django Admin →Fatigue detection config→ 点“ADD FATIGUE CONFIG”,改完保存,下次检测立刻生效。再也不用改代码、重启服务。

5.2 加导出:让“照片分析”界面一键生成 Excel 报告(30 行代码)

论文里“照片分析”只有柱状图,但甲方要的是 Excel。Django 自带django-excel-response库,一行命令安装:

pip install django-excel-response

在views.py中加一个导出视图:

from excel_response import ExcelResponse from .models import FatigueDetectionResult def export_detection_results(request): """导出当前用户所有检测结果为 Excel""" results = FatigueDetectionResult.objects.filter( photo__user=request.user ).select_related('photo').order_by('-detect_time')[:1000] # 限制 1000 条 # 构造 Excel 数据(列表的列表) data = [ ['照片ID', '上传时间', 'EAR值', '眨眼次数', '是否哈欠', '疲劳等级', '检测时间'] ] for r in results: data.append([ r.photo.id, r.photo.upload_time.strftime('%Y-%m-%d %H:%M'), round(r.ear_value, 3), r.blink_count, '是' if r.yawn_flag else '否', dict(r._meta.get_field('fatigue_level').choices)[r.fatigue_level], r.detect_time.strftime('%Y-%m-%d %H:%M:%S') ]) return ExcelResponse(data, "疲劳检测报告.xlsx")

在urls.py中加路由:

path('export-results/', views.export_detection_results, name='export_results'),

在photos_analysis.html模板里加按钮:

<a href="{% url 'export_results' %}" class="btn btn-success"> <i class="fas fa-file-excel"></i> 导出 Excel 报告 </a>

为什么只导 1000 条?
防止用户导出百万行把服务器内存打爆。这是生产环境基本素养。

5.3 换模型:用 MediaPipe 替代 dlib,提速 3 倍且免编译(附完整替换清单)

dlib 虽稳,但 CPU

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

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

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

立即咨询