ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv8路面积水检测实战:从数据集构建到C++部署全流程解析

YOLOv8路面积水检测实战:从数据集构建到C++部署全流程解析 简介YOLOv8路面积水识别项目代码面向目标检测与深度学习实践人群聚焦道路积水视觉检测任务可应用于智能交通、辅助驾驶、城市内涝监测等场景。压缩包共468个文件、大小约24.25MB其中包含130个Python脚本训练、推理与工具脚本、55个YAML/YML配置文件模型与训练参数、227个Markdown说明文档环境配置与使用笔记并附3个预训练PT权重文件及少量C推理、Docker部署支持文件目录结构清晰。目前已有225人浏览学习。资源提供完整的工程化代码按照requirements.txt配置依赖即可复现训练与推理流程训练日志、结果CSV及可视化文件可帮助分析模型表现适合希望快速迁移YOLOv8到自定义检测任务、并参考完整工程组织方式的开发者。除代码与权重外包内还提供详尽的Markdown文档便于对照数据集介绍理解实现细节。1. 路面积水这种尺度小、对比低的障碍物为什么恰好是 YOLOv8 的主场暴雨天在城市快速路上开车挡风玻璃前那一块深色水面是最难判断的障碍物。它的形状没有固定边界灰度又和沥青路面几乎一样传统视觉用边缘检测和纹理分析都容易翻车积水反光还会把光照信息彻底打乱。这个项目用 YOLOv8 把这层识别做成了端到端输入一帧前视图像直接输出积水区域的外接框和置信度训练好的权重可以落到 PC 端也能导出成其他格式部署到边缘设备。项目包本身就是一套完整交付物events.out.tfevents是训练过程的 TensorBoard 日志results.csv记录了每一轮的损失和 mAP 曲线main.cpp和inference.cpp对应 C 推理侧代码setup.cfg和CITATION.cff说明它已经按可安装、可引用的工程标准整理过。适合两类人一是做智慧城市、辅助驾驶相关课题的学生拿这个做基线模型再改检测头二是要快速验证“积水能不能被检测出来”的工程师不需要从零攒数据集。2. YOLOv8 网络结构里 C2f 与 anchor-free 检测头积水识别选型的关键2.1 为什么积水检测不选 Faster R-CNN 而选 YOLOv8积水在 640×640 输入下往往只占几十个像素属于典型的小目标但它又和一般小目标不一样没有清晰纹理只有“比路面更暗、边缘有弱反光”这种低对比特征。两阶段检测器在这种场景下精度上限高但前向速度慢一帧前视图像要跑在实时或准实时链路里并不划算。YOLOv8 的定位是单阶段、anchor-free、decoupled head在同等算力下比 YOLOv5 的召回率更高在低对比目标上也更稳。单阶段模型直接回归框和类别省掉了候选区域生成这一步训练和部署链路都简单。这个项目选 YOLOv8 而不是 YOLOv5 的另一个原因是代码生态ultralytics库把训练、验证、导出、推理都封装成了命令行对要快速出结果的人来说踩坑成本低得多。2.2 C2f 模块梯度分流让浅层特征不白学YOLOv8 的 backbone 里把 YOLOv5 的 C3 模块换成了 C2f名字里这个2指两条分支主干做 1×1 卷积降维后直接短路另一条经过若干 Bottleneck 后再拼接。关键在f它把每层 Bottleneck 的输出都保留下来最后统一 concat而不是像 C3 只保留最后一层。效果是梯度回传时有更多路径可以走浅层能学到更多路面的弱纹理信息。积水检测里这个机制很重要。水面的边缘是渐变的不像行人、车辆有明确的棱角浅层特征如果被深层梯度“带偏”学出来的就是一团模糊的暗色块。C2f 的密集跳连接让每一层都有独立的梯度来源边缘响应保留得更好。实际使用时中间的 Bottleneck 数量由模型的 depth factor 控制YOLOv8s 是 0.33YOLOv8m 是 0.67不需要手动去改结构。2.3 SPPF 与 PAN-FPN不同尺度水面都能被看到积水目标的大小跨度很大近处的水面可能占据画面的三分之一远处只是一条反光线。Backbone 最后接的 SPPF 用 5×5、9×9、13×13 三个池化核并联再接 concat把不同感受野的信息叠在一起保证大目标不会因为下采样太狠而丢失Neck 部分沿用 PAN-FPN 结构自顶向下传语义、自底向上传细节小目标的位置信息不会在下采样中丢干净。训练时 YOLOv8s 默认输入是 640×640如果你的场景是车载前视摄像头建议保留这个尺寸不要为了提速降到 416。积水本身对比度低分辨率再降下去远处水面的反光带在特征图里就只剩 1~2 个像素检测头很难给出稳定的框。2.4 Anchor-free 检测头不规则目标就该直接回归传统 anchor 机制要预先设计一组框的宽高比然后判断每个 anchor 和真值的 IoU。积水形状高度不规则有人字形水痕、有横贯路面的水带预设 anchor 很难覆盖这些形态。YOLOv8 的 anchor-free 头把每个位置直接预测“到左上角和右下角的距离”框的大小完全由回归分支决定不依赖先验形状。这对积水这种目标反而是优势只要标注框是准确的模型就能学会各种长宽比。代价是回归分支的收敛难度比 anchor-based 略高所以损失函数里 DFLDistribution Focal Loss占了很大比重它让框回归变成对边界分布的估计而不是直接回归坐标值。训练时如果发现框的边界抖动大优先检查 DFL loss 是否在正常下降而不是急着调 anchor 参数。下表是路线对比方便在写方案时直接引用方案定位方式对不规则目标适应度推理速度小目标表现Faster R-CNNanchor RoI中慢中依赖 RPN 质量YOLOv5anchor-based中低快中YOLOv8anchor-free高快高RTMDetanchor-free高快高但生态不如 ultralytics3. 从标注格式到 data.yaml积水数据集的构建与复现细节3.1 标注口径积水框应该标到哪里数据集来自博主整理的公开来源原项目说明里附了数据集详细介绍和下载入口。拿到原始图片后第一件事不是训练而是统一标注口径。积水的边界是渐变的不同人标出来的框差异会很大这直接决定模型收敛上限。我一般按这个口径执行框住水面本身的区域不含路缘石和绿化带反光边缘如果和水面连通算进框内如果只是路边湿润的深色痕迹不标。积水被车辆遮挡一半时标可见部分不猜被遮挡的区域。这个项目是单类目标nc1类名统一叫puddle。标注工具用 LabelImg 或 labelme 都行导出格式必须是 YOLO txt每一行是class x_center y_center width height四个坐标值都归一化到 0~1。一条标注示例0 0.521484 0.462891 0.187500 0.142578# 校验标注是否越界读取 label 文件检查归一化坐标是否都在 [0,1] 内 def check_label(txt_path, img_w, img_h): bad [] with open(txt_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad.append(f{txt_path}: 字段数异常 {line}) continue _, cx, cy, w, h parts cx, cy, w, h map(float, (cx, cy, w, h)) if not (0 cx 1 and 0 cy 1 and w 0 and h 0): bad.append(f{txt_path}: 坐标越界 {line}) return bad这段脚本在训练前跑一遍整个数据集目录能筛出标注时手滑导致的空框、负宽高和越界坐标。YOLO 格式的标签如果坐标越界训练时某些增强会把目标裁出图像外显存里会出现 NaN loss报错信息往往看不懂。提示图片尺寸不统一时不需要先 resizeYOLOv8 训练时会在数据加载阶段做 letterbox标签坐标是归一化的所以不受影响。3.2 划分数据集按拍摄场景切分而不是随机切积水图片往往来自连续视频抽帧相邻帧高度相似。如果直接随机划分 train/val同一段路面会同时出现在训练集和验证集验证指标虚高换成没见过的路段就翻车。正确做法是按场景分组同一个拍摄地点、同一场雨的连续帧放到同一个组里再把组切分到 train/val/test。项目数据量不大时我建议按 7:2:1 切分test 目录可以没有标注只用来做推理验证。目录结构按 ultralytics 默认习惯组织dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/3.3 编写 data.yaml 与数据集检查训练前需要一份 YAML 描述数据路径和类别信息ultralytics 会读取它去匹配图片和标签。路径建议写绝对路径或相对于 YAML 文件的位置避免换机器后目录对不上。# data.yaml路面积水识别数据集配置 path: /path/to/your/dataset # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 test: images/test # 测试图片目录 nc: 1 names: 0: puddlepath字段是根路径train和val是相对路径。写完后可以先跑一下数据检查命令确认每个标注框都对应到了图片上避免训练跑了两小时才发现类别 ID 对不上# 用 ultralytics 自带的检查逻辑跑一遍数据和标签匹配情况 yolo checks # 或者直接可视化几个标注框 python -c from ultralytics.data.utils import check_det_dataset check_det_dataset(data.yaml, autodownloadFalse) 可视化标注可以进一步沿用 YOLO 工具链里常用的plot_labelsTrue思路核心是确认框的尺寸分布是否合理如果大量框的高宽比都集中在 1:1检查是不是标注时只框了水面核心区而没包含细长水带这会影响模型对长条形积水的泛化。3.4 数据增强低对比场景下该改哪些参数YOLOv8 默认开启马赛克、随机仿射、HSV 扰动。对积水场景合理的增强策略和常规目标检测略有不同Mosaic 增强能模拟多块积水同时入画的场景保留默认开启但在最后 10 个 epoch 要设置close_mosaic10让模型从“拼图特征”过渡到真实分布积水对亮度极其敏感hsv_v默认 0.4 可以加大到 0.5模拟不同天气和阴影下的光照变化不建议开fliplr1.0以外的强翻转积水方向本身无语义左右翻转安全上下翻转会破坏地面透视关系别开加一点scale0.5让模型看到更小尺度的积水对应远处水面增强参数的调整要在训练命令里以超参数传入不要直接改库文件。原因很简单升级 ultralytics 版本时会覆盖掉本地改动而命令行参数是可复现的。4. 环境配置、训练命令与 results.csv 日志解读4.1 环境配置从 requirements.txt 到第一条训练命令项目里带了setup.cfg说明这个包支持可编辑安装。先创建干净的 Python 环境再按requirements.txt装依赖。依赖的核心是ultralytics、torch、torchvision和opencv-python训练可视化还需要tensorboard。装依赖时最容易出错的是 torch 和 CUDA 版本不匹配这个项目需要的核心版本组合可以直接抄# 创建虚拟环境并安装依赖Windows 直接用 conda create 也可以 python -m venv yolo_env source yolo_env/bin/activate # Windows 下执行 yolo_env\Scripts\activate pip install -r requirements.txt # 项目支持可编辑安装本地改代码后不需要重复安装 pip install -e . # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))最后一行输出True和显卡名才算环境就绪。torch.cuda.is_available()返回False时不要急着排查代码先看安装的 torch 是不是 CPU 版。常见组合是torch 2.0.1 CUDA 11.8或torch 2.1.0 CUDA 12.1注意requirements.txt里如果写的是torch2.0pip 默认会装 CPU 版需要手动指定 CUDA 版本安装。在 PyCharm 里使用时把项目解释器指到当前虚拟环境的 Python 路径yolo命令行和 Python API 就能共用同一个环境。GTX 1660 Ti 这类 6GB 显存的显卡跑 YOLOv8s 没有任何问题batch size 调到 8 即可。4.2 训练命令与参数选择数据集就绪、环境装好后训练命令非常短。关键是每个参数都要知道自己为什么这么设# 训练路面积水识别模型用 YOLOv8s 预训练权重做迁移学习 yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ optimizerSGD \ lr00.01 \ patience20 \ close_mosaic10 \ device0modelyolov8s.pt加载在 COCO 上预训练过的权重迁移学习能显著降低积水这种小数据集的训练难度。显存够就选yolov8syolov8n精度掉得明显yolov8m在 6GB 显存上会把 batch 压得很小epochs积水数据集通常几百张图100 轮足够收敛更早停在patience20触发早停batch按显存调节。16 是在 8~12GB 显存下的安全值6GB 卡改 8optimizerSGD数据量小的时候 SGD 比 AdamW 泛化更好虽然前期收敛慢但最终 mAP 更稳close_mosaic10最后 10 轮关闭马赛克增强让模型适应真实分布4.3 results.csv 字段与训练日志怎么看训练开始后项目目录下的runs/detect/train/会生成results.csv和events.out.tfevents前者每行一个 epoch 的关键指标后者给 TensorBoard 用。打开 TensorBoard 不需要额外配置tensorboard --logdir runs/detect/train浏览器打开http://localhost:6006就能看到损失曲线、mAP 曲线和模型结构图。events.out.tfevents*这个文件就是给这里读的不需要手动解析。results.csv的列名是固定的值得重点看的字段字段含义积水场景怎么看train/box_loss边框回归损失稳定下降则框回归正常train/cls_loss分类损失下降快说明类别可分性强train/dfl_loss分布损失若震荡大检查标注框质量metrics/precision(B)精确率关注高阈值下的表现metrics/recall(B)召回率漏检严重时这里会偏低metrics/mAP50(B)IoU0.5 平均精度主要参考指标积水任务 0.85 以上算可用metrics/mAP50-95(B)严格 IoU 平均精度框不精确时这个值会明显低于 mAP50积水目标的边缘天然模糊mAP50-95 比行人检测低 5~10 个点都是正常的不要因为 mAP50-95 不高就怀疑模型坏了。看训练是否收敛更直接的方法是手动画损失曲线import pandas as pd import matplotlib.pyplot as plt # 读取 ultralytics 训练时自动生成的指标记录 df pd.read_csv(runs/detect/train/results.csv) # 列名自带前后空格先清理再取数 df.columns df.columns.str.strip() plt.figure(figsize(10, 4)) # 只看 box_loss 和 dfl_loss分类损失在单类任务里参考价值低 plt.plot(df[train/box_loss], labelbox_loss) plt.plot(df[train/dfl_loss], labeldfl_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss_curve.png, dpi150)曲线如果一路平滑下降不再上升说明学习率设置合理如果 dfl_loss 在 40 轮后开始抖动说明框的边界在学习过程中不稳定优先检查标注框而不是调模型结构。4.4 训练完成后的模型选择runs/detect/train/weights/下有两个权重last.pt是最后一轮best.pt是验证集 mAP 最高的那轮。验证时用best.pt继续训练时用last.pt。两个文件都不要删前者用于断点续训后者用于部署和测试。5. 推理、导出与漏检优化从训练日志到 C 落地5.1 用 best.pt 做批量推理训练完成后第一时间在测试图上做推理注意和训练时用同一个imgsz# 对 test 目录下所有图片做推理置信度阈值 0.25 yolo detect predict modelruns/detect/train/weights/best.pt \ sourcedataset/images/test imgsz640 conf0.25 iou0.5# 用 Python API 做推理方便在代码里加后处理逻辑 from ultralytics import YOLO # 加载训练好的权重 model YOLO(runs/detect/train/weights/best.pt) results model(dataset/images/test/frame_001.jpg, conf0.25, iou0.5) # 遍历检测框过滤低置信度的小框 for box in results[0].boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf float(box.conf[0]) w, h x2 - x1, y2 - y1 # 面积小于 0.5% 图像大小的框大概率是误检 if w * h results[0].orig_shape[0] * results[0].orig_shape[1] * 0.005: continue print(fpuddle: conf{conf:.3f}, box({x1},{y1},{x2},{y2}))5.2 从 Python 到 C 部署项目里的main.cpp和inference.cpp是 C 侧的实现说明作者原始工程里已经考虑了 Python 之外的部署路径。常见做法是先把权重导出成 ONNX再用 ONNX Runtime 或 TensorRT 加载这样不依赖 ultralytics 运行时。YOLOv8 导出 ONNX 在命令行一行完成注意 opset 版本不能太低yolo export modelruns/detect/train/weights/best.pt formatonnx opset12 \ imgsz640 simplifyTrue// main.cpp 的典型片段加载引擎、读图、前处理、推理 // 实际工程里这里会封装一个 PuddleDetector 类 cv::Mat frame cv::imread(argv[1]); cv::Mat blob blobFromImage(frame, 1.0 / 255.0, cv::Size(640, 640), cv::Scalar(0, 0, 0), true, false); // 构造 onnxruntime 的输入输出执行 session.Run 拿输出张量 // 输出形状是 [1, 84, 8400]84 4 个框坐标 1 个置信度 80 个 COCO 类 // 自己训练的单类模型就要用 [1, 6, 8400] 来解析这是最常见的移植坑导出 ONNX 后类数量从 COCO 的 80 变成自己的 1 类输出张量的维度会变化C 代码里解析输出的地方必须同步改否则读出来全是乱码框。如果目标是香橙派这类边缘盒子还要进一步转成 TensorRT engine转的时候固定 batch size 和输入分辨率能明显提升推理速度。5.3 夜间和雨天积水漏检的针对性优化夜间积水反光弱模型容易漏检。我常用的优化手段分三层第一层训练时将 HSV 亮度扰动调大模拟夜晚路灯下的低照度环境第二层推理前对图像做 CLAHE 对比度增强让暗色水面和路面的灰度差变大第三层对置信度在 0.2~0.35 之间的候选框做二次判定利用积水先验——位于道路区域、宽高比在 1:2 到 3:1 之间、下方没有被其他框遮挡。这个阈值区间不要直接加到模型推理参数里而是在后处理环节单独处理。实测下来单纯调低 conf 会引入大量误检加上面积和位置约束后漏检率能降三成误检基本不增加。不同天气的数据混在一起训练会互相干扰更稳的做法是分场景各自训练再融合推理白天模型负责强光照夜晚模型负责低照度推理时根据图像平均亮度选择模型。这个思路在智慧交通项目里很实用也是个值得写进论文的改进点。本文还有配套的精品资源点击获取
返回列表