ARTICLE DETAIL

资讯详情

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

YOLOv8水下管道检测实战:数据集构建、模型训练与部署避坑指南

YOLOv8水下管道检测实战:数据集构建、模型训练与部署避坑指南 简介面向海洋工程与基础设施巡检场景的YOLOv8水下管道检测资源包主要用于识别和检测水下管道帮助运维人员掌握管道状态、维护海洋基础设施安全。包内包含七千九百七十一张已标注图像的目标检测数据集提供YOLO格式与VOC格式两种标签文件训练集、验证集和测试集已划分好并附有配套配置文件可直接用于YOLO系列算法的训练与推理还包含训练好的模型及使用教程便于快速上手。全包共两千个文件以一千九百八十五个xml标注文件为主另有十三个md说明文档以及yaml配置和txt标签文件各一个压缩后约八百七十八点四二MB目录结构清晰、命名规范适合需要数据集、模型和配套文档一体化方案的深度学习开发者。目前已有118人浏览学习。1. 水下管道检测为什么不能拿陆地模型硬套水下管道检测和普通目标检测最大的区别在于图像本身被水“处理过”了。蓝绿光衰减把颜色压成一个色系悬浮颗粒散射让边缘糊掉对比度低的地方目标可能只有几十个像素。直接拿陆地上训练的YOLOv8模型到水下场景常见结果是误检一堆、漏检一堆mAP在报告里很好看一到现场就翻车。标题里的ultralytics-yolov8-pred-underwater-pipes工程包解决的是“从哪儿起步”的问题它把水下管道的数据集、ultralytics训练配置和已经训练好的模型权重一起给到你能直接复现推理链路再基于自己的场景重训而不是从零攒数据。适合做ROV巡检、管道内部检测、水利结构评估这类工作的人参考。下面按拿到包→核对数据→重训→排坑→部署的顺序拆开讲。2. 从工程包拆出能用的东西场景定位与ultralytics框架选择2.1 水下管道巡检到底在检测什么目标水下管道检测最终交付的不是“有没有管道”而是“管道的健康状况”。常见检测目标分四类管道本体表面特征锈蚀、裂纹、焊接缺陷、附着物藤壶、海生物、泥沙覆盖、外部干扰渔网缠绕、锚链撞击痕迹、连接件法兰、螺栓、阀门接口。不同项目对精度要求差别很大附着物检测只需要框住位置焊接缺陷对框回归精度要求高差10个像素可能漏报。工程包里的类别定义通常围绕其中一到两类展开不需要盲目求多类别先看data.yaml里names有多少项再决定这包适不适合直接用。我拿到包先做的不是训练而是把data.yaml里每个类别对着标注样例看一遍确认它定义的是“管道在哪”而不是“缺陷是什么”。这直接决定后续重训成本。假如你要做的是焊缝裂纹检测但包里的标注只覆盖海生物附着那这个权重对你来说只能当预训练模型用不能直接交差。2.2 为什么用YOLOv8而不是Faster R-CNN或YOLOv5YOLOv8成为水下项目默认选择的理由不在“精度最高”而在主干统一和复现成本低。ultralytics框架把训练、验证、导出、推理都收在同一套命令行和Python API里环境搭建踩坑少后续部署也是同一套接口团队交接成本低。相比YOLOv5YOLOv8的C2f结构和anchor-free解耦头对多尺度目标更稳水下典型场景恰恰是大面积深色背景里埋着几个微小缺陷相比Faster R-CNN这类两阶段模型YOLOv8在CPU和嵌入式设备上都能跑到可用的帧率更适合巡检视频的离线后处理。选型时还得权衡算力。GPU资源紧张或者要跑CPU推理的场景优先选yolov8s或yolov8n不要一上来就挑x模型。水下数据集规模通常几百到几千张大模型在小数据集上收益有限反而更容易过拟合到训练集的光照条件。ultralytics安装也简单一套pip install ultralytics就能把训练、推理、导出命令都带上对做工程项目的人来说少一个自定义训练脚本就少一处维护成本。2.3 拿到包先核对三样东西数据、配置和权重unzip ultralytics-yolov8-pred-underwater-pipes*.zip -d underwater_pipes cd underwater_pipes find . -maxdepth 3 -type f | sort | head -60 du -sh datasets weights 2/dev/null先解压再看目录head -60把常见的weights、datasets、data.yaml都暴露出来。du -sh看数据量和权重体积正常包含数据集时总量在几十MB到几GB之间权重通常几MB到几十MB如果整个包只有几百KB基本只是代码骨架得自己补数据。接下来直接看data.yaml。cat datasets/data.yaml 2/dev/null || find . -name *.yaml -maxdepth 3 -exec cat {} \;data.yaml是训练和推理共用的主配置path字段通常是绝对路径换到别的机器后大概率第一件事就是改path。names顺序要重点看比如0: pipe1: corrosion2: marine_growth后面所有标注和推理都按这个顺序对齐。很多“模型不能用”其实是类别顺序错位不是模型训练失败。2.4 用训练好的best.pt跑通一次最小推理from ultralytics import YOLO import cv2 # 加载训练好的权重注意用绝对路径最稳 model YOLO(underwater_pipes/runs/detect/train/weights/best.pt) # 推理一张水下管道图 results model.predict( sourcesamples/pipeline_underwater.jpg, conf0.15, # 水下低对比度场景阈值先放低 iou0.45, imgsz640, saveTrue, devicecpu # 先排除CUDA环境问题 ) boxes results[0].boxes print(model.names) # 核对类别顺序 print(boxes.cls.cpu().numpy()) # 每个框的类别序号 print(boxes.conf.cpu().numpy()) # 每个框的置信度 print(boxes.xyxy.cpu().numpy()) # 每个框的像素坐标conf0.25在陆地场景合适水下先降到0.1到0.2避免因为对比度低导致框被后处理抑制掉。imgsz640是速度和精度的默认平衡点不满意再试768。如果包里只有last.pt没有best.pt也可以直接用last.pt跑毕竟上一轮权重也可能表现不错。如果推理结果全空多数不是模型坏了而是图像中目标相对整幅图太小。先用ROI把管道区域裁出来再预测或者把imgsz调到960看是否有响应。这一步跑通后整个链路才算立住。3. 把水下管道数据集整理成YOLO可用的格式三个来源和转换脚本3.1 水下图像的数据从哪来实拍抽帧、公开水下数据集和合成数据水下管道数据集的构建绕不开三个来源。实拍抽帧是主力ROV或潜水员拍摄的视频按每5到10秒抽一帧再筛掉模糊、曝光过度的帧尽量覆盖不同水深、不同流速、不同光照角度。公开水下目标检测数据集和水下机器人竞赛数据集可以做预训练来源但类别通常是海洋生物或水底目标和管道检测类别对不上直接拿来标注成本高适合先做特征提取。合成数据是补充手段用渲染软件生成管道模型再叠加水下光学衰减、散射模型能批量制造低概率出现的角度和姿态。强调一句像燃气管道图像数据集这类陆上公开数据可以当预训练来源不能替代水下数据。陆上管道的光照、背景、纹理衰减特性和水下完全不同混入训练集后模型会把蓝绿色调当成干扰信号反而降低水下精度。3.2 Labelme标注转YOLO格式的Python脚本Labelme保存的是JSON里的多边形像素坐标YOLO训练需要的是一行归一化坐标。转换脚本是绕不开的第一步。import json import os from glob import glob def labelme_to_yolo(json_path, save_dir, class_names): os.makedirs(save_dir, exist_okTrue) with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_name os.path.basename(json_path).replace(.json, .txt) out_lines [] for shape in data[shapes]: label shape[label] if label not in class_names: continue points shape[points] # 只取多边形的外接矩形对旋转框场景够用 xs [p[0] for p in points] ys [p[1] for p in points] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h out_lines.append(f{class_names.index(label)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(save_dir, txt_name), w) as f: f.write(\n.join(out_lines)) class_names [pipe, corrosion, marine_growth] # 必须和data.yaml顺序完全一致 for jf in glob(labels_json/*.json): labelme_to_yolo(jf, labels_txt, class_names)class_names的顺序决定txt文件第一列的数字一旦和data.yaml里names顺序错位训练出的模型推理时类别全是乱的。转换后随机抽几张图把txt里画出的框叠在原图上人工核对一遍这一步半小时能省后面半天的排查时间。3.3 训练集/验证集划分同源视频帧必须分开很多水下数据是从视频里抽帧来的相邻帧高度相似。如果划分时随机打散同一个视频的帧会同时出现在训练集和验证集验证mAP会虚高实际部署性能却差很多。import random import os import shutil seed 42 random.seed(seed) imgs sorted(os.listdir(images)) random.shuffle(imgs) n len(imgs) train_split imgs[:int(n * 0.7)] val_split imgs[int(n * 0.7):int(n * 0.85)] test_split imgs[int(n * 0.85):] for split, files in [(train, train_split), (val, val_split), (test, test_split)]: os.makedirs(fdataset/{split}/images, exist_okTrue) os.makedirs(fdataset/{split}/labels, exist_okTrue) for f in files: shutil.copy(fimages/{f}, fdataset/{split}/images/{f}) label f.rsplit(., 1)[0] .txt if os.path.exists(flabels_txt/{label}): shutil.copy(flabels_txt/{label}, fdataset/{split}/labels/{label})更严格的做法是按视频ID分组再划分也就是一个视频的所有帧只能进同一个集合。具体到代码就是把文件名里的视频编号解析出来按编号做分层采样而不是对单帧文件随机抽样。水下巡检视频动辄几万帧抽样能截到重复帧的概率极高这一步千万别省。3.4 增强水下图像的参数边界水下图像的增强不是越猛越好。YOLOv8自带的hsv增强、翻转、缩放都比较保守但针对水下特有的低对比度和雾状散射数据层的增强可以再补一档。import albumentations as A # 针对水下图像的补强重点模拟浑浊和色偏 aug A.Compose([ A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.2, p0.5), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit20, p0.5), A.RandomFog(fog_intensity_range(0.3, 0.6), p0.3), # 模拟悬浮颗粒散射 A.GaussNoise(var_limit(10.0, 50.0), p0.2), ])HueSaturationValue的色调偏移范围要小水下图像本身就偏蓝绿偏移过大容易把管道的颜色漂成紫红色。RandomFog是模拟浑浊水体的关键增强但fog_intensity_range到0.6以上会把目标彻底盖住模型学到的就不是特征而是噪声。GaussNoise幅度也不要过大水下图像的噪声本来就来自传感器加太多反而让模型去拟合噪声。3.5 训练前用三条规则检查数据集的“脏数据”训练前花十分钟做一次数据体检比训练完再回头看日志高效得多。from pathlib import Path for split in [train, val]: img_dir Path(fdataset/{split}/images) lbl_dir Path(fdataset/{split}/labels) for img in img_dir.glob(*): lbl lbl_dir / (img.stem .txt) if not lbl.exists(): print(fmissing label: {img}) elif lbl.stat().st_size 0: print(fempty label: {img})三条规则分别是每张图都有同名txt文件、txt文件不为空、txt里的坐标值都在0到1之间。YOLO遇到没有标注的图片不会报错但会在训练统计里把它当背景样本处理稀释正样本让你误以为数据集很大。坐标超过1通常是Labelme标注时框出了图像边界不修正会导致训练loss异常跳动。4. 重训水下管道模型data.yaml、训练参数和损失曲线的判读方法4.1 data.yaml里的names顺序决定后面所有判断沿用工程包里的data.yaml或自己重建一份。结构如下path: /home/user/underwater_pipes/dataset train: train/images val: val/images test: test/images names: 0: pipe 1: corrosion 2: marine_growthpath必须改成自己机器上的绝对路径。相对路径在某些版本下解析不彻底训练时报找不到图片的错误特别隐晦。names顺序和标注txt的数字序号一一对应训练脚本和后续推理脚本都读这份yaml不要在两个地方各写一份。4.2 一份可复现的YOLOv8训练命令与关键参数yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.005 \ optimizerauto \ device0 \ projectruns/detect \ nameunderwater_pipe_v1modelyolov8s.pt表示用官方预训练权重做迁移学习比从零训练收敛快很多。batch根据显存来16G显存跑yolov8s配batch16比较稳显存不够就先降到8。lr0在标准目标检测任务里常用0.01但水下图像对比度低、样本量小我会降到0.005避免前期loss震荡太厉害。optimizerauto让框架在AdamW和SGD之间自动选择大多数情况省心。关键参数如下表参数作用水下场景建议imgsz输入尺寸640起步小目标多时试768batch批大小8到16视显存而定lr0初始学习率0.005到0.01虚标高易震荡patience早停轮数50到100防止无效长训cache数据缓存显存和内存充足才用ram否则diskaugment内置增强默认开启hsv幅度可适当调低4.3 训练日志看什么loss、精确率和mAP50-95怎么一起看训练日志里每分钟刷新的是box_loss、cls_loss、dfl_loss三项每轮结束还有P、R、mAP50、mAP50-95。只看mAP50是新手最容易犯的错。水下管道的缺陷目标往往小且边缘模糊mAP50-95比mAP50低十几个点是很正常的现象低不代表模型废而是框回归精度还有提升空间。训练过程中发现best.pt保存在第97轮不代表后面到200轮一定更好。cls_loss持续下降而val_loss回升时说明模型开始记忆训练集的光照条件早停机制会在patience轮数内自动截断。4.4 用results.csv画损失函数曲线判断早停时机训练结束后runs/detect/underwater_pipe_v1/results.csv里存了每一轮的完整指标画出来比看终端日志直观得多。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/underwater_pipe_v1/results.csv) fig, axes plt.subplots(2, 2, figsize(12, 8)) df.plot(xepoch, ytrain/box_loss, axaxes[0][0], labeltrain box) df.plot(xepoch, yval/box_loss, axaxes[0][0], labelval box) df.plot(xepoch, ytrain/cls_loss, axaxes[0][1], labeltrain cls) df.plot(xepoch, yval/cls_loss, axaxes[0][1], labelval cls) df.plot(xepoch, ymetrics/mAP50(B), axaxes[1][0], labelmAP50) df.plot(xepoch, ymetrics/mAP50-95(B), axaxes[1][0], labelmAP50-95) plt.tight_layout() plt.savefig(loss_curves.png)判断规则很简单train loss继续降但val loss开始升过拟合了val mAP50-95连续几十轮原地不动继续训练没有意义。早停是性价比最高的时间管理方式水下数据本身有限硬跑满epoch只会加重过拟合。5. 水下管道YOLOv8的排查避坑五条实操踩坑记录5.1 加载best.pt后所有框都变成类别0现象用工程包里的训练权重推理图上所有检测框全部标成第一个类别第二类和第三类一个都出不来。原因data.yaml的names顺序和训练时不一致。比如训练时用的是0: pipe1: corrosion2: marine_growth推理时换了另一份yaml写成0: marine_growth1: corrosion2: pipe模型输出的类别序号和显示名称就全错位了。解决用训练时生成的runs/detect目录下的args.yaml里面的names是训练时的真实顺序。推理代码里用model.names直接打印当前模型实际认可的类别列表和各种文档对照。如果只有权重没有训练配置优先靠打印model.names确认而不是去翻不完整的yaml。5.2 训练到60轮mAP卡在0.3loss也在高位现象训练曲线平稳但mAP50始终在0.3附近loss停在1.7左右降不下去。原因水下图像整体偏蓝绿大面积背景主导了模型的特征统计模型在拟合背景色调而不是管道纹理。尤其深水区图像目标区域相对整幅图占比太小很容易被当成背景噪声。解决训练前先做数据层修正用CLAHE或白平衡把图像的对比度拉回来。常见做法是逐通道减去均值再拉伸让管道纹理的梯度更明显。再把imgsz从640提到768小目标受益明显。如果数据量小把增强里的hsv_h和hsv_s幅度调低避免颜色扰动干扰模型学习。这一套组合下来mAP上0.6是正常水平。5.3 验证集mAP50高实际视频却检不到框现象验证集mAP50高达0.88但把模型拿到现场录像上跑一条视频里几乎一个框都没有。原因验证集和训练集来自同一段离线视频的相邻帧数据划分时没有按视频ID分组发生隐性数据泄漏。验证mAP看到的是模型背题目的成绩现场光照、浑浊度、拍摄角度一变模型立刻打回原形。解决先检查划分脚本确认没有同源帧横跨训练集和验证集。再按来源视频重做验证集保证验证集只含模型没见过的场景。推理时把conf从0.25降到0.1水下场景宁可多检再过滤也不要漏检。mAP高只代表“验证集上表现好”不代表“现场能用”这两个概念要拆开看。5.4 batch改大后进程被Killed没有OOM报错现象batch从16提到32训练到中段进程直接被系统Killed终端没有任何CUDA OOM提示。原因Mosaic增强在每轮训练时要把一批图同时加载进内存再做拼接变换batch翻倍时宿主内存峰值远高于显存占用。如果再开了cacheram整份数据集全部驻留内存内存直接被打满系统就会杀进程。解决把cache参数从ram改成disk或者直接关掉。workers降到4避免数据加载线程抢占内存。batch退回16先确认训练流程完整再逐步往上探内存上限。这类问题不看nvidia-smi而要看free -h查的是宿主内存而不是显存。5.5 导出ONNX后在边缘设备上推理时间翻倍现象在RK3588这类边缘设备上部署时YOLOv8导出的ONNX模型单帧推理时间比预期慢一倍以上。原因ultralytics默认导出的ONNX可能是动态shape动态维度会触发某些推理引擎的fallback路径用非优化kernel运行。另外640×640的输入对水下巡检图像不一定必要整幅图全图推理浪费算力。解决导出时固定batch1和输入尺寸opset选12开启simplifyyolo export modelruns/detect/underwater_pipe_v1/weights/best.pt \ formatonnx imgsz640 batch1 opset12 simplifyTrue导出后先在onnxruntime里做一次CPU基准测试确认单帧耗时再进rknn或TensorRT流程。如果现场关注的是管道区域而不是全画面用ROI裁剪后再推理比缩小全图imgsz更划算。6. 部署前多做的三件小事视频流、ONNX基准测试和预标注循环6.1 用视频流推理生成带帧号的检测记录巡检视频不是单张图片输出需要按帧记录检测结果方便回放定位。from ultralytics import YOLO import cv2 import json model YOLO(best.pt) cap cv2.VideoCapture(underwater_pipe.mp4) frame_idx 0 results_all [] while True: ret, frame cap.read() if not ret: break # 每3帧推理一次巡检视频相邻帧变化很小 if frame_idx % 3 0: results model.predict(frame, conf0.2, imgsz640, devicecpu) cur [] for box in results[0].boxes: x1, y1, x2, y2 [round(v, 2) for v in box.xyxy[0].tolist()] conf round(float(box.conf[0]), 3) cls int(box.cls[0]) cur.append({frame: frame_idx, bbox: [x1, y1, x2, y2], conf: conf, class: cls}) results_all.append(cur) frame_idx 1 with open(detections.json, w) as f: json.dump(results_all, f)每3帧跳帧不是偷懒水下巡检视频中相邻帧高度相似全量推理的边际收益很低跳帧能让同样的算力覆盖更长的视频。conf取0.2是因为部署阶段漏检代价大于误检宁可多报候选框再由下游规则过滤。6.2 ONNX导出后先做一次CPU基准测试导出模型后先跑基准测试再决定要不要继续转rknn或TensorRT避免在嵌入式设备上反复调试。import onnxruntime as ort import numpy as np import time sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name input_shape sess.get_inputs()[0].shape x np.random.randn(1, 3, 640, 640).astype(np.float32) # 预热10次排除首次加载的冷启动影响 for _ in range(10): sess.run(None, {input_name: x}) t0 time.perf_counter() for _ in range(50): sess.run(None, {input_name: x}) t1 time.perf_counter() print(favg infer time {(t1 - t0) / 50 * 1000:.2f} ms)大图先裁剪ROI再推理比缩小整幅图更保真。在RK3588这类设备上rknn-toolkit2对算子的支持程度直接影响量化效果跑一遍目标检测实测比看支持矩阵靠谱。6.3 让模型帮你预标注省下重复劳动最实用的进阶技巧是用已经训练好的权重给新素材做预标注提高人工标注效率。对新采集的视频跑一次推理保留置信度在0.3到0.7之间的框导出成Labelme格式人工只需要修正边界和剔除误检而不是从零画框。这样每一轮新增数据都形成“模型提候选→人工复核→增量训练”的闭环越标越准。我现在拿到任何水下检测包第一件事也是先跑一遍低置信度推理把所有可能的目标找出来再决定是直接重训还是先补数据。训练前花时间做数据体检比训练完花几倍时间排查玄学问题有效得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表