尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于YOLOv8与PyQt5的安全帽佩戴检测识别系统开发实践

基于YOLOv8与PyQt5的安全帽佩戴检测识别系统开发实践 在工地安全管理场景里人员是否按规定佩戴安全帽是每天都要检查的高频事项。人工巡检只能抽查无法覆盖全部监控画面虽然摄像头已经普及但普通摄像头本身没有判断能力。要让监控系统自动识别未佩戴安全帽的人员并把结果推送给管理人员就需要一个目标检测识别系统。这里选择 YOLOv8 作为检测算法PyQt5 作为桌面客户端开发框架两者组合可以快速搭出一个可运行的工地安全帽佩戴检测识别系统。读者看完正文后能完成四件事搭建 YOLOv8 训练环境准备安全帽标注数据集并训练检测模型把训练好的模型集成到 PyQt5 桌面程序里实现图片和摄像头实时检测理解浮点精度、模型导出、线程设计和常见问题排查。整个流程以可复现为目标文中的目录和代码是工程示例实际项目要结合自己的数据集、显卡型号和部署环境调整。1. 先理解检测系统的整体架构与检测流程1.1 为什么这类项目适合用 YOLOv8安全帽佩戴检测本质上是目标检测任务在图像或视频帧中找出“安全帽”“人头”等目标并输出边界框、类别和置信度。YOLOv8 是目前落地频率较高的检测框架原因是它的使用链路完整训练、验证、预测、导出都有统一命令行和 Python API。相比早期的 YOLOv5YOLOv8 在结构上做了几处调整主干网络继续使用 C2f 模块颈部采用 FPN-PAN 结构检测头将分类和回归分支解耦并使用 Anchor-Free 方式预测目标。对于安全帽这类大小相对固定、背景复杂的工地场景这些调整带来的收益是训练更稳定、后处理更简单。需要说明的是YOLOv8 并不是在所有场景都绝对最优。比如极端小目标场景可能还需要配合 SAHI 切片推理或自定义检测头。但作为通用基准YOLOv8 适合中小团队快速落地而且后续可以切换更高版本或改进模型。工程项目的关键不是追最新结构而是建立一条可迭代、可评估、可回滚的流程。1.2 系统的功能模块划分一个完整的安全帽佩戴检测系统至少包含三个层面不能只写一个模型文件就算完成。模块职责常见技术选择数据层采集工地图片、标注安全帽和未佩戴状态、划分训练集和验证集LabelImg、LabelMe、Python 脚本模型层训练 YOLOv8 模型、评估指标、导出部署格式Ultralytics YOLOv8、ONNX、TensorRT应用层打开图片或摄像头、调用模型推理、显示结果、保存日志PyQt5、OpenCV、QThread实际项目里应用层比算法层更容易被低估。摄像头流不稳定、界面卡顿、模型路径找不到、日志没有落盘这些都会让算法表现大打折扣。因此本文将算法链路和客户端工程放在同等重要的位置。1.3 从视频帧到检测结果的完整链路检测链路可以概括为采集图像或视频帧 - 图像预处理 - 模型推理 - 后处理 - 结果绘制与展示。预处理阶段主要做尺寸调整、归一化和 letterbox。letterbox 的作用是把原始图像等比缩放后填充到模型输入尺寸比如 640x640避免因为目标被拉伸变形而降低检测精度。模型推理输出的是原始特征图上的预测信息必须经过解码、置信度过滤、非极大值抑制才能得到最终的边界框。非极大值抑制用来解决同一个目标被多个框重复预测的问题。很多初学者直接调用model.predict()看到结果正常就不关心链路这会导致遇到精度下降或推理变慢时无从排查。把链路拆清楚后至少知道问题出在预处理、模型还是后处理。1.4 本文要实现的最小闭环本文的最小闭环包含两部分先训练一个安全帽检测模型再用 PyQt5 做一个带界面的小程序。小程序支持选择本地图片检测也支持打开摄像头实时检测。训练输出模型格式先用best.pt方便快速验证后续再根据性能需求导出 ONNX 或 TensorRT 格式。整个流程跑通后你可以在此基础上增加告警、数据库记录、多路摄像头切换等功能但核心的“数据-训练-推理-展示”路径不会变化。2. 环境准备与依赖安装2.1 硬件与软件版本建议训练阶段建议使用 NVIDIA GPU因为 YOLOv8 的卷积操作在 GPU 上能明显提速。推理阶段如果只做图片演示CPU 也可以接受但实时视频检测会有明显卡顿。环境项建议配置说明操作系统Windows 10/11 或 Ubuntu 20.04训练和部署尽量使用同一种环境避免路径和编码差异Python3.9 或 3.10Ultralytics 对 Python 版本有依赖要求3.10 兼容性较好GPUNVIDIA 显卡显存 8GB 以上训练精度可选显存不足时降低 batch 或 imgszCUDA 版本11.8 或 12.1要先确认显卡驱动支持再选择 PyTorch 版本PyTorch2.0 以上不同版本对 CUDA 的支持不同需要搭配安装Ultralytics8.0 以上训练和推理 API 在 8.x 版本中一致PyQt55.15 系列用来开发桌面客户端OpenCV4.x图像读取和视频帧处理这里给出的版本只是示例实际安装前要查看当时最新的 PyTorch 和 Ultralytics 支持矩阵。不要直接复制网上过时的命令。2.2 创建虚拟环境并安装依赖推荐使用 conda 创建独立环境防止不同项目之间的依赖冲突。下面的命令假设你已经安装并配置好 conda并且显卡驱动版本可以支持 CUDA 11.8。conda create -n helmet python3.10 -y conda activate helmet pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install pyqt5 pip install opencv-python如果只有 CPU 环境torch的安装命令可以改成pip install torch torchvision torchaudio这里要注意训练安全帽模型时如果 GPU 显存较小先不要安装 CPU 版 PyTorch因为二者不可混用。先确认需要训练还是只做推理再选择对应版本。2.3 验证 YOLOv8 和 PyQt5 是否可用安装完成后用一段简单的 Python 脚本验证环境是否正常import torch print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available()) from ultralytics import YOLO model YOLO(yolov8n.pt) print(yolo ok) import PyQt5.QtCore print(pyqt ok)第一次执行YOLO(yolov8n.pt)时Ultralytics 会下载预训练权重需要保持网络通畅。如果torch.cuda.is_available()返回False即使装了 GPU 版 PyTorch也说明 CUDA 驱动或版本匹配有问题。2.4 环境安装最常见的坑安装阶段最常见的问题有三个。第一个是 PyTorch 与 CUDA 版本不匹配。现象是安装后torch.cuda.is_available()为False或者运行时提示找不到 CUDA 动态库。解决思路是先通过nvidia-smi查看驱动支持的 CUDA 版本再选择对应的 PyTorch 安装命令。第二个是pip与conda混装导致包版本被覆盖。推荐在虚拟环境中统一使用pip不要先conda install torch再用pip install ultralytics否则容易出现动态链接库冲突。第三个是没有安装pyqt5-sip导致导入PyQt5报错。出现这种情况时单独安装pyqt5-sip可以解决pip install pyqt5-sip注意环境验证不只是看 import 是否成功还要确认 PyTorch 能否正常使用 GPU。训练前先跑一个最小模型能节省大量排错时间。3. 准备安全帽数据集并转换为 YOLO 格式3.1 类别定义与标注约定安全帽佩戴识别的类别定义有多种方式。最简单的方案是只标注一个类别helmet但这样无法区分“未佩戴安全帽”的状态。实际项目中更常见的方案是定义两个类别类别 ID名称含义0helmet戴在头上的安全帽或独立的安全帽目标1head没有佩戴安全帽的人头在应用层可以结合head和helmet的位置做判断当helmet框覆盖了head框的大部分区域时判定为已佩戴否则为未佩戴。如果业务需要同时识别整个人体可以增加person类但类别越多标注成本和训练难度越高。标注时要统一两个原则第一一张图片中所有可见的相关目标都要标注漏标会导致模型把未标注目标当成背景产生误检第二遮挡严重的目标也尽量标注安全帽在工地画面中经常被遮挡完全不标注会让模型在推理阶段漏掉真实目标。3.2 数据集目录结构YOLO 格式对目录结构有严格要求。推荐使用下面的结构dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ └── val/ │ ├── 1001.jpg │ └── 1002.jpg ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ ├── 0002.txt │ └── val/ │ ├── 1001.txt │ └── 1002.txt └── data.yaml图片和标签必须同名且位于对应的 train 或 val 目录下。比如images/train/0001.jpg对应labels/train/0001.txt。3.3 标注与格式转换LabelImg 支持直接保存为 YOLO 格式LabelMe 默认输出 JSON 文件需要转换。YOLO 标签每一行表示一个目标格式如下class_id x_center y_center width height需要注意x_center、y_center、width、height都是相对图片宽高的归一化值范围在 0 到 1 之间。例如一张图片宽 1920、高 1080某个安全帽边界框的中心点坐标是(960, 540)宽高是(192, 216)对应的坐标应该写成0 0.5 0.5 0.1 0.2如果标注工具输出的是像素坐标需要先转换再写入 txt 文件。转换脚本的核心逻辑如下import os # 像素坐标 x_min, y_min, x_max, y_max 100, 120, 220, 280 img_w, img_h 1920, 1080 # 转换为 YOLO 格式 box_w x_max - x_min box_h y_max - y_min x_center x_min box_w / 2 y_center y_min box_h / 2 x_center_norm x_center / img_w y_center_norm y_center / img_h box_w_norm box_w / img_w box_h_norm box_h / img_h line f0 {x_center_norm:.6f} {y_center_norm:.6f} {box_w_norm:.6f} {box_h_norm:.6f}\n这段代码展示了转换思路实际项目中建议使用批量脚本处理整个数据集避免手工计算。3.4 划分训练集和验证集训练集和验证集的划分要保证类别分布一致最简单的做法是随机划分但需要设置随机种子保证结果可复现。import os import random import shutil random.seed(42) image_dir dataset/images train_dir dataset/images/train val_dir dataset/images/val label_dir dataset/labels train_label_dir dataset/labels/train val_label_dir dataset/labels/val all_images [f for f in os.listdir(image_dir) if f.endswith(.jpg)] random.shuffle(all_images) val_ratio 0.2 val_count int(len(all_images) * val_ratio) for i, img_name in enumerate(all_images): base os.path.splitext(img_name)[0] label_name base .txt if i val_count: shutil.move(os.path.join(image_dir, img_name), os.path.join(val_dir, img_name)) shutil.move(os.path.join(label_dir, label_name), os.path.join(val_label_dir, label_name)) else: shutil.move(os.path.join(image_dir, img_name), os.path.join(train_dir, img_name)) shutil.move(os.path.join(label_dir, label_name), os.path.join(train_label_dir, label_name))实际项目中要注意划分前先检查标签文件是否存在避免只移动图片没有移动标签。还要检查训练集和验证集中每个类别的目标数量防止某一类在验证集里完全没有样本导致评估指标失真。3.5 编写 data.yaml 配置文件Ultralytics 使用data.yaml描述数据集路径和类别信息path: dataset train: images/train val: images/val nc: 2 names: 0: helmet 1: headpath可以写绝对路径也可以写相对于当前工作目录的路径。训练时如果提示找不到图片优先检查path是否正确。names的类别顺序必须和标注文件里的class_id一致否则模型会把安全帽当成头训练结果会非常混乱。3.6 数据集阶段的常见坑数据集阶段最致命的坑是图片和标签不同名训练时大量图片没有标签。Ultralytics 会跳过没有标签的图片但训练日志中不会明显报错只有到验证阶段才会发现 mAP 异常低。第二个常见坑是标注框坐标越界。某些标注工具生成的坐标可能超出图片范围模型训练时会出现警告。可以在训练前写脚本统一裁剪到图片范围内。第三个坑是类别不平衡。比如helmet样本有 8000 个head样本只有 2000 个模型会偏向预测helmet。处理方式不是简单增加训练轮数而是补充更多head未戴帽样本或者使用类别权重。4. 训练 YOLOv8 安全帽检测模型4.1 选择预训练模型与设置训练参数YOLOv8 提供 n、s、m、l、x 几个不同规模的预训练模型。n 最小适合快速验证和边缘设备s 和 m 是安全帽检测项目的常见选择l 和 x 精度更高但对显存和推理时间要求也更高。训练命令示例yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0yolov8s.pt是预训练权重以它为初始权重可以明显加快收敛也能在小数据集上得到更稳定的效果。epochs表示最大训练轮数imgsz是输入图片尺寸batch是每次迭代处理的图片数量device0表示使用第一张 GPU。4.2 关键训练参数说明参数默认值含义调整建议epochs100最大训练轮数数据量少时可缩短到 50数据量大时适当增加imgsz640训练输入尺寸小目标多时提高到 960但会显著增加显存占用batch16批大小显存不足时降低到 8 或 4lr00.01初始学习率使用预训练权重时可降低到 0.001 附近patience50早停等待轮数验证指标不再提升时停止训练防止过拟合optimizerauto优化器选择默认自动选择一般不需要改ampTrue是否使用混合精度训练显存不足时开启可节省显存并提高速度参数之间是联动的。imgsz640和batch16组成的训练显存占用与imgsz960、batch16差别很大。如果训练时报 CUDA out of memory先降低batch再考虑降低imgsz。4.3 启动训练并理解日志中的指标启动训练后终端会输出类似下面的信息Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 1/100 6.2G 1.352 1.421 1.211 50 640 2/100 6.2G 1.102 1.203 1.052 45 640训练日志中box_loss是边界框回归损失cls_loss是分类损失dfl_loss是分布式焦点损失。它们都不是越小越一定是好事要结合验证集指标一起看。验证阶段更关注指标含义precision预测为正样本中真正正确的比例recall真实目标中被正确检出的比例mAP50IoU 阈值为 0.5 时的平均精度mAP50-95IoU 阈值从 0.5 到 0.95 的平均精度更严格假设训练 100 轮后mAP50达到 0.85说明在 IoU 阈值为 0.5 的情况下模型整体识别效果较好如果mAP50-95明显偏低说明模型的定位精度还不够精细。训练完成后结果保存在runs/detect/train/目录下其中weights/best.pt是验证集上表现最好的权重weights/last.pt是最后一轮的权重results.png包含损失和指标曲线。4.4 训练阶段常见坑与解决思路第一个坑是不加区分地使用last.pt部署。如果训练后期发生过拟合last.pt反而比best.pt更差。部署时默认选择best.pt。第二个坑是显存不足。可以降低batch但不要降到 1 后仍然不收敛。此时应优先考虑降低imgsz或者使用yolov8n.pt做基线实验。第三个坑是训练不收敛。原因可能是学习率设置不合适、数据标注明显错误、类别排列混乱。排查方法是先用少量样本训练 20 轮看是否能过拟合。如果不能优先检查数据和标注。如果要在已有模型基础上继续训练比如新增了一批困难样本可以采用增量训练方式yolo detect train \ datadataset/data.yaml \ modelruns/detect/train/weights/best.pt \ epochs50 \ lr00.0005增量训练使用小学习率让模型在新数据上微调不要直接用默认学习率重新训练否则容易破坏已经学到的特征。4.5 导出模型用于部署训练好的best.pt可以直接在 PyTorch 中使用但如果要部署到更轻量的环境可以导出为 ONNX 或 TensorRT 格式。导出 ONNXyolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出 TensorRTyolo export modelruns/detect/train/weights/best.pt formatengine imgsz640 halfTruehalfTrue会让模型使用半精度推理降低显存占用和计算量但前提是部署环境支持 GPU。导出前要确认使用场景如果只在 CPU 上运行ONNX 和 PyTorch 的.pt格式是更稳妥的选择。注意best.pt只是训练产物不是最终交付物。部署前一定要在真实测试集上重新验证导出后的模型因为不同推理引擎对算子的支持程度不同输出可能会有微小差异。5. 用 PyQt5 搭建桌面识别客户端5.1 客户端功能设计与线程模型PyQt5 客户端的功能可以拆成几个按钮和区域选择图片按钮、打开摄像头按钮、检测画面显示区域、状态信息栏。功能逻辑不复杂但线程模型需要提前设计。检测模型推理是一个耗时操作如果直接在 PyQt5 的主线程里执行界面会在推理期间无响应。尤其摄像头实时检测时每一帧都要推理UI 线程会被彻底阻塞。正确做法是把检测任务放到QThread子线程中通过信号把结果传回主线程。5.2 使用 QThread 封装推理任务下面是一个简单的检测线程类import cv2 from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectWorker(QThread): result_ready pyqtSignal(object, object, float) error pyqtSignal(str) def __init__(self, model_path, source, parentNone): super().__init__(parent) self.model YOLO(model_path) self.source source self.running True def run(self): if isinstance(self.source, int): self.process_camera() else: self.process_image() def process_image(self): frame cv2.imread(self.source) if frame is None: self.error.emit(无法读取图片) return img, results, spend self.detect_frame(frame) self.result_ready.emit(img, results, spend) def process_camera(self): cap cv2.VideoCapture(self.source, cv2.CAP_DSHOW) if not cap.isOpened(): self.error.emit(摄像头无法打开) return while self.running: ret, frame cap.read() if not ret: break img, results, spend self.detect_frame(frame) self.result_ready.emit(img, results, spend) cap.release() def detect_frame(self, frame): start cv2.getTickCount() results self.model(frame, conf0.5, iou0.45)[0] spend (cv2.getTickCount() - start) / cv2.getTickFrequency() * 1000 annotated results.plot() return annotated, results, spend def stop(self): self.running False self.wait()这个类同时支持图片路径和摄像头设备号。source为整数时使用摄像头为字符串路径时读取图片。running标志用于退出摄像头循环关闭窗口时调用stop()可以避免线程崩溃。5.3 图片检测与结果展示在 PyQt5 主窗口中选择图片后创建检测线程from PyQt5.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import Qt import cv2 import numpy as np class MainWindow(QWidget): def __init__(self): super().__init__() self.layout QVBoxLayout(self) self.label QLabel(检测画面) self.label.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.label) self.btn_image QPushButton(选择图片) self.btn_camera QPushButton(打开摄像头) self.layout.addWidget(self.btn_image) self.layout.addWidget(self.btn_camera) self.worker None self.btn_image.clicked.connect(self.open_image) self.btn_camera.clicked.connect(self.open_camera) def open_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.jpg *.png *.bmp)) if not path: return self.worker DetectWorker(best.pt, path) self.worker.result_ready.connect(self.show_result) self.worker.error.connect(self.show_error) self.worker.start() def show_result(self, frame, results, spend): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) rgb np.ascontiguousarray(rgb) h, w, ch rgb.shape qimage QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimage) self.label.setPixmap(pixmap.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.setWindowTitle(f检测耗时: {spend:.1f} ms) def show_error(self, msg): self.label.setText(msg)这里展示的是核心逻辑。QImage构造时传入rgb.data由于numpy数组在传递时可能会被回收使用np.ascontiguousarray并转为 RGB 后再传给QPixmap能降低内存访问异常的概率。5.4 摄像头实时检测摄像头实时检测和图片检测的差异主要在循环读取帧。DetectWorker中已经包含process_camera方法。在窗口中打开摄像头时需要先停止之前的检测线程防止并发冲突def open_camera(self): if self.worker is not None: self.worker.stop() self.worker DetectWorker(best.pt, 0) self.worker.result_ready.connect(self.show_result) self.worker.error.connect(self.show_error) self.worker.start()在 Windows 上使用cv2.VideoCapture(0, cv2.CAP_DSHOW)比默认方式更容易打开摄像头因为CAP_DSHOW会使用 DirectShow 后端减少摄像头被占用时的等待时间。5.5 界面布局与启动入口完整的界面还可以增加保存检测结果、显示实时帧率、设置置信度阈值等控件。启动入口很简单import sys from PyQt5.QtWidgets import QApplication if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.resize(960, 640) window.show() sys.exit(app.exec_())最小界面已经能完成“打开图片 - 检测 - 显示结果”但要注意每次创建DetectWorker都会重新加载模型图片检测场景可以接受摄像头实时检测场景建议把模型加载放到初始化阶段避免重复加载耗时。6. 推理性能与浮点精度选型6.1 浮点格式的基本概念YOLOv8 训练时默认使用 32 位浮点数也就是 FP32。FP32 精度高但占用内存多、计算慢。推理时如果显卡支持低精度计算把模型切换到 FP16、BF16 或 TF32可以减少显存占用并提高速度代价是可能带来轻微精度损失。理解和选型浮点格式是深度学习模型部署的常见考点也是 YOLOv8 导出部署时最容易出问题的环节。6.2 四种推理精度格式对比格式位宽指数位尾数位特点适用场景FP3232823通用精度基准兼容性最好训练默认、CPU 推理、精度优先FP1616510显存占用减半速度更快但动态范围小NVIDIA GPU 推理支持 TensorCore 的环境BF161687动态范围与 FP32 相同精度降低主要来自尾数减少训练和推理均可尤其适合大模型TF3219 位有效运算810Ampere 架构 GPU 的 TensorCore 加速模式不是完整存储格式在支持 TF32 的 GPU 上快速训练FP16 的尾数位只有 10 位数值范围比 FP32 小。当激活值过大或过小时FP16 可能出现溢出需要配合混合精度技术。BF16 保留了 8 位指数位因此动态范围更广但尾数位只有 7 位数值精度比 FP16 还低适用于对精度要求不高的任务。6.3 在 YOLOv8 推理中切换精度在 Ultralytics 中推理时可以直接启用半精度from ultralytics import YOLO model YOLO(best.pt) results model.predict(test.jpg, halfTrue)halfTrue会尝试把模型和输入数据转为 FP16。这个操作只在 GPU 环境有意义CPU 上 FP16 通常不会加速反而可能因为转换开销变慢。导出 ONNX 时也可以使用半精度yolo export modelbest.pt formatonnx halfTrue不过 ONNX Runtime 在不同的执行提供程序上对 FP16 支持不同导出后要实际运行一次不能只凭导出成功判断可用。6.4 结合部署场景选择合适的精度部署场景推荐精度原因训练阶段FP32 AMP混合精度默认开启节省显存训练速度更快服务端 GPU 推理FP16 或 TensorRT FP16NVIDIA GPU 上加速明显显存占用低Windows 桌面端 CPU 推理FP32CPU 对 FP16 支持有限FP32 更稳定低算力嵌入式设备INT8 量化需要校准数据并验证 mAP 下降幅度在安全帽检测项目中如果客户端是普通办公电脑CPU 推理使用 FP32 往往比 FP16 更稳定。如果客户端有 NVIDIA 显卡再用 FP16 优化不要盲从网上“半精度一定更快”的说法。6.5 精度切换的常见坑第一个坑是在 CPU 上执行halfTrue结果检测慢或报错。发现环境只支持 CPU 时直接使用默认 FP32。第二个坑是 FP16 导出后精度下降很多。安全帽检测中小目标对框回归精度比较敏感。如果mAP50-95下降超过可接受范围可以用 INT8 量化加校准来优化或者退回 FP32。第三个坑是混淆 TF32 和 FP16。TF32 是 NVIDIA Ampere 架构上 TensorCore 对 FP32 运算的加速模式它并不改变存储格式而是在矩阵乘算时截断输入。PyTorch 中可以通过torch.backends.cuda.matmul.allow_tf32 True控制不是简单的模型精度转换。7. 常见问题排查链路7.1 训练时 loss 不下降现象是训练日志中 loss 在很大范围内波动或者一直居高不下。常见原因包括数据集标注错误、学习率设置不当、训练集和验证集划分混乱。排查顺序可视化一批训练图片把标注框绘制出来检查类别 ID 和坐标是否正确。使用小数据集训练 20 轮看模型能否过拟合。把lr0调低到0.0001排除学习率过大的问题。查看results.png中损失曲线是否有下降趋势。如果小数据集能过拟合通常说明数据量或数据多样性不足而不是模型结构问题。7.2 检测准确率不足现象是安全帽被漏检或者把其他物品误检成安全帽。常见原因和检查方式如下表问题现象常见原因检查方向处理建议小目标漏检目标像素面积小统计训练集中目标宽高分布提高 imgsz使用切片推理远距离漏检验证集与训练集分布差异大观察错误样本的拍摄距离增加对应距离的训练数据误检率偏高背景与安全帽颜色纹理相似查看误检样本增加负样本或使用更高置信度阈值同一目标多个框NMS 阈值过低查看后处理参数适当提高 iou 阈值排查时不要一上来就换大模型先看错误样本集中在哪一类。常见的情况是某类样本太少模型对该类别的 recall 很低此时扩充样本比换模型更有效。7.3 PyQt5 界面卡顿现象是移动窗口、点按钮响应很慢。原因通常是推理操作放到了 UI 线程。排查时看 CPU 或 GPU 占用情况如果在点击检测按钮后程序无响应说明主线程被阻塞。解决办法是把推理放到QThread中同时控制摄像头处理帧率不需要每帧都检测可以设置为 10 FPS 或 15 FPS避免连续推理导致画面延迟不断累积。还可以把检测结果显示优化为只
返回列表