☰
YOLO水下垃圾检测数据集构建与训练实战:从标注到部署避坑指南
2026/9/28 17:21:05 网站建设 项目流程

简介:这份资源面向计算机视觉学习者、水下目标检测研究者及需要真实场景数据训练YOLO模型的开发者,提供一套已标注完成的海洋水下垃圾检测数据集,可用于算法训练、模型对比与课程实验。压缩包共约2000个文件,包含7667张jpg真实场景图像、7667个xml格式VOC标签与7668个txt格式YOLO标签,两种标注格式分文件夹存放,整体约174.28MB,方便直接接入YOLO系列训练流程或转换为其他框架所需格式。数据覆盖metal、wood、plastic、rubber、cloth等多个海洋垃圾类别,场景丰富、目标多样,适合做多类别检测与泛化能力验证。目前已有2081人学习下载,读者可据此快速搭建训练与评估环境,省去从零采集和标注的成本,把精力集中在模型结构、数据增强与调参策略上,也便于复现和对比不同检测方案的效果。

1. 水下垃圾检测为什么总在浑浊场景翻车:从 YOLO 数据集说起

做海洋水下垃圾检测的团队,十有八九都经历过同一个场景:在实验室用公开数据集跑出 0.85 的 mAP,信心满满地丢进真实水域,结果模型把海草认成塑料袋、把鱼群认成漂浮瓶,召回率直接腰斩。问题往往不在 YOLO 算法本身,而在数据集——水下成像的色偏、悬浮颗粒、光照衰减,和陆地上那套 COCO、VOC 的分布差得太远。YOLO算法海洋水下垃圾检测数据集,本质上是为这类场景准备的一套「图像 + 标注 + 训练配置」的组合拳,让 YOLO 系列模型(v5/v8/v11 都行)能在水下这个特殊域里真正收敛。

它解决的核心问题是:把散落在各个水下机器人、ROV 拍摄素材、公开海洋监测影像里的垃圾目标,整理成 YOLO 能直接吃的格式,并给出针对水下成像特点的增强和调参策略。适合谁?做海洋环保监测的算法工程师、水下机器人视觉方向的在校研究生、以及想用 YOLO 快速验证水下检测可行性的开发者。如果你手上只有一堆没标注的潜水视频,或者拿 COCO 预训练权重直接下水,这篇内容能帮你少走至少两周弯路。

2. 水下垃圾数据集到底长什么样:类别、标注与格式拆解

2.1 水下垃圾的类别体系怎么定才不返工

陆地垃圾检测常用「可回收/有害/其他」这种粗分类,但水下场景完全不能照搬。我见过最惨的一次,团队按陆标定义了 6 类,结果标注到一半发现「塑料袋」和「塑料碎片」在水下根本分不清,返工重标了 3000 张。比较稳妥的做法是按材质 + 形态双维度定类,常见的水下垃圾检测类别体系如下:

类别名典型目标水下识别难点
plastic_bag塑料袋、保鲜膜半透明,与水体颜色接近
plastic_bottle塑料瓶、饮料瓶反光,易与气泡混淆
metal_can易拉罐、金属罐锈蚀后颜色与礁石接近
fishing_net废弃渔网、绳索细长结构,YOLO 小目标易漏
glass_bottle玻璃瓶折射导致边缘模糊
tire轮胎、橡胶制品体积大,但常被泥沙半掩
other_trash无法归类的杂物类内差异极大

类别数控制在 6~8 类是比较舒服的区间。太少区分度不够,太多单类样本不足,YOLO 的分类头会训崩。如果只做二分类「垃圾/非垃圾」,那另说,但实际落地项目基本都需要细分。

2.2 YOLO 格式标注:从原始标注到 labels 目录

YOLO 要求每张图对应一个同名.txt,每行格式是class_id x_center y_center width height,全部归一化到 0~1。如果你用 LabelImg 或 CVAT 标出来的是 VOC 的 XML,需要转一道。下面这个脚本是我常用的转换逻辑,处理了边界裁剪和空标注两个坑:

import os import xml.etree.ElementTree as ET # 类别映射,顺序必须和 data.yaml 里的 names 一致 CLASS_MAP = { "plastic_bag": 0, "plastic_bottle": 1, "metal_can": 2, "fishing_net": 3, "glass_bottle": 4, "tire": 5, "other_trash": 6 } def voc_to_yolo(xml_path, img_w, img_h, out_dir): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASS_MAP: continue # 未定义类别直接跳过,避免训练时报错 cls_id = CLASS_MAP[name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪到图像边界,水下标注常出现越界框 xmin, xmax = max(0, xmin), min(img_w, xmax) ymin, ymax = max(0, ymin), min(img_h, ymax) if xmax <= xmin or ymax <= ymin: continue # 裁剪后无效的框丢弃 x_c = (xmin + xmax) / 2.0 / img_w y_c = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}") if not lines: return # 空标注不生成文件,否则 YOLO 会当负样本 out_path = os.path.join(out_dir, os.path.basename(xml_path).replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))

逻辑说明:先做类别映射,未定义类别直接跳过而不是报错,这在多人协作标注时很关键。边界裁剪那两行是血泪经验——水下目标经常贴着画面边缘,标注员手一抖框就出界了,不裁剪的话归一化坐标会大于 1,YOLO 训练时直接报normalized coordinates outside [0,1]。空标注不生成文件,是因为 YOLO 把没有对应 txt 的图片当纯背景负样本处理,如果你确实想要负样本,应该生成一个空 txt 文件而不是不生成。

参数上,CLASS_MAP的 value 必须从 0 连续编号,中间断了 YOLO 会报类别数不匹配。img_w和img_h要读原图真实尺寸,别用标注工具里显示的缩放后尺寸。

2.3 data.yaml 与目录结构:一次配对,终身受用

YOLOv8 之后统一用data.yaml描述数据集,目录结构建议这样组织:

underwater_trash/ ├── images/ │ ├── train/ # 约 70% │ ├── val/ # 约 20% │ └── test/ # 约 10% ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

对应的data.yaml:

path: /data/underwater_trash train: images/train val: images/val test: images/test nc: 7 names: ['plastic_bag', 'plastic_bottle', 'metal_can', 'fishing_net', 'glass_bottle', 'tire', 'other_trash']

注意names的顺序必须和转换脚本里的CLASS_MAP完全一致,错一位整个训练结果的混淆矩阵就全乱了。划分比例上,水下数据如果总量少于 3000 张,建议 train:val:test 用 8:1:1,验证集太小评估波动会很大。

3. 用 YOLOv8 在水下垃圾数据集上跑通第一个基线

3.1 环境与预训练权重:别从零训

水下数据集通常不大,从零初始化训练基本没戏。常见做法是加载 COCO 预训练权重做迁移学习。YOLOv8 的加载方式很直接:

pip install ultralytics # 训练时指定预训练权重,ultralytics 会自动下载 yolo detect train model=yolov8s.pt data=data.yaml epochs=100 imgsz=640 batch=16

yolov8s.pt是 small 版本,参数量约 11M,在水下这种中等难度任务上性价比最高。n 版太浅,对细长渔网这种小目标欠拟合;m/l 版在数据量不足 5000 张时容易过拟合。imgsz=640是默认值,但如果你的原始图像是 4K 水下相机拍的,建议先缩到 1280 再训,直接 640 会丢太多小目标细节。

3.2 水下专属的数据增强配置

YOLO 默认的 HSV 增强是为陆地设计的,水下图像本身色偏严重,直接套用会加剧颜色失真。我一般会自定义一个增强配置,重点调整色调和饱和度扰动范围:

from ultralytics import YOLO model = YOLO("yolov8s.pt") model.train( data="data.yaml", epochs=100, imgsz=640, batch=16, hsv_h=0.010, # 默认 0.015,水下降低色调扰动 hsv_s=0.500, # 默认 0.7,饱和度扰动也收窄 hsv_v=0.400, # 亮度保持默认附近,水下亮度变化本就大 degrees=10.0, # 旋转角度,水下目标朝向随机 translate=0.1, scale=0.5, fliplr=0.5, # 水平翻转,水下左右对称场景可用 mosaic=1.0, # 马赛克增强,小目标友好 mixup=0.1, # 混合增强,别开太高否则水下目标更糊 patience=20, # 20 轮无提升就早停 device=0 )

参数说明:hsv_h调到 0.01 是因为水下图像整体偏蓝绿,色调扰动太大会让模型学到错误的颜色先验。hsv_s降到 0.5 同理,水下饱和度本身就被水体吸收削弱了。mosaic=1.0保持开启,它对小目标检测提升明显,废弃渔网这种细长目标受益最大。mixup只开 0.1,开高了水下图像叠加后更浑浊,模型反而学不到清晰特征。

3.3 训练过程看什么:loss 曲线与 mAP 的读法

训练启动后,runs/detect/train/下会生成results.csv和一堆曲线图。重点盯三个信号:

第一,box_loss和cls_loss是否同步下降。如果 cls_loss 震荡不降,多半是类别不平衡,某个类别样本太少。第二,mAP50在前 10 轮应该快速爬升到 0.3 以上,如果 10 轮还在 0.1 徘徊,检查标注格式或 data.yaml 路径。第三,验证集 loss 如果连续 15 轮上升而训练 loss 还在降,就是过拟合了,该早停或加增强。

有个玄学现象:水下数据集的 mAP 曲线经常在 60~80 轮之间突然跳一下,这是因为 mosaic 增强在最后 10 轮默认关闭,模型开始适应真实分布。所以别看到中间平台期就手动停,让它跑完。

4. 水下检测的坑:从标注到部署的 5 个翻车现场

4.1 坑一:把气泡标成塑料瓶

现象:模型在验证集上 plastic_bottle 的精确率很高,但一放到有大量气泡的视频里就疯狂误检。原因:标注阶段标注员把成串气泡框成了塑料瓶,因为水下气泡和半透明瓶体在低分辨率下确实像。解决:标注规范里明确写「气泡不标」,并在标注工具里放大到 200% 再判断;已经标错的,用置信度阈值 0.5 以上的预测结果反向筛查,人工复核高置信度误检样本。

4.2 坑二:类别编号不连续导致训练直接崩

现象:训练启动几秒后报AssertionError: nc mismatch或IndexError: index out of range。原因:data.yaml里nc: 7但标注文件里出现了 class_id 7 甚至 8,通常是转换脚本的 CLASS_MAP 和 yaml 的 names 顺序不一致。解决:写一个校验脚本,遍历所有 txt 文件统计 class_id 的最大值,和 nc 对比:

import glob max_id = -1 for txt in glob.glob("labels/**/*.txt", recursive=True): with open(txt) as f: for line in f: if line.strip(): max_id = max(max_id, int(line.split()[0])) print(f"max class_id = {max_id}, 请确保 nc >= {max_id + 1}")

4.3 坑三:验证集 mAP 虚高,测试集一塌糊涂

现象:val mAP50 到 0.82,test 只有 0.51。原因:划分数据时按视频帧随机切分,同一段视频的相邻帧同时进了 train 和 val,模型其实在「背」场景。解决:按视频源或拍摄批次划分,同一段视频的帧只能进同一个集合。如果数据来自多个 ROV 任务,按任务 ID 分组划分最稳。

4.4 坑四:小目标渔网漏检严重

现象:plastic_bag 和 metal_can 召回率 0.8+,fishing_net 只有 0.3。原因:渔网是细长结构,在 640 分辨率下经过多次下采样后特征几乎消失。解决:把imgsz提到 960 或 1280,同时在data.yaml同级加一个anchors自定义配置(YOLOv8 已改为自适应锚框,但可以通过增大imgsz间接改善);另一个办法是在训练时对含 fishing_net 的样本做 2 倍过采样。

4.5 坑五:BN 层在水下小批量训练时崩溃

现象:训练几十轮后 loss 突然变 NaN,报BN momentum相关警告。原因:batch size 太小(比如 4 或 8),水下图像分布又和预训练分布差异大,BN 统计量估计不准。解决:把 batch 提到 16 以上,显存不够就降 imgsz 到 512;或者改用yolov8s.pt时冻结前 10 层 BN,只训检测头。这个坑在 V100 上跑大模型时反而少见,因为 batch 能开大。

5. 把 mAP 再往上推 5 个点:三个我反复验证过的技巧

5.1 用测试时增强(TTA)榨干最后一点精度

YOLOv8 内置了 TTA,推理时加augment=True即可。它会对每张图做多尺度 + 翻转推理再融合结果,代价是推理速度慢 2~3 倍,但 mAP 通常能涨 1~3 个点。水下场景尤其适合,因为水体折射导致目标在不同尺度下特征差异大,多尺度融合能互补。

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="test_images/", augment=True, # 开启 TTA conf=0.25, iou=0.45, imgsz=960, save=True )

conf=0.25是水下场景比较稳的阈值,低于 0.2 误检会明显增多。iou=0.45用于 NMS,渔网这种重叠目标多的情况可以调到 0.5 减少漏检。

5.2 类别不平衡用 copy-paste 增强补样本

如果某个类别(比如 glass_bottle)只有 200 张,别硬训。用 copy-paste 把该类别目标抠出来,随机贴到其他水下背景图上,能快速扩充到 800~1000 张。注意粘贴时要匹配水下光照,直接贴会显得突兀。我一般会做一次高斯模糊 + 颜色迁移,让粘贴区域和背景色调一致。这个技巧在 YOLO 官方文档里没写,但在水下这种长尾分布场景里效果拔群。

5.3 验证时别只看 mAP,混淆矩阵才是排错利器

YOLO 训练完会在runs/detect/train/下生成confusion_matrix.png。水下检测最常出现的混淆是 plastic_bag ↔ fishing_net 和 metal_can ↔ tire。前者是因为都是半透明或细长,后者是因为锈蚀后颜色接近。看到混淆矩阵里某两个类互相误判严重,优先补这两类的区分性样本,比盲目加数据有效得多。

最后说个我自己的习惯:每次训完模型,我都会挑 20 张验证集里预测错的图,逐张看是标注错了、目标太小、还是真的长得像。十次里有六次是标注问题。水下数据集的质量,比换什么模型、调什么参数都重要。希望帮到你。

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

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

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

立即咨询