免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

YOLOv5+PyQt5疲劳驾驶检测:从数据集训练到GUI打包全流程

YOLOv5+PyQt5疲劳驾驶检测:从数据集训练到GUI打包全流程 简介基于YOLOv5与PyQt5的疲劳危险驾驶行为检测项目面向计算机专业正在准备毕业设计的学生及需要项目实战练习的开发者既可用于课程设计、期末大作业也可作为学习目标检测与桌面应用开发的完整范例。资源内置了可直接运行的Python检测主程序、训练完成的模型权重、人脸68关键点检测模型以及PyQt5图形界面同时附有详细代码注释、使用说明、演示动画和Docker容器部署配置能够利用摄像头或视频输入实时识别疲劳闭眼、打哈欠等危险驾驶状态并给出预警。整个压缩包共58个文件主要包括20个.py源码、18个yaml模型配置、13个pyc编译文件、ui界面定义文件、模型权重文件及关键点模型数据等整体大小约89MB目录模块划分清晰方便快速定位源码、模型与辅助资源。目前已有918人学习下载项目经过严格调试下载解压后即可运行使用。对于希望将YOLOv5与PyQt5结合完成毕设或工程实践的学习者这份资源在模型调用、界面开发、关键点检测与算法部署等方面均有较好的参考与复用价值能够帮助快速搭建属于自己的人机交互检测系统。1. 疲劳驾驶检测项目拆解YOLOv5 和 PyQt5 各自解决什么问题疲劳驾驶检测不是单一算法能覆盖的任务。YOLOv5 负责在每一帧画面里定位人眼、嘴巴、手部等目标PyQt5 负责把推理结果变成带有视频预览、开始停止、报警状态的桌面 GUI。这类项目的 zip 包通常包含训练好的 .pt 权重、带注释的源码、GUI 脚本和 README。对刚接触目标检测和桌面开发的开发者来说值得学习的不是某个神秘模型而是数据类别设计、推理线程和界面刷新如何协作以及打包后模型路径为什么还能找到。下面按一次标准交付流程展开先讲 YOLOv5 训练自己的数据集再写 PyQt5 调用模型的 GUI 最小实现第三步解决多线程卡顿第四步处理 PyInstaller 打包最后用报警状态机把检测框变成真正可用的疲劳判断。整个过程能在普通笔记本上复现。2. YOLOv5 疲劳检测数据集的类别设计与训练命令2.1 先确定类别疲劳和危险驾驶行为怎么拆疲劳检测通常需要两类眼睛闭合、打哈欠。危险驾驶行为则扩展到抽烟、用手机、低头等。类别定义直接影响标注成本常见做法是先按可观测、可互斥的标准拆成三类到五类。例如eye_closed表示整个眼皮明显遮住眼球mouth_open表示张嘴幅度超过一定比例using_phone表示手部持握设备靠近面部。如果类别定义含混比如 tired 这种整体状态YOLOv5 是没法直接学出语义的。这里有一个很多初学者混淆的点YOLOv5 是目标检测模型输出的是目标边界框和类别置信度它本身不会计算眼睑闭合比例或嘴巴开合角度。所以你需要把闭眼打哈欠直接作为类别来标注而不是期望模型替你判断疲劳程度。后续的疲劳判断其实是基于这些单帧检测结果在时间轴上的统计比如连续 N 帧出现eye_closed或mouth_open就触发报警。2.2 数据标注与目录结构标注工具常用 LabelImg输出 YOLO 格式的 txt 文件。训练集与验证集目录结构如下fatigue_datasets/ images/ train/ val/ labels/ train/ val/每个 txt 文件与图片同名每一行是class x_center y_center width height所有坐标都归一化到 0 到 1。这样 YOLOv5 的 DataLoader 才能直接读取。下面是一个典型的data/fatigue.yaml配置path: ../fatigue_datasets train: images/train val: images/val nc: 4 names: 0: eye_closed 1: mouth_open 2: using_phone 3: smokingpath是相对当前工作目录的数据集根路径train和val是图片目录nc是类别数量names的索引顺序必须和标注文件里的 class id 一致。改类别后要同时改nc和names否则训练时会报标签索引越界。建议把验证集单独留出来不要和训练集混在一起否则模型早停和测试指标都会失真。然后克隆官方 YOLOv5 仓库并安装依赖git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt这段命令会把官方仓库拉到本地requirements.txt里包含了 torch、opencv-python、pandas 等必要依赖。如果你用的是 CUDA 版本的 PyTorch建议先按 PyTorch 官网命令安装对应版本再执行上面的pip install -r requirements.txt避免 requirements 里的 CPU 版覆盖掉 GPU 版。2.3 训练命令与超参表训练自己的数据集时直接调用官方train.py在yolov5目录下执行python train.py \ --data data/fatigue.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --img 640 \ --epochs 100 \ --device 0 \ --cache--weights yolov5s.pt使用 COCO 预训练权重做迁移学习s代表 small显存占用小、速度最快如果类别相似度低或目标较小可以考虑yolov5m.pt。--img 640是训练尺度疲劳检测中的眼睛、嘴巴属于小目标低于 640 会明显掉精度高于 640 显存压力大。--cache把所有图片预加载进内存能缩短训练时间但内存小或数据集大时别开。下面是几个常用训练参数对照参数推荐值说明--weightsyolov5s.pt小目标场景用 s精度优先用 m--datadata/fatigue.yaml数据集配置--epochs100-200数据少时 100 即可加太多会过拟合--batch-size8-32由显存决定OOM 时先减半--img640小目标检测时不要低于 480--patience50验证集连续 50 轮不提升就早停训练结束看runs/train/exp/weights/best.pt这是验证集指标最优的权重。继续训练时用--weights runs/train/exp/weights/last.pt而不是从 COCO 权重重新开始学习率会按剩余 epoch 重新调度。数据集数量方面每个类别至少准备 300-500 张有效图片正样本要覆盖不同肤色、光线、角度和摄像头距离。疲劳检测是在车内场景使用最好录制车载视角而不是实验室正脸数据否则部署到真实环境时误报率会很高。如果懒得自己标可以先用公开数据集验证流程再用自己的场景数据微调。但注意公开数据集和本地真实场景的光照、摄像头位置差异很大直接拿别人训练好的 .pt 模型放到自己的 PyQt5 程序里效果大概率不稳定。这也是为什么源码包里虽然带了训练好的模型你仍然需要重训或至少做验证集评测。3. PyQt5 GUI 加载 YOLOv5 模型的最小实现3.1 安装 PyQt5 并准备模型加载函数PyQt5 是 GUI 框架它本身不关心目标检测。需要先用 OpenCV 读取视频帧再把帧交给 YOLOv5 模型推理最后在 QLabel 上显示。安装pip install pyqt5 pyqt5-toolsPyQt5-tools 提供了 Qt Designer适合拖拽界面后导出 .ui 文件也可以不装直接用代码布局。加载模型的常用函数如下import torch def load_model(weights_path, conf_thres0.45, iou_thres0.45): model torch.hub.load(./yolov5, custom, pathweights_path, sourcelocal) model.conf conf_thres model.iou iou_thres return modeltorch.hub.load(./yolov5, custom, ...)会加载本地 YOLOv5 仓库里的自定义模型sourcelocal阻止它联网下载如果你用了yolov56.2的 pip 包可以替换为torch.hub.load(ultralytics/yolov5, custom, path...)。conf_thres表示置信度阈值0.45 是常规值疲劳检测里小目标置信度普遍偏低可以降到 0.35 观察一下误检率再决定。3.2 视频帧推理与画框推理函数返回的结果对象可以直接渲染也可以转成 Pandas DataFrame 使用def infer(model, frame): results model(frame) df results.pandas().xyxy[0] # df 列: xmin, ymin, xmax, ymax, confidence, class, name return results, df推理函数返回的 results 对象自带渲染和显示能力也可以转成 Pandas DataFrame 使用。pandas().xyxy[0] 是一个扁平结构的表格每一行是一条检测结果包含边界框、置信度、类别名称后续的闭眼次数统计和状态判断都要遍历它。注意results.render()会修改传入帧如果后续还需要原始帧提前 copy。3.3 在 QLabel 上播放检测画面一个最简单的 PyQt5 窗口只有三部分摄像头捕获、定时刷新、QLabel 显示。import cv2 from PyQt5.QtCore import QTimer from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QMainWindow, QLabel class FatigueWindow(QMainWindow): def __init__(self, model): super().__init__() self.model model self.label QLabel() self.setCentralWidget(self.label) self.cap cv2.VideoCapture(0) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.timer.start(30) def update_frame(self): ret, frame self.cap.read() if not ret: return results, df infer(self.model, frame) annotated results.render()[0] rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg))这里有几个容易踩的坑。QImage(rgb.data, ...)只是引用了 numpy 数组的缓冲区如果 rgb 变量被回收画面会出现花屏所以需要在setPixmap之后仍然保持 rgb 存活。QTimer(30)表示每 30 毫秒触发一次理论上约 33 帧但 YOLOv5 在 CPU 推理一帧可能需要 200 毫秒定时器会堆积实际看起来就是界面假死或画面延迟。这个简版适合验证流程真正的项目要用 QThread。cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)把摄像头输出降到 640 宽是为了匹配 YOLOv5 的输入尺度减少不必要的缩放耗时。如果摄像头默认是 1080p在推理前也需要用cv2.resize到 640。下面表格说明两种加载模型方式的差异方式特点适用场景torch.hub.load sourcelocal依赖本地克隆的 YOLOv5 文件夹调试带修改过的网络结构时DetectMultiBackend官方推理脚本用的类直接读 .pt 和导出的模型打包和部署时更轻量DetectMultiBackend在 YOLOv5 的models.experimental里路径不同打包时更容易控制依赖。等代码稳定后建议切换到它而不是torch.hub因为后者会把整个仓库资源打进程序里。4. QThread 多线程与信号槽让 YOLOv5 推理不拖垮 PyQt5 界面4.1 为什么不能在主线程里跑模型PyQt5 的界面事件循环在主线程QLabel重绘、按钮点击、窗口拖动都由它处理。如果把model(frame)放进QTimer回调里CPU 推理期间主线程被占用窗口无法移动、按钮无法响应用户会以为程序崩溃了。正确做法是开一个后台线程持续读摄像头并推理主线程只负责接收结果并刷新界面。线程之间不能直接操作组件必须通过信号槽。PyQt5 提供的pyqtSignal可以在非主线程中发射主线程会排队接收从而把画面数据安全地送回 GUI。4.2 QThread 推理线程的完整结构定义一个DetectThread把读取、推理、画框都放在run()里每处理完一帧就发射两个信号一个传 QImage一个传统计信息。import cv2 import torch import numpy as np from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage class DetectThread(QThread): frame_signal pyqtSignal(QImage) stats_signal pyqtSignal(dict) def __init__(self): super().__init__() self.running True self.conf_thres 0.45 def run(self): cap cv2.VideoCapture(0) model load_model(best.pt, self.conf_thres) while self.running: ret, frame cap.read() if not ret: continue frame cv2.resize(frame, (640, 640)) results model(frame) annotated results.render()[0] df results.pandas().xyxy[0] stats self.compute_stats(df) rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) # 复制一份避免原帧被回收 self.frame_signal.emit(qimg.copy()) self.stats_signal.emit(stats) def compute_stats(self, df): stats {closed: 0, yawn: 0} for _, row in df.iterrows(): if row[name] eye_closed: stats[closed] 1 elif row[name] mouth_open: stats[yawn] 1 return statsframe_signal.emit(qimg.copy())是关键copy()会把像素数据完整复制到 Qt 管理的缓冲区里避免在信号传递过程中出现数据竞争。stats_signal传出的是每帧检测到的闭眼和打哈欠数量后续可以累积判断。在组件里启动线程self.thread DetectThread() self.thread.frame_signal.connect(self.update_frame) self.thread.stats_signal.connect(self.update_stats) self.thread.start()对应的槽函数只做界面刷新def update_frame(self, qimg): self.label.setPixmap(QPixmap.fromImage(qimg)) def update_stats(self, stats): self.closed_label.setText(f闭眼: {stats[closed]}) self.yawn_label.setText(f打哈欠: {stats[yawn]})注意update_stats里不要去调用模型或做耗时计算否则又会在主线程里卡住。如果统计逻辑复杂可以放到另一个 QThread 或者用QTimer定期消费队列。4.3 QTimer 与 QThread 的选择很多入门版本会用 QTimer 直接读摄像头但当推理时间过长时问题明显。下表是两者的对比方案界面响应线程安全代码复杂度QTimer 定时读帧推理期间卡顿主线程内无竞争最低QThread 信号槽流畅需要管理信号中等QTimer 子进程推流最流畅进程隔离通信复杂较高如果摄像头数据来自网络流或视频文件QThread 里的cap.read()本身也会阻塞。可以考虑把摄像头读取和模型推理拆成两个线程中间用队列解耦但这对于疲劳检测项目来说过度设计一般不需要。线程停止时要设置self.running False并在窗口关闭事件里调用def closeEvent(self, event): self.thread.running False self.thread.wait() event.accept()如果直接杀进程摄像头资源可能没有释放下次启动会报设备被占用。5. 用 PyInstaller 打包 YOLOv5 PyQt5 项目成独立程序5.1 打包命令与资源文件处理把项目发布给没有 Python 环境的电脑通常用 PyInstaller。一般命令pip install pyinstaller pyinstaller \ -w \ --name FatigueDetector \ --add-data best.pt;. \ --add-data yolov5;yolov5 \ --add-data data/fatigue.yaml;data \ --collect-all torch \ --collect-all numpy \ main.py--add-data把模型权重和 YOLOv5 仓库一起带进程序。Windows 下源路径和目标路径用分号分割Linux / macOS 用冒号。--collect-all torch会收集 torch 的依赖、动态库和资源文件没有它打包出的 exe 运行时会提示找不到 CUDA dll 或 torch 相关模块。-w表示不弹出命令行窗口如果希望看日志排查问题第一次打包可以不加-w。main.py是程序入口PyInstaller 会分析它导入的模块并打包。5.2 资源路径的兼容写法打包后程序会解压到一个临时目录通过sys._MEIPASS访问。自己的代码里不能再用相对路径找模型需要封装import sys import os def resource_path(relative_path): if hasattr(sys, _MEIPASS): base_path sys._MEIPASS else: base_path os.path.abspath(.) return os.path.join(base_path, relative_path)调用时把模型路径改成model load_model(resource_path(best.pt), 0.45)这样无论是在源码目录运行还是打包后运行路径都能正确找到。YOLOv5 仓库里的文件也需要通过这个函数引用比如在torch.hub.load(resource_path(yolov5), ...)。5.3 打包后的典型报错下表是几个高频问题报错/现象原因解决No module named torchvision依赖收集不完整加--collect-all torchvision找不到 best.pt路径被重定向后失效使用 resource_path一运行就闪退缺 DLL 或动态库去掉 -w 看日志逐个补 add-dataFileNotFoundError: yolov5hub 加载时相对路径失效把 yolov5 整个目录 add-dataPyInstaller 的 onedir 和 onefile 也有区别。onefile 将资源压缩进单个 exe启动慢但分发方便onedir 是一个体积较大的文件夹启动快用于频繁调试时更方便。在疲劳检测这类任务里通常模型权重有几十 MB推荐先用 onedir 模式去掉--onefile稳定后再用 onefile。如果打包出的程序在目标电脑上检测不到摄像头检查是否把摄像头驱动和opencv-python的 DLL 一起打包。可以先用cv2.VideoCapture(0)写一个空程序打包后在目标机验证如果空程序也读不到就是环境问题而不是 PyInstaller 的问题。6. PyQt5 报警状态机抑制误报并验证 YOLOv5 检测效果6.1 从单帧检测到连续状态疲劳驾驶不能只看一帧。摄像头偶尔会因为眨眼、光线变化产生误报所以要把eye_closed或mouth_open的单帧检测结果累积起来。我一般用简单的三态状态机NORMAL、WARNING、ALARM。初始still_frames为 0每检测到目标帧就加一没有目标帧就减一或者清零。当连续帧数超过阈值时进入下一状态。class FatigueStateMachine: def __init__(self): self.state NORMAL self.still_frames 0 self.normal_frames 0 def update(self, closed, yawn): if closed or yawn: self.still_frames 1 self.normal_frames 0 else: self.still_frames 0 if self.normal_frames 5: self.normal_frames 1 if self.state NORMAL: if self.still_frames 8: self.state WARNING elif self.state WARNING: if self.still_frames 15: self.state ALARM elif self.state ALARM: if self.normal_frames 10: self.state NORMAL return self.state参数的含义很直接。still_frames 8表示眼睛闭合或打哈欠约 0.25 秒进入预警达到 15 帧约 0.5 秒才报警。报警后在连续 10 帧正常后复位避免一次眨眼就把报警状态锁死。阈值需要按实际 FPS 调整如果推理只有 10 FPS那么 8 帧是 0.8 秒闭眼一秒的检测要适当降低帧数。6.2 验证方法用视频回放而不是对着摄像头调验证阈值时准备一段 3-5 分钟包含正常驾驶、闭眼、打哈欠、玩手机的视频。跑完统计三种指标正常片段误报次数、疲劳片段漏报次数、从闭眼开始到报警的延迟。保存每一帧的检测结果和状态到 CSV方便找出连续误报的片段。还可以在results.render()之后把state文字画在画面上看状态切换是否稳定。不要一上来就调模型置信度先把状态机阈值调好。很多项目模型本身没有问题但因为单帧阈值太低导致频繁报警最后被判定为系统不可用。6.3 导出 ONNX 或 TensorRT 再提速如果 CPU 推理速度不足把训练好的 .pt 导出成 ONNX再配合 OpenVINO 或 TensorRT 推理。YOLOv5 自带导出脚本python export.py --weights best.pt --include onnx --opset 12导出后会得到best.onnxPyQt5 程序里用onnxruntime或 OpenVINO 的 Python API 加载。推理速度比 PyTorch 快但需要额外处理 NMS 和输出解析代码复杂度会升高。对毕设项目来说先用 QThread 加上摄像头分辨率降到 640 往往已经够用导出 ONNX 是优化中的下一步。模型权重本身只能检测出闭眼打哈欠这类具体目标真正区分疲劳程度的判断逻辑在 PyQt5 侧的状态机里。例如把 ALARM 的触发帧数设成当前 FPS 的三分之一比反复重新训练相同数据更能直接改善体验。本文还有配套的精品资源点击获取
返回列表