基于OpenCV与Django的轻量级实时人脸疲劳检测系统 简介本资源是一套面向本科毕业设计的疲劳检测系统完整交付材料适用于计算机、人工智能或软件工程专业学生开展基于视觉感知的实践项目。系统采用PythonDjango框架构建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.85 分钟就能看到自己闭眼时系统跳红框。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(targetrun_detection_loop) detector_thread.daemon True # 设为守护线程避免阻塞主线程 detector_thread.start() return JsonResponse({status: started}) return JsonResponse({error: Method not allowed}, status405) 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()是纯计算函数只接收framenumpy 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眼睛开合度EAREye 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防抖设计避免单帧噪声误判眨眼。2PERCLOSPercentage 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哈欠检测基于嘴部纵横比 MARMouth 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], axis0) # 上唇中间3点 lower_lip np.mean(mouth_points[12:15], axis0) # 下唇中间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_userDjango 自带username,password,email用户登录认证—fatigue_photoid,user_id(FK),image_path,upload_time,status存每张上传照片的元信息user_id → auth_user.idfatigue_detection_resultid,photo_id(FK),ear_value,blink_count,yawn_flag,fatigue_level,detect_time核心表每次检测的详细数值结果photo_id → fatigue_photo.idfatigue_user_profileuser_id(FK),real_name,phone,department用户扩展信息论文里“用户管理”模块用user_id → auth_user.idfatigue_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_deletemodels.CASCADE) image_path models.CharField(max_length255) # 例media/uploads/20240510_142301.jpg upload_time models.DateTimeField(auto_now_addTrue) status models.CharField(max_length20, defaultpending) # pending/processing/completed/failed def __str__(self): return f{self.user.username} - {self.upload_time} class FatigueDetectionResult(models.Model): photo models.ForeignKey(FatiguePhoto, on_deletemodels.CASCADE) ear_value models.FloatField() # 例如 0.215 blink_count models.IntegerField(default0) yawn_flag models.BooleanField(defaultFalse) fatigue_level models.CharField(max_length10, choices[ (normal, 正常), (medium, 中度疲劳), (high, 高度疲劳) ]) detect_time models.DateTimeField(auto_now_addTrue) def __str__(self): return fResult 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_rootsettings.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 python3.8 conda activate fatigue_env # 安装核心依赖注意版本 pip install django3.2.20 pip install opencv-python4.5.5.64 pip install dlib19.24.1 # 必须指定版本新版 dlib 与旧版 OpenCV 有 ABI 冲突 pip install numpy1.21.6 pip install mysqlclient2.1.1 # 注意不是 pymysql是 mysqlclientDjango 官方推荐为什么强调 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 dlib19.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_modeSTRICT_TRANS_TABLES, }, } }执行前必须做三件事创建数据库和用户MySQL 8.0 密码策略严格-- 登录 MySQL root CREATE DATABASE fatigue_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER fatigue_userlocalhost IDENTIFIED WITH mysql_native_password BY fatigue_pass123; GRANT ALL PRIVILEGES ON fatigue_db.* TO fatigue_userlocalhost; FLUSH PRIVILEGES;解决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运行 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.log90% 是字符集或密码策略问题。3.3 启动服务别直接 runserver先检查摄像头权限# Windows 用户注意cmd 或 PowerShell 运行不要用 Git BashOpenCV 摄像头不兼容 python manage.py runserver 0.0.0.0:8000但此时访问http://127.0.0.1:8000可能白屏。因为Linux/macOS终端可能报VIDEOIO ERROR: V4L: cant 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 值浏览器打开http://127.0.0.1:8000/admin/用createsuperuser创建的账号登录左侧菜单点Users→Add user新建一个普通用户如testuser/testpass123访问http://127.0.0.1:8000/login/用新用户登录点导航栏图片识别→ 页面顶部有Start Detection按钮点击关键一步浏览器会弹出“允许访问摄像头”点“允许”页面中央出现实时视频流右上角开始滚动显示EAR: 0.28 | Blink: 0 | Yawn: No | Level: normal当你闭眼 1 秒EAR会瞬间降到0.15Level变成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中加 fallbackdef 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( userrequest.user, image_pathrelative_path, statuspending ) # 新增立即分析这张图并存入 detection_result frame cv2.imread(full_path) detector FatigueDetector() result_data detector.analyze_frame(frame) FatigueDetectionResult.objects.create( photophoto, ear_valueresult_data[ear], blink_countresult_data[blink_count], yawn_flagresult_data[yawn], fatigue_levelresult_data[fatigue_level] ) photo.status completed photo.save() return JsonResponse({status: success})4.3 现象MySQL 连接池耗尽页面报django.db.utils.OperationalError: (2003, Cant connect to MySQL server)原因Django 默认每个请求都新建 MySQL 连接而start_detection的后台线程又长期持有连接导致连接数爆满MySQL 默认 max_connections151。解决在settings.py中启用连接池DATABASES { default: { # ... 原有配置 ... OPTIONS: { init_command: SET sql_modeSTRICT_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_CUDA4.5 现象中文路径下cv2.imread()返回None导致analyze_frame崩溃原因OpenCV 的imread不支持 UTF-8 路径Windows 尤其明显。源码里image_path是media/uploads/张三_20240510.jpgcv2.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(fFailed 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.pyclass FatigueConfig(models.Model): 疲劳检测全局配置 ear_threshold models.FloatField( default0.22, help_textEAR 闭眼阈值越小越敏感 ) mar_threshold models.FloatField( default0.5, help_textMAR 哈欠阈值越大越不敏感 ) blink_window_seconds models.IntegerField( default60, help_text眨眼计数时间窗口秒 ) perclos_window_seconds models.IntegerField( default2, help_textPERCLOS 计算时间窗口秒 ) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 疲劳检测配置 verbose_name_plural 疲劳检测配置 def __str__(self): return fConfig {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_threshold0.22, mar_threshold0.5, blink_window_seconds60, perclos_window_seconds2 ) 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__userrequest.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, nameexport_results),在photos_analysis.html模板里加按钮a href{% url export_results %} classbtn btn-success i classfas fa-file-excel/i 导出 Excel 报告 /a为什么只导 1000 条防止用户导出百万行把服务器内存打爆。这是生产环境基本素养。5.3 换模型用 MediaPipe 替代 dlib提速 3 倍且免编译附完整替换清单dlib 虽稳但 CPU本文还有配套的精品资源点击获取