水下垃圾检测数据集:从数据采集到YOLO训练的全流程指南
简介面向水下垃圾自动检测任务这份YOLO算法海洋水下垃圾检测数据集提供真实场景拍摄的高质量jpg图像并配套由labelImg标注生成的VOC格式xml与YOLO格式txt标注文件可帮助研究者和开发者直接训练、验证目标检测模型省去手动采集与标注环节。数据覆盖金属、木材、塑料、橡胶、布料等多类水下垃圾场景丰富适合水下机器人、海洋环境监测等方向的项目实践。压缩包共2000个文件以jpg原始图像、xml标签和txt标签为主整体大小174.28MB解压后即可按需划分训练集与验证集。目前已有2083人学习或下载侧面反映其在实际项目中的参考价值。用户能同时获得VOC与YOLO两种格式的标注文件既便于熟悉经典标注流程也能直接接入YOLO系列模型训练在垃圾分类定位、水下目标识别等任务中快速验证算法效果。1. 先别急着调YOLO海洋水下垃圾检测数据集才是真正的瓶颈第一次把YOLO算法跑在海洋水下垃圾检测数据集上大多数人的第一反应是换更大的网络、调更久的训练轮次。但实际上水下垃圾检测项目里九成以上的翻车都发生在模型进训练之前——数据集的成像质量、标注规范和划分方式里早就写好了结果。这个标题覆盖的并不只是一份“图片标签”的打包下载而是一条从数据采集、格式转换、训练验收到部署回潜器上的完整链路。适合正在做海洋环保巡检、水下机器人视觉或者毕业设计课题的从业者你们拿到手的应该不只是一份权重而是一套能持续补充数据的工作流。我常跟搭档说花两周把数据集盘明白比花两周调参有用得多。2. 数据从哪来公开水下垃圾集与海试视频补样本水下垃圾检测的特殊性在于网络上随手能搜到的干净桌面垃圾图、河道垃圾图都不能直接用。海洋水下环境的成像劣化非常明显蓝绿色光衰减、悬浮颗粒散射、近距离拍摄带来的模糊都会让模型的输入分布和普通场景差一大截。所以数据集的选取逻辑只有一条贴近你实际部署水域的成像风格永远优先于图片数量和标注华丽的类别。2.1 候选数据源怎么挑成像是第一道门槛我一般会把可用数据源分成三类来评估而不是直接下载最大的那份打包。数据源类型典型特征适合场景主要风险泳池/水箱拍摄垃圾图背景干净、光照均匀、目标大跑通训练流程、验证代码与真实海域差异大迁移后掉点潜水器/ROV抓拍序列帧真实水体、有模糊和遮挡、类别杂作为训练主集标注质量参差需要重点清洗仿真合成/图像退化增强图对真实图片加浑浊、偏色、模糊扩充边缘场景、小样本类别增强过度会让模型学到错误的纹理选数据源时我会先抽 200 张真实看一遍而不是只看标注数量。重点看三件事目标是否肉眼可辨、图像是否严重过曝或全黑、标注框是否紧贴目标边缘。某公开水下垃圾数据集的类别名常常是英文缩写转换时要把plastic bottle、fishing net、metal can这类名称映射成一组统一的整数 ID避免后面训练时类别错位。另外不少数据集的标注框来自半自动工具贴在目标边缘外扩 10%-20% 是常态抽检时发现这类问题就要重新过一遍标注不然 YOLO 的默认 anchor 匹配会一直不稳定。2.2 自采视频抽帧与清洗把2小时ROV片段变成训练集公开数据集之外最可靠的数据来源是自己记录的海试视频。水下机器人缓慢巡航时相邻帧高度相似所以不能连续抽帧而是每秒抽 1 到 2 帧再用相似度过滤去掉冗余帧。ffmpeg -i rov_raw_001.mp4 -vf fps2,scale1280:720 -q:v 2 train_frames/%05d.jpg这条命令从rov_raw_001.mp4中按每秒 2 帧抽帧统一缩放到 1280x720-q:v 2控制 JPEG 质量数值越小画质越高建议在 2 到 5 之间。抽完帧之后再用 OpenCV 做一次感知哈希去重把余弦相似度高于 0.95 的相邻帧丢掉。import cv2 import numpy as np prev_hash None for img_path in sorted(frame_list): img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) small cv2.resize(gray, (16, 16)) # 用 8x8 区域的均值做感知哈希抗小幅位移干扰 cur_hash (small small.mean()).flatten() if prev_hash is not None: dist np.mean(cur_hash ! prev_hash) # 汉明距离 if dist 0.05: os.remove(img_path) # 超过95%像素一致视为重复帧 continue prev_hash cur_hash.copy()这里的关键参数是汉明距离阈值 0.05它表示 64 位哈希里只有不到 3 位不同才判定为重复。水下视频常有镜头缓慢平移阈值设太高会误删有效帧设太低又起不到去重作用。我习惯先跑一遍看剩余帧数再调阈值。2.3 把低质量帧识别出来模糊度与亮度过滤水下视频里有大量白茫茫、全黑或严重模糊的帧这些放进训练集只会让模型变成“蒙眼猜目标”。最实用的过滤方法是拉普拉斯方差它反映图像梯度的强弱模糊图的方差会很低。g cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2GRAY) fm cv2.Laplacian(g, cv2.CV_64F).var() if fm 80: # 经验阈值明亮水域可放低到60浑浊碎屑多时收到120 os.remove(img_path)阈值 80 是我在近岸浑水视频上的常用值如果画面本身有大量微小颗粒噪声方差会被抬高这时用 120 更合适。另外再叠加一个亮度判断计算灰度均值低于 20 的帧直接丢弃。这类帧占比高说明镜头被泥沙糊住或水体能见度极差不值得人工标注。3. 标注与格式转换数据集的上限在标注规范标注决定了模型的上限训练只是逼近这个上限。水下垃圾检测里同类目标不同形态差异极大塑料瓶有完整、压扁、半埋在泥沙里三种状态渔网更是皱成一团。标注时如果随意性太大模型会学到“只有这个固定形状才算垃圾”换一片水域就漏检。3.1 用VOC/JSON转YOLO txt的转换脚本与四个边界坑常见做法是拿到 Pascal VOC XML 或 Labelme JSON再转成 YOLO 需要的 txt。YOLO 的标签格式是每行class_id center_x center_y width height坐标归一化到 0 到 1。一个稳妥的转换脚本如下import os import cv2 import xml.etree.ElementTree as ET CLASS_MAP {plastic: 0, net: 1, metal: 2, glass: 3} def xml_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w, img_h int(size.find(width).text), int(size.find(height).text) # 读像素尺寸避免EXIF旋转导致xml里的宽高错位 img cv2.imread(os.path.join(img_dir, root.find(filename).text)) if img is not None: h, w img.shape[:2] if (w, h) ! (img_w, img_h): img_w, img_h w, h lines [] for obj in root.findall(object): cls obj.find(name).text.strip().lower() if cls not in CLASS_MAP: continue id_ CLASS_MAP[cls] box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 越界修正标注通常比物体大一圈 x1 max(0, min(img_w, x1)) y1 max(0, min(img_h, y1)) x2 max(0, min(img_w, x2)) y2 max(0, min(img_h, y2)) if x2 - x1 1 or y2 - y1 1: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w_n (x2 - x1) / img_w h_n (y2 - y1) / img_h lines.append(f{id_} {cx:.6f} {cy:.6f} {w_n:.6f} {h_n:.6f}) with open(os.path.join(out_dir, os.path.basename(xml_path)[:-4] .txt), w) as f: f.write(\n.join(lines))这个脚本里有四个最容易踩的坑。第一个坑是坐标越界标注软件偶尔会标出超出图像范围的框不做 clip 就会得到大于 1 的归一化值YOLO 读入时直接删除该目标导致标注白做。第二个坑是类别名大小写不一致脚本用strip().lower()归一化成小写再查表但如果你用的是Plastic和plastic混合命名必须在映射表里做兼容。第三个坑是空标注文件图片本来就没有目标时 txt 文件是空的不少人会删掉它我建议保留空 txt 并让图片作为背景负样本进入训练集但负样本比例控制在 10% 以内太多会让模型过度保守。第四个坑是 EXIF 旋转手机或某些相机拍的 JPG 会用 EXIF 信息旋转OpenCV 读取时自动旋转了但 XML 里的 width 和 height 还是原值必须像脚本里那样重新读像素尺寸覆盖。3.2 水下垃圾的类别定义与标注规则类别定义不宜一开始就做细。我见过某开发者把类别扩到 12 类包含“饮料罐-压扁”和“饮料罐-完整”结果每类样本量都不到 200模型在细分类别上互相混淆。更稳妥的方案是先从大类出发例如塑料瓶、渔网、金属罐、玻璃瓶、轮胎、复合垃圾共 6 类。数据量积累到每类上千张之后再把塑料细分出瓶和碎片。标注规则要提前写进规范。目标被遮挡超过 50% 时可以不标或统一标成“复合垃圾”但同批次必须保持一致。分辨率低于 16×16 像素的目标我建议放弃水下场景目标本来就小这类微小标注只会带来大量漏检噪音先把中小目标的数量拉上去更重要。还有一类特殊场景是目标半埋在沙泥里只露出一部分如果能在 3 秒视频里被肉眼认出来就标模棱两可就删掉该帧。标注完成之后一定要抽 10% 复标统计标注框是否紧贴目标、类别是否均匀这一步能省掉后面大量的训练排错时间。4. 训练YOLO水下模型的选型与必调参数很多人拿到数据集第一件事就是直接跑train.py但水下场景的成像分布特殊训练参数不能照搬 COCO 的默认组合。选模型时先问自己部署平台是什么边训练边想部署能省掉一整轮返工。水下巡检通常跑在小型嵌入式设备上浮点算力有限所以模型选型的优先级也是先算力后精度。4.1 轻量网络还是精度网络yolov5n与yolov8s怎么选我在项目中做过一组对比覆盖 YOLO 系列的轻量变体结论是部署端的推理延迟瓶颈常常在预处理而不是模型本身所以模型体积可以适当放宽但输入尺寸必须控制。模型典型体积适合算力场景判断v5系列 n 型约 4MB 权重边缘盒子、低功耗板目标大且数量少时够用v5系列 s 型约 14MB 权重中端GPU、机器人机载平衡点最常用v8系列 s 型约 22MB 权重中高端GPU有好的预训练精度略高但推理更慢水下垃圾检测的目标尺寸分布偏向中小目标v5s 是大多数项目的起点。如果后续发现渔网召回率不够再从 v5s 升到 v8s 或 m 型而不是一步到位选大模型。另一个常见误区是直接使用 COCO 预训练权重不做任何改动这些权重对陆地场景的纹理响应很高在首轮 fine-tune 时会出现 loss 快速下降但验证集 mAP 上不去的情况属正常现象多等几个 epoch 就好。4.2 先改anchor还是先改输入尺寸水下场景里锚框设计直接影响小目标召回。默认 anchor 是按 COCO 目标尺寸统计的水下垃圾的宽高比偏向长条形渔网和鱼线更是极端细长。我的建议是先用 KMeans 在训练集的目标框上重新聚类锚框。import numpy as np from sklearn.cluster import KMeans boxes [] # 从yolo txt里收集所有wh形状为 (N, 2) boxes np.array(boxes, dtypenp.float32) # 用相对尺寸聚类避免大目标主导 boxes[:, 0] / img_width boxes[:, 1] / img_height km KMeans(n_clusters9, random_state0).fit(boxes) anchors km.cluster_centers_ anchors anchors[anchors[:, 0] * anchors[:, 1] 1e-4] # 过滤过小的锚点聚类得到锚框后按照面积从小到大排序填入模型的 anchor 配置。参数上我一般把聚类数设成 9因为 YOLO 在三个检测头一共输出 9 个锚框。输入尺寸则优先选 640。水下目标不需要靠 1280 全图细节来识别640 能兼顾召回和边缘设备实时性很多开发者一上来就设成 1280结果推理直接掉到个位数帧率。4.3 yaml配置与bench训练命令训练之前先整理数据集目录写成 YAML 配置文件path: ./underwater train: images/train val: images/val nc: 6 names: [plastic, net, metal, glass, tire, mixed]train和val建议指向图片目录YOLO 会自动查找同名的 txt。类别顺序要在脚本所有模块里保持一致一旦训到一半再改顺序权重基本作废。基准训练命令如下python train.py --data underwater.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --cos-lr --hyp data/hyps/hyp.scratch-low.yaml这里--cos-lr使用余弦退火学习率在水下数据这种噪声大的场景里比线性衰减稳定hyp.scratch-low.yaml是低增强预设不会在一开始就把目标裁光。显存不够时先把--batch降到 8如果 loss 出现 NaN再去检查标注里有没有空的 txt 或者坐标超出范围的目标。一个比较隐蔽的现象是第一个 epoch 的 loss 几乎不变这往往不是因为模型没学而是 yaml 里类别数和标注类别数对不上模型把大部分预测都输给了空白类。启动训练后盯住前 20 个 epoch 的验证集 PR 曲线比只看 loss 曲线更容易发现这类问题。5. 避坑水下垃圾检测最容易翻车的5个场景水下数据集的坑不像陆地数据集那么明显出了问题也不容易复现。下面这 5 个场景来自我实际带过的项目每一条都是先看到现象、再定位到数据链条上的某个环节。5.1 现象训练集mAP上了0.85测试视频里却找不到易拉罐某开发者在一次近岸垃圾清理项目中把同一卷视频的前后帧直接随机划分进了训练集和验证集。因为相邻帧几乎一样模型在验证集上“见过了答案”mAP 虚高到 0.85。一拿到新录像检测率立刻掉到 0.3 以下。这个问题的本质是数据泄漏。解决方法是按视频片段划分数据把一条视频切成连续片段一个片段整体只进入训练集或验证集绝不做随机打散。同一片水域的不同趟位录像也要按趟位划分否则模型记住了某个具体场景的光照风格迁移性依然很差。5.2 现象模型把海底石头误判成“复合垃圾”某项目的训练集全部来自纯垃圾目标图像没有加入干净的海底背景。模型为了降低 loss开始学习用颜色和纹理猜测蓝绿色底的石头和沉积物被大量误报。解决方案是主动收集没有垃圾的大面积海底、沙地和礁石图像混入训练集负样本比例建议在 5% 到 10% 之间。加入之后误报会明显下降前提是这些背景图也要经过模糊度和亮度过滤保持与训练集一致的成像风格。5.3 现象loss前50轮一路下降50轮后震荡下不去水下小数据集上最常见的过拟合表现。前 50 轮模型还在学习大目标轮廓后 50 轮开始反复记忆标注噪声尤其是那些标得不准的模糊小目标。原因之一是高增强模式下 mosaic 把多个目标裁成碎片小目标被裁掉一半还能对得上语义标签但真实场景里不会出现这种形状。解决方法是把 mosaic 开启概率从 1.0 降到 0.5初始学习率从默认值降到 0.001再用余弦退火跑完剩余轮次。这个组合能明显稳住后期曲线。5.4 现象单帧推理显存顿卡部署端实时性不到15帧这是最容易被忽略的部署坑。很多人在训练时把输入尺寸设成 1280模型在测试集上精度确实高一些但换到边缘设备后预处理和推理都被卡住。水下机器人是移动平台功耗有限整体流程必须考虑预处理耗时。我给这类项目的建议是训练时就锁定推理阶段的输入尺寸和归一化方案用 640 输入训练和部署。推理时再叠加半精度权重和 tensorRT 导出可以把单帧时间从 200ms 压到 50ms 左右。关键是把图像缩放和归一化抠出来在预处理阶段完成不要留在模型内部。5.5 现象换了一个海域后检测率骤降水下模型的迁移性比陆地模型差得多因为不同海域的水体颜色、能见度和光照角度差异巨大。某开发者在一个港口水域训完模型转到外海礁石区就几乎失效。原因不是模型坏了而是训练集里只包含了一种色偏。解决方法是训练时加入颜色抖动和白平衡扰动模拟不同水体色温更有效的是每次外场作业后抽 2% 到 5% 的新素材用当前模型做预标注再人工复核后补充进训练集。这样持续迭代两三个航次模型才能真正适应新水域。6. 把数据集推向实战的验证阶梯与进阶技巧6.1 用PR曲线找召回短板训练结束后不要只看总的 mAP要逐类看 PR 曲线。我习惯把验证脚本输出按类别保存重点看召回率 R 低于 0.4 的类别。水下目标里渔网往往是重灾区因为形状不规则、纹理重复。针对这类目标单独收集更多该类图片比全局增加数据量更有效。如果某个类别 R 值低于 0.3还要往回查标注数量该类别少于 200 个目标时模型基本不可能学好。解决路径是先补该类数据、再重新训一轮而不是去调置信度阈值。6.2 用在线增强和伪标签迭代一版能下水的水下垃圾模型给自采视频跑一轮当前模型的推理把置信度高于 0.7 的检测框自动裁剪出来人工抽看一遍保留正确的并入训练集。伪标签阶段的关键是只接收高置信度且遮挡少的目标宁可少加也不要引入大量噪声。整套流程跑完之后再做一些水域自适应的在线增强例如把训练数据随机叠加蓝色色偏和雾化效果让模型不再依赖特定水色。最后提醒一个习惯每次外场作业回来先花半小时沉淀素材再补训练集而不是攒几个月一次性重训。某开发者曾经贪快不加背景样本就直接把模型装进水下机器人结果在礁石区狂报“复合垃圾”后来补到 8% 的负样本比例才压下来。希望帮到你。本文还有配套的精品资源点击获取