☰
YOLO11+PyQt5安全带检测实战:从数据集到界面部署
2026/10/11 8:21:56 网站建设 项目流程

简介:这份资源面向计算机视觉学习者与驾驶安全监控方向的开发者,提供一套基于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.yaml

data.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( source="test.jpg", conf=0.25, # 低于这个置信度的框会被丢弃 iou=0.45, # 重叠框合并阈值,安全带场景建议 0.4~0.5 imgsz=640, # 推理输入尺寸,要和训练时一致或成比例 save=True # 保存带框的结果图 ) # 打印每个检测框的类别和置信度 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 model=weights/best.pt data=dataset/data.yaml imgsz=640 batch=8

如果 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 python=3.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 版本,驱动太老就装低版本 PyTorch;Ultralytics 用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, conf=0.3): """对单张图做安全带检测,返回带框图像和检测结果列表""" results = model.predict(source=img_path, conf=conf, iou=0.45, imgsz=640) 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 内置的画框方法,省得自己写坐标转换。conf=0.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, conf=0.3): super().__init__() self.model = model self.img_path = img_path self.conf = conf def run(self): # 子线程里做推理,不阻塞界面 results = self.model.predict(source=self.img_path, conf=self.conf, imgsz=640) 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 没转 RGB,OpenCV 读的是 BGR,Qt 要 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_floor=0.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 model=weights/best.pt data=dataset/data.yaml imgsz=640 plots=True

看混淆矩阵里seatbelt和no_seatbelt的互相误判数量,比看总 mAP 更有针对性。如果是视频,我一般再写个逐帧统计脚本,记录每帧的判断结果和置信度,导出 CSV 后看误报集中在哪些时间段,往往能定位到具体的光照或角度问题。

从那以后我每次接安全带检测项目,都强制先跑一遍验证集混淆矩阵,再拿实际场景视频做逐帧统计,两个数据对不上就不往界面上接。这套流程帮我省了太多返工。希望帮到你。

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

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

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

立即咨询