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

资讯详情

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

基于YOLOv8的高速公路路面抛洒物检测系统:从数据集处理到部署的完整实战

基于YOLOv8的高速公路路面抛洒物检测系统:从数据集处理到部署的完整实战 简介这份资源面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师提供一套可直接运行的交通高速公路路面抛洒物检测方案适合作为毕业设计、课程设计或大作业的完整参考。压缩包共8个文件约15.91MB包含3个Python脚本、3个模型权重文件与2个说明文本分别承担可视化界面、模型训练与视频检测、预训练权重加载及部署指引等用途结构精简、上手门槛低。项目基于YOLOv8实现配套完整数据集与可视化页面可生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图便于答辩时系统展示实验过程与效果。目前已有91人学习下载代码均经运行验证后上传下载后按README说明即可完成部署也可在此基础上修改扩展功能用于学习进阶或项目初期立项演示。1. 路面抛洒物检测为什么YOLOv8成了高速场景的默认选项高速公路上一个掉落的轮胎皮、一袋散落的水泥留给后车的反应时间往往不到两秒。这类抛洒物检测和普通的目标检测任务有本质区别目标极小、背景高速运动、正负样本极度不均衡。我最早用传统帧差法做过一版白天误报率还能忍一到夜间或者雨天虚警直接把监控室淹了。后来换成YOLOv8才算把召回率和误报率同时压到可接受的范围。这套「基于YOLOv8的交通高速公路路面抛洒物检测系统」要解决的就是从监控视频流里实时框出路面异物并触发告警。它适合三类人做毕设或课程设计的学生需要一个功能完整、能跑通全流程的项目刚接触YOLOv8的工程师想拿一个真实场景练手数据集处理和部署还有做交通信息化的从业者评估这套方案能不能落到自己的路段上。标题里提到的源码、可视化界面、完整数据集、部署教程本质上是在降低从零搭建的门槛——但门槛低不代表没有坑后面几章我会把每个环节的边界条件讲清楚。2. 数据集怎么处理才能让抛洒物检测不翻车抛洒物检测的效果七成取决于数据集质量三成才是模型和调参。很多人拿到标注文件直接开训结果mAP卡在0.4上不去回头查才发现标注框大量漏标、类别定义混乱。这一章把数据集的清洗、转换、增强讲透。2.1 抛洒物数据集的类别定义与标注规范高速抛洒物的类别划分不能太细。我见过有人把「塑料袋」「纸箱」「轮胎皮」「石块」「金属件」分成五类训练出来每一类的样本都不够模型根本学不动。常见做法是合并成两类debris一般抛洒物和large_object大件抛洒物比如轮胎、家具。如果只做二分类检测直接用一个debris类也够用。标注时用Labelme或者LabelImg都行导出成YOLO格式。关键规范有三条第一小于16×16像素的目标要么放大标注区域要么直接丢弃YOLOv8的P3特征图对这么小的目标响应很弱第二运动模糊严重的帧不要标标了也是噪声第三同一目标在连续帧里只标关键帧不要每帧都标否则正样本冗余严重。标注完成后目录结构应该是这样dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml的内容path: ./dataset train: images/train val: images/val test: images/test nc: 2 names: [debris, large_object]这里nc是类别数names的顺序必须和标注文件里的class_id一一对应。我踩过的坑是标注时先标了large_object再标debris结果class_id反了训练出来模型把大件识别成小件排查了半天。2.2 用脚本把VOC和COCO格式转成YOLO训练格式很多公开数据集是VOC的XML格式或者COCO的JSON格式不能直接喂给YOLOv8。我一般写一个转换脚本统一处理。以VOC转YOLO为例import xml.etree.ElementTree as ET import os from pathlib import Path def voc_to_yolo(xml_dir, img_dir, out_label_dir, class_map): xml_dir: VOC标注文件目录 img_dir: 对应图片目录用于读取宽高 out_label_dir: 输出YOLO标签目录 class_map: {debris: 0, large_object: 1} os.makedirs(out_label_dir, exist_okTrue) for xml_file in Path(xml_dir).glob(*.xml): tree ET.parse(xml_file) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 转成YOLO的归一化中心点宽高 cx (x1 x2) / 2.0 / w cy (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_file Path(out_label_dir) / (xml_file.stem .txt) out_file.write_text(\n.join(lines)) if __name__ __main__: voc_to_yolo( xml_dirraw/Annotations, img_dirraw/JPEGImages, out_label_dirdataset/labels/train, class_map{debris: 0, large_object: 1} )逻辑说明遍历每个XML文件读取图片宽高把VOC的左上右下坐标转成YOLO需要的归一化中心点坐标和宽高。class_map控制类别映射不在映射表里的类别直接跳过。参数上注意cx和cy是除以图片宽高不是除以2这是新手最容易写错的地方。转换完成后一定要抽查几张图片的标签是否对齐。我一般用下面的脚本可视化验证import cv2 import numpy as np def visualize_label(img_path, label_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) visualize_label(dataset/images/train/001.jpg, dataset/labels/train/001.txt, [debris, large_object])如果框的位置明显偏移大概率是宽高读反了或者归一化除错了。2.3 针对小目标的增强策略与负样本采样抛洒物在画面里往往只占几十个像素默认的Mosaic增强对小目标其实不友好——四张图拼在一起后小目标被进一步缩小。我的做法是训练前期用Mosaic后期最后10个epoch关掉让模型在原始尺度上收敛。另外开启copy_paste增强把标注好的抛洒物复制粘贴到其他帧的路面上能有效增加正样本密度。负样本同样重要。高速路面上的车道线、阴影、水渍、路面修补痕迹都是常见的误报来源。我一般会从没有抛洒物的视频里抽2000到3000帧作为背景负样本混进训练集。YOLOv8对纯背景图的处理是如果一张图没有任何标注它会被当作负样本参与分类损失计算不会报错。注意负样本比例不要超过总样本的30%否则模型会偏向于「什么都不检测」。3. YOLOv8训练抛洒物检测模型的完整命令与参数数据集准备好之后训练本身反而是最标准化的环节。但标准化不代表可以无脑跑学习率、batch size、输入尺寸这几个参数直接决定模型能不能收敛到可用水平。3.1 环境配置从零搭好YOLOv8训练环境我一般用conda建一个独立环境避免和系统里的其他包冲突。Python版本选3.9或3.10太新的版本有些依赖还没跟上。conda create -n yolov8 python3.10 -y conda activate yolov8 # 安装PyTorch根据你的CUDA版本选对应命令 # CUDA 11.8的情况 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装ultralytics pip install ultralytics # 验证安装 yolo checksyolo checks会输出当前环境的信息包括PyTorch版本、CUDA是否可用、GPU型号。如果CUDA显示不可用检查驱动版本和PyTorch版本是否匹配。CPU版本也能跑但训练速度会慢一个数量级GTX 1660 Ti这种6GB显存的卡用imgsz640、batch8大概能跑起来。3.2 训练命令拆解每个参数为什么这么设最基础的训练命令yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ device0 \ workers4 \ projectruns/debris \ nameexp1逐参数说明参数含义抛洒物场景建议值model预训练权重yolov8s.pt起步显存够用yolov8m.ptepochs训练轮数150到300看早停imgsz输入尺寸640起步小目标多用960batch批大小6GB显存用8到1212GB用24lr0初始学习率0.01是默认小数据集降到0.005lrf最终学习率因子0.01即最终lrlr0×lrfpatience早停耐心值30验证集不涨就停workers数据加载线程4到8Windows下设0避免报错为什么选yolov8s而不是yolov8nnano版本虽然快但对小目标的特征提取能力明显不足抛洒物检测里mAP会低5到8个点。s版本在速度和精度之间平衡得最好如果部署到RK3588这类边缘设备再考虑用n版本或者做量化。3.3 训练过程监控与损失曲线判读训练启动后runs/debris/exp1/目录下会生成results.csv和一系列曲线图。重点看三个指标train/box_loss、val/box_loss、metrics/mAP50。正常的收敛过程是box_loss在前20个epoch快速下降之后缓慢下降mAP50持续上升到后期趋于平缓。如果val/box_loss开始上升而train/box_loss还在降说明过拟合了要么加数据要么加正则化调大weight_decay。画损失曲线的脚本import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/debris/exp1/results.csv) df.columns df.columns.str.strip() fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain) axes[0].plot(df[epoch], df[val/box_loss], labelval) axes[0].set_xlabel(epoch) axes[0].set_ylabel(box_loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50], labelmAP50) axes[1].plot(df[epoch], df[metrics/mAP50-95], labelmAP50-95) axes[1].set_xlabel(epoch) axes[1].set_ylabel(mAP) axes[1].legend() plt.tight_layout() plt.savefig(loss_curve.png, dpi150)这个脚本直接读results.csv列名可能有空格所以先strip一下。如果mAP50在0.7以上基本可以进入部署阶段低于0.5的话回头查数据集标注质量。4. 可视化界面与推理部署从权重文件到可交互系统训练出best.pt只是第一步真正交付的是一个能打开摄像头、能上传视频、能实时框出抛洒物并告警的系统。可视化界面用PyQt5或者Gradio都行我倾向于PyQt5因为打包成exe更方便。4.1 用PyQt5搭一个抛洒物检测可视化界面界面不需要花哨核心功能就三个视频显示区、检测结果列表、告警日志。下面是一个最小可运行版本import sys import cv2 from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget, QPushButton, QTextEdit) from PyQt5.QtCore import QTimer, Qt from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class DebrisDetector(QMainWindow): def __init__(self): super().__init__() self.model YOLO(runs/debris/exp1/weights/best.pt) self.cap None self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.setWindowTitle(高速抛洒物检测系统) self.resize(900, 700) central QWidget() self.setCentralWidget(central) layout QVBoxLayout(central) self.video_label QLabel(等待视频输入...) self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setMinimumSize(800, 500) layout.addWidget(self.video_label) self.btn_open QPushButton(打开视频) self.btn_open.clicked.connect(self.open_video) layout.addWidget(self.btn_open) self.log QTextEdit() self.log.setReadOnly(True) layout.addWidget(self.log) def open_video(self): self.cap cv2.VideoCapture(0) # 0为摄像头也可换成视频路径 self.timer.start(30) def update_frame(self): ret, frame self.cap.read() if not ret: return results self.model(frame, conf0.4, iou0.5, verboseFalse) annotated results[0].plot() # 记录检测到的目标 boxes results[0].boxes if boxes is not None and len(boxes) 0: for cls_id in boxes.cls.tolist(): name self.model.names[int(cls_id)] self.log.append(f检测到: {name}) rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qimg)) def closeEvent(self, event): if self.cap: self.cap.release() self.timer.stop() if __name__ __main__: app QApplication(sys.argv) win DebrisDetector() win.show() sys.exit(app.exec_())逻辑说明QTimer每30毫秒触发一次update_frame从视频源读一帧送入YOLO推理results[0].plot()直接返回画好框的numpy数组转成QImage显示。conf0.4是置信度阈值iou0.5是NMS的IoU阈值。检测到目标时往日志区追加一条记录。参数上conf设太低会误报设太高会漏报。抛洒物场景我一般用0.35到0.45之间根据实际路测调整。iou保持0.5默认值即可除非发现同一目标被重复框选。4.2 推理参数调优置信度、IoU与输入尺寸的联动推理阶段有三个参数需要联动调整conf、iou、imgsz。它们不是独立的改一个往往要跟着改另一个。imgsz在推理时可以和训练时不同。训练用640推理可以用1280来提升小目标召回但速度会下降。如果部署在边缘设备上反而要降到416或320来保帧率。我的经验是服务器端用1280边缘端用640极端情况用416。conf和iou的联动关系调低conf会让更多候选框进入NMS此时如果iou也低NMS会过滤掉大量重叠框可能导致漏检如果iou高保留的框多误报增加。抛洒物检测里我一般先固定iou0.5然后从0.5往下调conf直到召回率满足要求再看误报能不能接受。# 批量测试不同conf下的检测结果 from ultralytics import YOLO model YOLO(best.pt) for conf in [0.25, 0.35, 0.45, 0.55]: results model(test_video.mp4, confconf, iou0.5, streamTrue) total 0 for r in results: total len(r.boxes) print(fconf{conf}, 总检测框数{total})这个脚本能快速看出不同置信度下的检测框数量变化帮你找到拐点。4.3 部署到边缘设备RK3588与CPU版本的取舍如果标题里的部署教程要落到实际硬件上RK3588是常见选择。它自带NPU能把YOLOv8的推理速度从CPU上的2到3帧提升到25帧以上。但模型需要先转成RKNN格式转换过程中有几个坑第一YOLOv8的输出层需要做后处理适配不能直接用ONNX的输出去转第二量化时要用真实抛洒物图片做校准集否则量化后小目标精度掉得厉害第三RKNN Toolkit的版本要和板端驱动匹配版本不对会报init_runtime失败。如果只是做毕设演示CPU版本足够了。Ubuntu 20.04上装好PyTorch CPU版推理一张640×640的图大概200到400毫秒用视频流的话做个跳帧处理也能跑。5. 抛洒物检测落地时最容易踩的五个坑这一章记录我在实际部署中翻车最多的几个问题每个都按「现象→原因→解决」写清楚。5.1 夜间误报率飙升路灯和车灯被当成抛洒物现象白天跑得好好的模型一到夜间监控画面里路灯的光斑、对向车灯的眩光被大量框成debris误报率从5%涨到40%以上。原因训练集里夜间样本太少模型没见过这种高对比度、大面积过曝的负样本。另外YOLOv8的默认色彩增强没有针对夜间做特殊处理。解决从夜间监控视频里抽2000帧以上作为负样本加入训练集同时开启hsv_v增强让模型对亮度变化更鲁棒。如果还不行在前处理里加一个自适应直方图均衡化把夜间画面的动态范围压一压。5.2 小目标漏检远处抛洒物在640输入下只剩几个像素现象距离摄像头50米以外的抛洒物在640×640输入下只有8×8像素模型完全检测不到。原因YOLOv8的P3特征图下采样8倍640输入下P3的特征图是80×808像素的目标在上面只占1个格子特征太弱。解决推理时把imgsz提到1280P3特征图变成160×160小目标占4个格子召回率明显提升。代价是推理速度降一半左右。另一个办法是在训练时开启copy_paste人为增加小目标样本。5.3 类别不平衡导致大件抛洒物被忽略现象large_object类的AP只有0.3而debris类有0.75模型几乎不检测大件。原因大件抛洒物在数据集里占比不到10%分类损失被小类主导。解决在data.yaml里给每个类加权重或者在训练时用fraction参数控制采样比例。更直接的办法是过采样大件样本把包含large_object的图片复制2到3份放进训练集。5.4 视频流推理延迟累积导致画面卡顿现象用streamTrue处理长视频时内存持续上涨画面越来越卡。原因streamTrue返回的是生成器如果不及时消费结果会堆积在内存里。另外OpenCV的VideoCapture缓冲区默认会缓存多帧。解决设置cv2.VideoCapture的缓冲区大小为1cap cv2.VideoCapture(video.mp4) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)推理时用streamTrue并逐帧消费不要一次性把结果转成list。5.5 模型导出ONNX后精度下降现象PyTorch权重推理正常导出ONNX后用onnxruntime推理mAP掉了3到5个点。原因导出时的opset版本和推理引擎不匹配或者动态轴设置不对导致resize行为不一致。解决导出时指定opset12并且用dynamicFalse固定输入尺寸yolo export modelbest.pt formatonnx opset12 imgsz640 dynamicFalse导出后用onnxruntime跑一遍验证集和PyTorch的结果对比确认精度一致再部署。6. 把mAP再提5个点几个我常用的进阶技巧训练到mAP50在0.75左右时常规调参已经很难再涨了。这时候需要从数据和模型结构两个层面找突破口。第一个技巧是难例挖掘。用训练好的模型跑一遍训练集把置信度在0.1到0.4之间的预测框对应的图片挑出来人工复核。这些是模型「犹豫」的样本把它们重新标注后加入训练集对精度的提升比随机加数据明显得多。我一般做两轮难例挖掘mAP能涨3到5个点。第二个技巧是多尺度训练。在data.yaml里设置imgsz为一个范围而不是固定值比如imgsz640,960训练时每10个epoch随机换一次尺度。这样模型对不同距离的抛洒物都有适应性。代价是训练时间增加20%左右。第三个技巧是替换检测头。YOLOv8默认的检测头是解耦头对小目标已经比较友好。如果还想再进一步可以换成带注意力机制的检测头比如在P3特征图上加一个CBAM模块。改动不大但小目标的AP能涨2个点左右。# 在YOLOv8的head部分插入CBAM的示意 import torch import torch.nn as nn class CBAM(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.fc nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, biasFalse), nn.ReLU(), nn.Conv2d(channels // reduction, channels, 1, biasFalse) ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.fc(self.avg_pool(x)) max_out self.fc(self.max_pool(x)) return x * self.sigmoid(avg_out max_out)这个模块加在P3的卷积层后面通道数对齐即可。注意加了之后要重新训练不能直接加载原权重。最后一个习惯每次训练完我都会用验证集里mAP最低的那10张图做一次可视化看看模型到底在哪些场景下失败。十次里有八次能发现标注错误或者场景遗漏。模型不会骗人它学不会的东西要么是数据没给够要么是标注有问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表