
简介这份资源面向计算机视觉学习者与驾驶安全监控方向的开发者提供一套基于YOLO11与PyQt5的完整安全带检测方案可用于课程设计、毕业项目或行为规范执行类应用的原型验证。压缩包共2000个文件以1999个XML标注文件和1份PDF使用说明为主XML对应数据集中的标注信息PDF则梳理了YOLO11与PyQt5的配合流程整体约328.4MB已包含训练好的模型权重省去从零标注与训练的成本。系统通过PyQt5构建可视化界面可实时读取摄像头或视频画面并高亮显示驾驶员与乘客的安全带佩戴状态便于将检测结果用于提醒与记录。目前已有190人学习适合希望快速跑通检测、界面展示与数据集复用全流程的读者参考。1. 从一次误报说起这套 yolo11-pyqt5-gui 安全带检测资源到底能干什么去年帮一个做车队管理的朋友看他们的事故回溯视频发现一个很尴尬的现象司机系了安全带但深色衣服和安全带颜色接近传统基于颜色阈值的检测方案频繁漏报反过来夜间红外画面里座椅轮廓被误判成安全带又频繁误报。这类玄学问题在驾驶行为监控里非常典型——安全带检测看似简单实际受光照、遮挡、坐姿、摄像头角度影响极大。这套 yolo11-pyqt5-gui 安全带检测资源正是冲着这个场景来的它把 YOLO11 目标检测模型、标注好的安全带数据集、训练好的权重以及一个 PyQt5 可视化界面打包在一起开箱就能跑推理、看结果、做二次训练。适合两类人一是做驾驶安全监控和行为规范执行的算法工程师需要快速验证安全带检测可行性二是想学 YOLO11 PyQt5 完整落地链路的学生或转行者缺一个能跑通的端到端项目。它解决的不是从零造轮子而是跳过环境踩坑和数据标注直接进入调优和集成。2. 拆开压缩包数据集结构、模型权重与 PyQt5 界面各管什么拿到这个包第一件事不是急着跑main.py而是先搞清楚里面每一块的角色。很多人下载完直接双击运行报错后一脸懵就是因为没分清数据、模型、界面三层的依赖关系。这一章把包内结构、数据格式和界面职责讲透后面调参和排错才有依据。2.1 数据集目录与标注格式YOLO 格式的 images/labels 配对逻辑YOLO 系列训练用的标注格式是每张图对应一个同名.txt每行class_id x_center y_center width height坐标都是归一化到 0~1 的相对值。这套资源里的安全带数据集通常按下面这种结构组织dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── ... │ └── val/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── ... │ └── val/ │ └── ... └── data.yamldata.yaml是训练入口的配置文件内容一般长这样# 数据集配置文件YOLO11 训练时通过 data 参数指定 path: ./dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 2 # 类别数安全带场景常见为 2 类 names: # 类别名称顺序必须与标注里的 class_id 对应 0: seatbelt 1: no_seatbelt这里有个容易翻车的点names的顺序必须和标注文件里写的class_id严格一致。我见过有人把seatbelt和no_seatbelt顺序写反训练 loss 降得很漂亮但推理结果全是反的排查半天才发现是类别映射错了。另外nc要和names的条目数对上少一个多一个都会在训练启动时报维度错误。提示如果数据集里只有系安全带一个类别那检测逻辑要改成检测到安全带即判定为系了未检测到即判定为没系这种单类方案在遮挡场景下误报率会更高建议至少保留两类。2.2 训练好的模型权重pt 文件怎么加载、怎么验证是否可用包里带的训练好权重一般是.pt格式这是 Ultralytics 框架的标准权重格式既能直接推理也能继续微调。加载方式有两种一种是用 Ultralytics 的 Python API一种是用命令行。先看 Python 方式from ultralytics import YOLO # 加载训练好的权重路径按实际包内位置改 model YOLO(weights/best.pt) # 对单张图做推理conf 是置信度阈值iou 是 NMS 的 IoU 阈值 results model.predict( sourcetest.jpg, conf0.25, # 低于这个置信度的框会被丢弃 iou0.45, # 重叠框合并阈值安全带场景建议 0.4~0.5 imgsz640, # 推理输入尺寸要和训练时一致或成比例 saveTrue # 保存带框的结果图 ) # 打印每个检测框的类别和置信度 for r in results: for box in r.boxes: print(r.names[int(box.cls)], float(box.conf))conf这个参数是安全带检测里最需要调的。调低了座椅边缘、衣服褶皱会被误判成安全带调高了深色安全带在暗光下又检不出来。我一般先在验证集上跑一遍看 PR 曲线找平衡点而不是拍脑袋定 0.25。imgsz也要注意如果训练用的是 640推理用 1280 虽然能提升小目标召回但速度会掉一大截实时监控场景要权衡。验证权重是否真的可用别只看它能不能跑出框要看它在验证集上的 mAP。命令行一行就能出指标# 在验证集上评估权重输出 mAP50、mAP50-95 等指标 yolo val modelweights/best.pt datadataset/data.yaml imgsz640 batch8如果 mAP50 低于 0.6基本说明这个权重在你的场景下不够用要么数据分布差异大要么需要重新微调。别硬着头皮往界面上接后面误报会让你怀疑人生。2.3 PyQt5 界面层它负责什么、不负责什么PyQt5 在这套资源里的角色是展示和交互外壳它本身不做检测。典型的分工是界面负责选图片/视频、点按钮触发推理、把带框结果画到 QLabel 上YOLO 负责真正的推理计算。理解这条边界很重要因为很多人把推理卡顿归咎于界面其实是模型在 CPU 上跑。界面里常见的几个控件和它们的作用控件作用常见坑QPushButton触发选文件、开始检测槽函数里做耗时推理会卡死界面QLabel显示图片/视频帧直接 setPixmap 大图会内存暴涨QFileDialog选图片、视频、权重路径含中文时部分环境读取失败QThread把推理放到子线程子线程更新 UI 必须用信号槽这里最关键的一条推理必须放子线程。如果你在按钮的槽函数里直接调model.predict界面会全程无响应用户以为程序崩了。正确做法是继承QThread在run里做推理通过pyqtSignal把结果传回主线程更新 QLabel。这个点在后面第 4 章的避坑里还会展开。3. 从零跑通推理环境搭建、权重加载与界面联调上一章讲清了各模块职责这一章动手把它跑起来。目标很明确装好依赖、加载权重、让 PyQt5 界面能显示检测结果。整个过程我按实际复现顺序拆成环境、推理脚本、界面联调三步每步都给可抄的命令和代码。3.1 环境搭建Python 版本、PyTorch 与 PyQt5 的版本匹配这套资源对版本比较敏感尤其是 PyTorch 和 CUDA 的匹配。我一般用 conda 建独立环境避免污染系统 Python# 创建 Python 3.9 环境YOLO11 和 PyQt5 在 3.9 上兼容性最稳 conda create -n seatbelt python3.9 -y conda activate seatbelt # 安装 PyTorch这里以 CUDA 11.8 为例没有 GPU 就用 CPU 版 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics 和 PyQt5 pip install ultralytics pyqt5 opencv-python版本选择上有几个血泪经验Python 别用 3.12部分 PyQt5 轮子在 3.12 上还没跟上PyTorch 版本要和你的显卡驱动匹配nvidia-smi看右上角 CUDA 版本驱动太老就装低版本 PyTorchUltralytics 用pip install ultralytics装最新即可它会自动带numpy、opencv等依赖。装完验证一下import torch from PyQt5.QtCore import QT_VERSION_STR print(torch:, torch.__version__, cuda:, torch.cuda.is_available()) print(Qt:, QT_VERSION_STR)torch.cuda.is_available()返回 False 就说明在用 CPU推理会慢很多实时视频基本跑不动。如果确实没 GPU就把推理间隔拉大或者只做单张图片检测。3.2 推理脚本把权重、图片路径、置信度串起来环境好了先写一个不依赖界面的纯推理脚本确认模型本身没问题再去接界面。这一步能帮你把模型问题和界面问题分开import cv2 from ultralytics import YOLO # 加载权重路径按包内实际位置调整 model YOLO(weights/best.pt) def detect_image(img_path, conf0.3): 对单张图做安全带检测返回带框图像和检测结果列表 results model.predict(sourceimg_path, confconf, iou0.45, imgsz640) r results[0] # 在原图上画框r.plot() 返回 BGR 格式的 numpy 数组 annotated r.plot() detections [] for box in r.boxes: cls_name r.names[int(box.cls)] score float(box.conf) detections.append((cls_name, score)) return annotated, detections if __name__ __main__: img, dets detect_image(test.jpg) cv2.imwrite(result.jpg, img) print(检测结果:, dets)r.plot()是 Ultralytics 内置的画框方法省得自己写坐标转换。conf0.3是我在安全带场景常用的起点比默认 0.25 稍高能压掉一部分座椅误报。detections里存的是类别名和置信度后面界面要显示系/未系判断就靠它。跑通这一步看到result.jpg上有框说明模型和数据没问题可以进界面了。3.3 界面联调QThread 子线程推理 信号槽更新 QLabel界面联调是新手最容易卡的地方。核心就一句话推理放子线程结果用信号传回主线程。下面是一个最小可用的检测线程和主窗口骨架from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QPixmap, QImage import cv2 from ultralytics import YOLO class DetectThread(QThread): # 定义信号传回带框图像和检测结果文本 finished pyqtSignal(object, str) def __init__(self, model, img_path, conf0.3): super().__init__() self.model model self.img_path img_path self.conf conf def run(self): # 子线程里做推理不阻塞界面 results self.model.predict(sourceself.img_path, confself.conf, imgsz640) r results[0] annotated r.plot() text , .join(f{r.names[int(b.cls)]}:{float(b.conf):.2f} for b in r.boxes) self.finished.emit(annotated, text) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.model YOLO(weights/best.pt) self.label QLabel(等待选择图片) self.btn QPushButton(选择图片并检测) self.btn.clicked.connect(self.on_click) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn) container QWidget() container.setLayout(layout) self.setCentralWidget(container) def on_click(self): path, _ QFileDialog.getOpenFileName(self, 选图片, , Images (*.jpg *.png)) if not path: return self.thread DetectThread(self.model, path) self.thread.finished.connect(self.on_result) self.thread.start() # 启动子线程界面不卡 def on_result(self, img, text): # 把 BGR 的 numpy 转成 QImage 再转 QPixmap 显示 rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, c rgb.shape qimg QImage(rgb.data, w, h, w * c, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg).scaled(self.label.width(), self.label.height())) self.label.setToolTip(text) if __name__ __main__: import sys app QApplication(sys.argv) win MainWindow() win.show() sys.exit(app.exec_())这段代码的关键点有三个DetectThread继承QThread并定义pyqtSignal推理在run里执行主窗口点按钮时thread.start()启动子线程界面立刻恢复响应on_result在主线程里更新 QLabel因为信号槽跨线程是安全的。QImage那行要注意w * c这个步长参数写错会导致图像花屏或错位这是 PyQt5 显示 OpenCV 图像的经典坑。4. 避坑与排查安全带检测落地时最容易翻车的五件事前面把流程跑通了但真正接到实际监控场景问题才刚开始。这一章列五个我实际踩过的坑每条按现象 → 原因 → 解决写都是能省你半天排查时间的经验。4.1 现象界面点检测后直接卡死任务管理器显示无响应原因推理写在了按钮槽函数里主线程被model.predict阻塞Qt 事件循环停转。解决把推理移到QThread子线程用pyqtSignal回传结果参考 3.3 的写法。如果已经用了线程还卡检查是不是在子线程里直接操作了 QLabel跨线程操作 UI 是未定义行为必须走信号槽。4.2 现象模型在验证集 mAP 很高但实际视频里漏检严重原因训练集和实际场景分布不一致。常见的是训练数据多为白天正面视角实际监控是俯视或夜间红外安全带在画面里只占几十个像素。解决先确认推理imgsz是否和训练一致小目标可以适当提高到 960 或 1280再抽实际场景的图做标注微调几轮。别指望一个权重通吃所有摄像头角度。4.3 现象检测框位置对但类别全反了系安全带的被判成没系原因data.yaml里names的顺序和标注文件里的class_id映射反了。解决打开几个标注 txt看class_id是 0 还是 1和names对照。改完data.yaml后如果只是推理权重里的类别顺序是训练时固定的改 yaml 没用得重新训练或做类别映射后处理。4.4 现象PyQt5 显示图像花屏、颜色错乱或只显示一部分原因QImage构造时的步长参数写错或者 numpy 数组不是连续内存。解决确保QImage(rgb.data, w, h, w * c, QImage.Format_RGB888)里步长是w * c如果图像来自切片操作先rgb rgb.copy()保证内存连续。颜色错乱通常是 BGR 没转 RGBOpenCV 读的是 BGRQt 要 RGB。4.5 现象打包成 exe 后在其他机器上跑不起来报缺少 DLL原因PyTorch 和 PyQt5 的依赖没打全或者目标机器没有对应 CUDA 运行库。解决用 PyInstaller 打包时加--collect-all ultralytics --collect-all torch把依赖收全如果目标机器没 GPU打包 CPU 版 PyTorch。打包体积会很大这是 PyTorch 项目的通病别指望几十兆。注意以上五条里4.1 和 4.3 出现频率最高基本每个新手都会撞一次。先把这两条排掉再去看模型精度问题。5. 进阶技巧用置信度阈值和类别后处理把误报压下去跑通、排完坑之后真正决定这套资源能不能用在生产环境的是误报率。安全带检测的误报主要来自两类座椅、衣服褶皱被误判成安全带深色安全带在暗光下漏检。这一章讲两个我常用的后处理技巧配合阈值调优能把误报压到可接受范围。5.1 分场景调 conf白天和夜间用不同阈值固定一个conf很难同时兼顾白天和夜间。我的做法是按画面亮度动态切换阈值import cv2 import numpy as np def adaptive_conf(img_path): 根据图像平均亮度返回建议的置信度阈值 img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) mean_brightness np.mean(gray) if mean_brightness 60: # 夜间/暗光降低阈值保召回 return 0.20 elif mean_brightness 150: # 白天强光提高阈值压误报 return 0.40 else: return 0.30逻辑很直白暗光下模型置信度普遍偏低阈值定高了会漏检所以降到 0.20强光下座椅纹理容易被误判阈值提到 0.40。mean_brightness用灰度均值衡量简单但有效。这个函数可以在界面选图后先算一次把结果传给DetectThread。5.2 类别后处理用未系优先级覆盖系的误判安全带检测最终要输出一个二值判断系了还是没系。模型可能同时检出seatbelt和no_seatbelt也可能只检出一个。我的后处理规则是只要no_seatbelt的置信度超过某个下限就判定为未系哪怕seatbelt也有检出。因为漏报未系把没系判成系了比误报未系把系了判成没系在安全监控里后果更严重。def judge_seatbelt(detections, no_seatbelt_floor0.15): 根据检测结果判断是否系了安全带 detections: [(cls_name, score), ...] no_seatbelt_floor: 未系类别的最低采纳阈值低于此值忽略 has_no any(name no_seatbelt and score no_seatbelt_floor for name, score in detections) if has_no: return 未系安全带 has_yes any(name seatbelt and score 0.3 for name, score in detections) return 已系安全带 if has_yes else 无法判定no_seatbelt_floor设得比正常conf低是为了宁可多报未系也不漏。无法判定这个中间态很重要画面里人不在座位上或者遮挡严重时硬判已系是危险的。这套规则配合 5.1 的动态阈值我在几个测试视频上把误报从每百帧十几次压到了个位数。5.3 验证方法用混淆矩阵和逐帧统计确认效果调完阈值别凭感觉说好多了要有数据。Ultralytics 的val能直接出混淆矩阵# 输出混淆矩阵和各类别指标保存到 runs/ 目录 yolo val modelweights/best.pt datadataset/data.yaml imgsz640 plotsTrue看混淆矩阵里seatbelt和no_seatbelt的互相误判数量比看总 mAP 更有针对性。如果是视频我一般再写个逐帧统计脚本记录每帧的判断结果和置信度导出 CSV 后看误报集中在哪些时间段往往能定位到具体的光照或角度问题。从那以后我每次接安全带检测项目都强制先跑一遍验证集混淆矩阵再拿实际场景视频做逐帧统计两个数据对不上就不往界面上接。这套流程帮我省了太多返工。希望帮到你。本文还有配套的精品资源点击获取