
简介面向需要完成图像识别类课题的计算机专业学生这套资料以基于PythonDjangoOpenCV的疲劳检测系统设计与实现为主题围绕疲劳驾驶预警、眼动信号分析等实际问题展开。资源内含完整学士学位论文docx文档系统论述了眼动信号与人脸判断相结合的检测方法借助OpenCV图像处理库检测眼睛闭合程度通过面部表情呈现与眨眼频次表征疲劳状态并涉及Python编程语言与MySQL数据库在图像识别、图片分析及照片管理功能模块中的应用可用于论文撰写参考、系统设计思路梳理及答辩准备。压缩包共1个文件为docx格式整体大小约1.01MB。目前已有365人学习适合需要获取完整论文模板、理解疲劳检测实现方案与关键技术点的毕业设计人群。1. 疲劳检测系统难点不在深度学习而在整条链路能否转起来你看到的“基于 python Django opencv 的疲劳检测系统源码数据库论文.docx”本质上是一套典型的毕业设计 / 课程设计项目组合用摄像头采集画面OpenCV 负责图像处理dlib 提取人脸关键点通过眼睛开合度、打哈欠频率判断疲劳状态再用 Django 把检测记录写进数据库并在网页上展示。这类项目最容易让新手误判的一点是真正难的不是检测算法本身而是怎么把 OpenCV 的实时视频流和 Django 的请求 / 响应模型拧在一起。整套系统的技术栈正好覆盖图像处理、Web 开发、数据库设计三个方向适合做毕设、课设也适合给小规模考勤或值班监控场景做一个能出结果的原型。这篇笔记会把从环境搭建到落库展示的完整链路走一遍并标注参数和踩坑点。2. 选型与原理为什么是 OpenCV 做图像、Django 做业务2.1 这个组合为什么比 C 写图像、PHP 写网页更合适疲劳检测系统要解决的三个核心问题分别是“拿到画面”“判定疲劳”“保存和展示记录”。如果全用 C 写 OpenCV性能确实好但 Web 部分、数据库部分、后台管理的开发成本会成倍增加如果只用 Python 的 Flask轻量是轻量可用户管理、ORM、后台管理页面都要自己搭做出来的项目在“数据库论文”这个维度上会显得单薄。Django 自带 Admin 后台、ORM、Auth 用户体系天然适合“带数据库的管理型小系统”这个定位。所以这个标题里 Python 负责把三块粘起来OpenCV 做图像采集与处理dlib 做关键点定位OpenCV 的 DNN 也能做人脸检测但 68 点关键点目前 dlib 最顺手Django 做业务模型、数据落库和 Web 展示。常见做法是 OpenCV 只负责“眼睛看到的东西”Django 只负责“记录下来的东西”中间用线程和队列连接避免互相阻塞。这套分工清晰写论文时也容易把每一块的职责讲明白。2.2 EAR、PERCLOS 与打哈欠检测疲劳判定到底在算什么疲劳判定的主流方案不是直接上深度学习分类而是用几何特征。眼睛纵横比 EAREye Aspect Ratio是最常用的指标它基于眼睛周围的 6 个关键点坐标计算EAR (||p2-p6|| ||p3-p5||) / (2*||p1-p4||)。人正常睁眼时 EAR 大约在 0.3 左右闭眼时趋近于 0.1所以设定一个阈值常用 0.25就能区分睁眼和闭眼。这个公式的好处是可解释性强、计算量小写论文时可以直接给出公式推导。PERCLOS 则是在一段时间窗口内统计闭眼帧的占比比如 60 秒内闭眼帧比例超过 0.4就判定为疲劳。打哈欠检测用嘴巴纵横比 MAR取嘴部 6 个关键点计算方式和 EAR 类似阈值一般设在 0.6 左右。相比把整张脸丢进卷积神经网络做分类这种几何特征方案在普通 CPU 上也能跑得动且阈值可调方便针对不同摄像头安装高度和光照做调整。2.3 系统模块划分与数据流一张表看清每个部件的职责把整个系统拆开看其实是四个模块在轮流干活。摄像头模块负责取流图像处理模块负责人脸检测和关键点定位判定模块负责算出 EAR/MAR 并维护状态Web 模块负责记录查询和展示。数据流是单向的摄像头采集一帧 → OpenCV 预处理 → 人脸检测 → 关键点提取 → 计算 EAR/MAR → 疲劳状态机更新 → 满足条件则写库 → Django 页面展示历史记录。模块职责核心技术点采集模块读取摄像头画面控制分辨率与帧率OpenCV VideoCapture图像处理模块人脸检测、关键点定位、画框dlib HOG / CNN 检测器68 点模型判定模块EAR/MAR 计算、疲劳状态机、报警触发几何特征 阈值判断Web 模块用户管理、检测记录存储、报表展示Django ORM、Admin、StreamingHttpResponse这里要提醒一点不要把“模型训练”和“模型推理”混为一谈。这套系统用的是 dlib 预训练好的 68 点关键点模型不需要自己训练你只需要调用它。很多新手拿到项目源码后以为要跑训练脚本实际上整套系统里并没有训练环节所有“智能”都来自预训练模型和阈值判断。3. 跑通最小系统从空环境到摄像头里画出 68 个关键点3.1 环境准备Python 安装、opencv 安装与 dlib 的前置依赖先解决环境问题。Python 建议装 3.8 到 3.10 之间的版本太新的版本在某些 Windows 环境下装 dlib 会遇到 wheel 不匹配的问题。装好 Python 后创建一个独立的虚拟环境避免和系统 Python 环境互相污染。python -m venv fatigue_env # Windows 激活虚拟环境 fatigue_env\Scripts\activate # Linux / macOS 激活虚拟环境 source fatigue_env/bin/activate # 安装核心依赖 pip install opencv-python opencv-contrib-python dlib Django这里有几个关键点。opencv-python 是基础包opencv-contrib-python 额外包含了一些扩展模块疲劳检测里如果用到 SIFT 这类特征算子就需要它建议一起装上。dlib 在 Windows 上经常编译失败报错信息通常和 cmake、Visual Studio Build Tools 有关所以要先装 cmake。pip install cmake # Windows 还需要安装 Visual Studio Build Tools勾选“使用 C 的桌面开发”安装完成后务必验证一下导入是否正常python -c import cv2; print(cv2.__version__) python -c import dlib; print(dlib.__version__) python -c import django; print(django.get_version())如果 import cv2 报ModuleNotFoundError: No module named cv2多半是 pip 装到了全局环境而当前解释器是虚拟环境检查一下终端里 python 指向的路径即可。这一环节最容易翻车但也是这套系统里最不该卡住的地方——所有依赖都是预编译包不需要自己编译 OpenCV。3.2 摄像头取流第一段能跑的代码拿到摄像头画面的最小代码并不复杂核心是 VideoCapture、read 循环和释放资源。这里最容易犯的错是忘记 release导致下一次运行时摄像头被占用画面黑屏。import cv2 cap cv2.VideoCapture(0) # 0 表示默认摄像头 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: print(读取摄像头画面失败) break cv2.imshow(camera, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里有两个参数值得说明。分辨率设成 640x480 是为了给后续的人脸检测留出性能余量分辨率越高dlib 检测耗时越长read 返回的 ret 是布尔值表示这一帧是否读取成功在某些摄像头热插拔或驱动异常时 ret 会变成 False这时候直接 break 比继续处理空帧更安全。waitKey(1) 的参数单位是毫秒表示等待键盘输入的超时时间同时也给 OpenCV 窗口刷新留出时间这个值不要设成 0否则窗口会卡死。3.3 人脸检测与 68 点关键点从框到点的坐标提取画面有了下一步就是找到脸并定位脸上的关键点。dlib 提供两个预训练模型shape_predictor_68_face_landmarks.dat是关键点模型约 100MB可以从 dlib 官网模型库下载人脸检测器则直接用dlib.get_frontal_face_detector()不需要额外文件。import dlib import cv2 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: break # dlib 检测器接收的是 RGB 图像OpenCV 默认是 BGR rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(rgb_frame, 0) for face in faces: landmarks predictor(rgb_frame, face) for i in range(68): x landmarks.part(i).x y landmarks.part(i).y cv2.circle(frame, (x, y), 2, (0, 255, 0), -1) cv2.imshow(landmarks, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里有两个必须讲透的细节。其一OpenCV 读进来的是 BGR 色彩空间而 dlib 的检测器内部用的是 RGB所以要先cv2.cvtColor转换否则在暖色光源下检测率会明显下降。其二detector(rgb_frame, 0)的第二个参数是 upsample_num_times表示对图像做几次上采样后再检测设为 1 可以检测更远更小的脸但耗时翻倍实时场景下用 0 更合理。如果你在暗光环境下检测率很低优先考虑加一个简单的亮度直方图均衡化cv2.equalizeHist而不是盲目调大 upsample 次数。4. 疲劳判定与 Django 落库EAR 阈值、打哈欠和检测记录的保存4.1 用 EAR/MAR 做疲劳判定完整函数与阈值参数表拿到 68 个关键点之后疲劳判定就是纯粹的坐标计算。dlib 的关键点索引是固定的左眼是 42 到 47右眼是 36 到 41嘴巴外圈是 48 到 59。写一个 EAR 计算函数把眼睛关键点坐标传进去即可。from scipy.spatial import distance def eye_aspect_ratio(eye_points): # eye_points 是包含 6 个关键点坐标的列表 p2_p6 distance.euclidean(eye_points[1], eye_points[5]) p3_p5 distance.euclidean(eye_points[2], eye_points[4]) p1_p4 distance.euclidean(eye_points[0], eye_points[3]) ear (p2_p6 p3_p5) / (2.0 * p1_p4) return ear def mouth_aspect_ratio(mouth_points): # 嘴部取 6 个点计算方式和 EAR 类似 p2_p8 distance.euclidean(mouth_points[2], mouth_points[8]) p3_p7 distance.euclidean(mouth_points[3], mouth_points[7]) p1_p5 distance.euclidean(mouth_points[0], mouth_points[4]) mar (p2_p8 p3_p7) / (2.0 * p1_p5) return mar实际业务里不建议对每一帧都做判定常见做法是维护一个帧计数器当 EAR 连续低于阈值超过 N 帧才判定为一次闭眼疲劳事件打哈欠同理MAR 连续高于阈值超过 M 帧才计数一次。这样做是为了过滤瞬时低头、眨眼、说话等干扰。参数标定是这套系统的灵魂我常用的初始值如下表参数推荐值说明EAR 闭眼阈值0.25睁眼约 0.3闭眼约 0.1MAR 哈欠阈值0.6说话时 MAR 也会波动需要结合连续帧过滤闭眼连续帧数3030fps 下约等于 1 秒闭眼哈欠连续帧数15约 0.5 秒持续张嘴检测间隔每 3 帧检测一次跳帧可以显著降低 CPU 占用这里有一个写论文时非常加分的细节阈值不要拍脑袋定而是录制一段自己正常状态和故意打哈欠状态的视频离线跑一遍 EAR/MAR 曲线取波峰波谷的中值作为初始阈值。这样你论文里写“阈值为 0.25”的时候能附上一张曲线截图作为依据比空口说“经验值”有说服力得多。4.2 Django 数据模型检测记录表怎么设计疲劳检测产生了事件数据接下来要解决的是“存哪里、怎么存”。这一步需要用 Django 创建一个 app 来管理业务逻辑命令是python manage.py startapp fatigue。数据表设计是整个系统的核心字段要覆盖“谁、什么时间、什么状态、证据在哪”。# fatigue/models.py from django.db import models from django.contrib.auth.models import User class FatigueRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) status models.CharField(max_length20, choices[ (normal, 正常), (eye_closed, 闭眼), (yawn, 哈欠), (fatigue, 疲劳) ], verbose_name检测状态) ear_value models.FloatField(default0.0, verbose_nameEAR均值) mar_value models.FloatField(default0.0, verbose_nameMAR均值) image_path models.CharField(max_length255, blankTrue, verbose_name报警截图路径) created_at models.DateTimeField(auto_now_addTrue, verbose_name检测时间) class Meta: ordering [-created_at]代码里有两个设计值得说明。外键user关联 Django 自带的 User 表而不是直接存一个用户名字符串这样可以借助 Django 的权限体系也能按用户维度做报表统计。image_path字段存的是截图文件的相对路径不是二进制内容图片文件放在 MEDIA_ROOT 下数据库只存路径避免数据库迅速膨胀。status字段用 choices 限定枚举值后续前端展示和论文里的状态统计都会方便很多。模型写好后执行迁移python manage.py makemigrations fatigue python manage.py migrate python manage.py createsuperuser4.3 视频流接入 Web线程模型与 MJPEG 输出接下来是这套系统里最令人头疼的部分Django 的请求-响应模型是“来一个请求返回一个响应”而摄像头是持续不断的视频流。如果在一个视图函数里写while True循环读帧Django 开发服务器会被这个请求堵死。常见做法是单独启动一个后台线程负责从摄像头读帧并更新一个全局的最新帧变量视图函数只负责把当前最新帧以 MJPEG 流的形式推给浏览器。# fatigue/views.py import cv2 import threading from django.http import StreamingHttpResponse # 全局视频流对象由后台线程持续更新 class VideoCamera: def __init__(self): self.cap cv2.VideoCapture(0) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) self.frame None self.lock threading.Lock() self.running True self.thread threading.Thread(targetself._update, args()) self.thread.daemon True self.thread.start() def _update(self): while self.running: ret, frame self.cap.read() if ret: with self.lock: self.frame frame def get_frame(self): with self.lock: return self.frame.copy() if self.frame is not None else None然后写一个生成器函数把帧编码成 JPEG 并包装成 multipart 响应def gen(camera): while True: frame camera.get_frame() if frame is None: continue ret, jpeg cv2.imencode(.jpg, frame) if not ret: continue yield b--frame\r\n \ bContent-Type: image/jpeg\r\n\r\n jpeg.tobytes() b\r\n def video_stream(request): camera VideoCamera() return StreamingHttpResponse(gen(camera), content_typemultipart/x-mixed-replace; boundaryframe)这里必须说明一个潜在问题VideoCamera()在视图里被构造每个访问页面的客户端都会创建一个新的摄像头实例如果两个人同时打开页面会出现摄像头被抢占的问题。小型演示场景无所谓但如果你要把它做成一个正经系统应该把 VideoCamera 设计成模块级单例所有请求共享同一个摄像头实例。4.4 查询、删除与页面刷新Django 上的收尾工作页面展示部分的核心是从数据库里把最近的检测记录查出来渲染到模板里。Django 的 ORM 查询和删除对象都非常直接# 查询最近的 20 条疲劳记录 records FatigueRecord.objects.filter( userrequest.user ).exclude(statusnormal)[:20] # 删除某条误报记录 FatigueRecord.objects.filter(idrecord_id, userrequest.user).delete()模板页面里实时视频流用img src/video_stream/就能显示因为 MJPEG 流本质上就是不断刷新图片。报警状态则用 JavaScript 定时器轮询接口setInterval(() { fetch(/api/latest_status/) .then(res res.json()) .then(data { if (data.status fatigue) { document.getElementById(alert).style.display block; } }); }, 3000);这一步的轮询间隔设为 3 秒比较合适。太短会对 Django 开发服务器造成压力太长则报警不及时。检测线程和 Web 线程通过数据库或内存队列交换数据不要直接在检测线程里操作 Django ORM 的数据库连接Django 的 ORM 不是线程安全的跨线程使用同一个连接容易出现Database connection is used by another thread的报错。建议检测线程只负责写一条 JSON 到内存队列由 Django 侧单独的处理函数负责落库。5. 高频避坑从模块安装失败到摄像头黑屏的 5 个真实现场5.1 环境与安装类三个最常见的装机翻车现场坑一ModuleNotFoundError: No module named cv2。这个报错十有八九是装错环境了pip 装到了全局 Python而 PyCharm 或 VSCode 里选的是虚拟环境解释器。解决方式是先检查pip list里有没有 opencv-python再在终端里执行python -c import sys; print(sys.executable)确认解释器路径最后在 IDE 里把解释器切到虚拟环境。坑二dlib 安装编译失败。Windows 上 dlib 需要 cmake 和 Visual Studio Build Tools缺一不可。现象是 pip install dlib 时刷出一长串 C 编译日志最后报error: command cl.exe failed。解决方法是先pip install cmake然后安装 Visual Studio Build Tools 并勾选“使用 C 的桌面开发”装完重启终端再装 dlib。如果你实在不想折腾编译可以考虑用face_recognition库封装好的 dlib它提供了预编译版本但人脸关键点索引和 dlib 略有差异换成它需要改特征点映射逻辑。坑三cv2.error: OpenCV(4.4.0)这类运行时报错通常出现在 import cv2 或调用 VideoCapture 时错误信息里带一长串路径和 DLL 名称。多数原因是系统缺少 Visual C Redistributable 运行库或者 opencv-python 被多个版本混装导致 DLL 冲突。解决方法是先卸载干净再装指定版本pip uninstall opencv-python opencv-contrib-python然后pip install opencv-python4.8.0.74这类稳定版本。5.2 运行与集成类摄像头占用和静态文件 404 的处理坑四摄像头打开后黑屏或者第二次运行时报[ WARN:0] videoio(MSMF): cant access camera。最普遍的原因是上一个 Python 进程没有释放摄像头代码里cap.release()没有执行或者进程被强杀导致摄像头被系统锁定。解决分两步先打开任务管理器把所有残留的 python.exe 进程结束掉然后在代码里改用cv2.VideoCapture(0, cv2.CAP_DSHOW)DSHOW 模式在 Windows 下对摄像头独占的处理比默认模式更宽容适合调试阶段反复启停。如果是笔记本摄像头还要检查是否有其他软件微信、腾讯会议正在占用。坑五Django 页面能打开但报警截图和静态文件全部 404。原因几乎都是 settings.py 里的 MEDIA_ROOT 和 STATICFILES_DIRS 没有配置完整。我一般会在 settings.py 末尾加上import os MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media) STATIC_URL /static/ STATICFILES_DIRS [os.path.join(BASE_DIR, static)]然后在项目的 urls.py 里补充urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这样开发环境下媒体文件才能被访问。生产环境用 Nginx 托管静态文件是另一套配置你可以先不管但要知道“开发环境能显示图片”不等于“部署后也能显示”。6. 进阶调优帧率、报表与权限让系统离开本地跑起来6.1 帧率优化跳帧、降分辨率与特征点扫描间隔系统能跑之后下一步是让它在普通笔记本上也不卡。最有效的手段是跳帧检测检测线程每 3 帧只处理 1 帧其余帧直接丢弃这样疲劳判定逻辑不用改但 CPU 占用能降一半。另一个手段是把摄像头采集分辨率从 640x480 降到 480x360dlib 关键点检测对小分辨率图像的处理速度会快很多。你可以加一个简单的 FPS 计数器来验证优化效果import time start time.time() frame_count 0 while True: ret, frame cap.read() frame_count 1 if frame_count % 30 0: fps frame_count / (time.time() - start) print(f当前帧率: {fps:.2f})如果换了优化方案后 FPS 从 8 涨到 20就说明方向对了。这里需要提醒的是不要为了帧率把 EAR 阈值也顺手改了阈值和性能是两个独立变量分开调。6.2 报表与权限让系统从演示变成可交付疲劳检测系统的价值不仅在于实时报警还在于事后能看出谁在什么时段疲劳次数最多。Django 的 ORM 可以按小时聚合数据喂给前端图表库。后期接入用户权限拦截 后台管理这套系统的完成度就从“演示版”上升到了“可交付”的状态再配合标题里的论文部分把设计思路与实现参数对应起来。我最初做这套系统时把大半时间耗在 dlib 编译和摄像头抢占上后来养成先写好环境检查脚本、再写业务代码的习惯跑通了链路之后所有的优化都是锦上添花。如果你也在做疲劳检测相关的项目希望这篇笔记能帮你把最坑的路先趟平——希望帮到你。本文还有配套的精品资源点击获取