简介:这份面向海洋垃圾检测的已标注数据集,基于Trash-ICRA19竞赛内容整理,专供计算机视觉学习者和目标检测项目开发者直接使用;压缩包共2288个文件,其中1144张jpg原始图像与1144个xml标注文件逐一对应,整体仅26.21MB,轻量且内容完整。所有xml标注均按标准格式组织,能够直接导入YOLO训练流程,省去自行标注和格式转换的繁复步骤,下载后即可开展模型训练、验证与测试。适用对象包括计算机、电子信息工程、数学等专业的大学生,可支撑课程设计、期末大作业和毕业设计中的目标检测实验,也可用于海洋垃圾识别、水面漂浮物检测等具体课题;当前已有259人学习下载,标注覆盖了多种水下常见物体,便于比对不同类别检测效果。作者为资深算法工程师,文件命名规范、目录清晰,能明显提升实验准备效率。
1. 找水下垃圾检测数据集?这份已标注的1144张Trash-ICRA19可以直接开训
做水下机器人目标检测,模型选型从来不是第一步,数据才是。我们组之前做海洋打捞项目,光标注一版水下视频就耗了两周,后来发现网上现成的海洋图像不是没标注,就是标注格式五花八门。这份Trash-ICRA19 Dataset属于ICRA 2019公开的水下垃圾检测数据集,整理好了1144张真实ROV拍摄图像和对应的VOC格式xml标注,文件名直接按obj(人造垃圾)和bio(海洋生物)分组,拿到手能直接转成YOLO训练格式。适合作课程设计、期末大作业和毕业设计,也适合想快速验证水下目标检测方案的从业者。接下来要做的很明确:把xml转成YOLO标签、配data.yaml、训练、调阈值,四步走。
2. Trash-ICRA19 摸底:文件命名、标注格式和一个能直接开训的YOLO训练集
2.1 这份数据集是什么来路
Trash-ICRA19 Dataset是ICRA 2019机器人会议上发布的水下垃圾检测数据集,图像来自水下机器人(ROV)在真实海域拍摄的视频帧。和网上随手抓的“海洋壁纸”不同,每一帧都是ROV实际作业时看到的画面:阳光从水面斜射下来,悬浮颗粒让背景发白,塑料袋和渔网半埋在沙里。这类图像直接拿COCO预训练模型去检测效果很差,因为COCO里没有“水下垃圾”这种语义,而且水下图像普遍存在光衰减、颜色偏蓝、对比度低的问题,陆上检测的那套数据增强思路在水下常常失灵。
数据集本身的标注是PASCAL VOC格式,每张jpg对应一个同名的xml文件。xml里记录了图像尺寸、目标类别和边界框坐标。用YOLO训练前,必须先把xml转换成YOLO要求的txt格式,这个转换流程下文给出可直接跑的脚本。这份资源最大的价值在于省掉了人工标注,1144张图带框直接可用,在几百张规模的数据集上做迁移学习,已经能训出一个能work的水下垃圾检测模型。
2.2 目录结构和文件命名规则
下载解压后,目录结构一般是这样:
Trash-ICRA19/ ├── images/ │ ├── obj0871_frame0000120.jpg │ ├── bio0003_frame0000185.jpg │ └── ... └── annotations/ ├── obj0871_frame0000120.xml ├── bio0003_frame0000185.xml └── ...文件名本身有信息量。obj前缀代表object,对应人造垃圾;bio前缀代表biological,即海洋生物。后面的数字是视频编号和帧号,比如obj0871_frame0000120就是第871段视频的第120帧。项目正文列出的那些文件名,恰好覆盖了obj和bio两个大类的样本。
提示:obj和bio只是文件命名的顶层分组,具体类别名要扫描所有xml里的name字段确认,不同来源的Trash-ICRA19转存版本,类别标签可能有差异。
2.3 XML标注怎么看
随便打开一个xml,结构基本是:
<annotation> <filename>obj0871_frame0000120.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>obj</name> <bndbox> <xmin>356</xmin> <ymin>240</ymin> <xmax>780</xmax> <ymax>620</ymax> </bndbox> </object> </annotation>size节点是图像宽高,object节点里name是类别名,bndbox是矩形框的四个角点坐标。YOLO训练需要的是归一化的中心点坐标和宽高,所以转换脚本要做三件事:解析xml、读取size做归一化、写成txt。另外,这份数据集里某些标注可能是polygon多边形,转换时要做外接矩形处理,下文脚本里我直接加了兼容逻辑。
2.4 训练集怎么划分
总共1144张图,规模不大,建议按8:2划分train和val,不需要单独划test。数据量少的时候test可以复用val,这样得到的mAP趋势更平稳,也方便对比每次训练的改进效果。划分时按视频编号分层,尽量避免同一段视频的连续帧同时出现在train和val里,否则会高估泛化性能。
3. VOC 转 YOLO:转换脚本与最容易翻车的四个细节
3.1 转换前先把标注扫一遍
不要急着写转换代码,先统计一下所有xml里的类别名。见过太多人直接硬编码一个类别表,结果xml里出现了没见过的类,训练时报错或者类别错位。扫描脚本:
import xml.etree.ElementTree as ET from pathlib import Path annot_dir = Path("Trash-ICRA19/annotations") class_counter = {} for xml_path in sorted(annot_dir.glob("*.xml")): tree = ET.parse(xml_path) root = tree.getroot() for obj in root.iter("object"): name = obj.find("name").text.strip() class_counter[name] = class_counter.get(name, 0) + 1 print(class_counter)这段代码遍历所有xml,统计每个类别的框数量。strip()很关键,能去掉xml里多余的换行和空格,避免“obj ”和“obj”被当成两个类。这一步输出的结果就是你data.yaml里names的基准。
3.2 转换脚本:兼容bndbox和polygon
import xml.etree.ElementTree as ET from pathlib import Path IMAGE_DIR = Path("Trash-ICRA19/images") ANNOT_DIR = Path("Trash-ICRA19/annotations") LABEL_DIR = Path("Trash-ICRA19/labels") LABEL_DIR.mkdir(exist_ok=True) # 根据上一步扫描结果按需修改 CLASS_NAMES = ["obj", "bio"] def voc_to_yolo(xml_file, label_file): tree = ET.parse(xml_file) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in CLASS_NAMES: continue cls_id = CLASS_NAMES.index(name) bndbox = obj.find("bndbox") if bndbox is not None: xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) else: # 部分标注是polygon,转外接矩形 polygon = obj.find("polygon") xs, ys = [], [] for pt in polygon.findall("pt"): xs.append(float(pt.find("x").text)) ys.append(float(pt.find("y").text)) xmin, xmax = min(xs), max(xs) ymin, ymax = min(ys), max(ys) # 防止边界溢出 xmin = max(0, xmin); ymin = max(0, ymin) xmax = min(img_w, xmax); ymax = min(img_h, ymax) # 归一化到0~1 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") label_file.write_text("\n".join(lines), encoding="utf-8") for xml_path in sorted(ANNOT_DIR.glob("*.xml")): label_path = LABEL_DIR / (xml_path.stem + ".txt") voc_to_yolo(xml_path, label_path)脚本逻辑分四块:解析size拿到图像宽高;遍历object节点按类别名映射id;兼容bndbox矩形框和polygon多边形;最后做归一化并写入txt。参数说明:CLASS_NAMES顺序决定txt里第一列的数字编号,后续data.yaml里的names必须和这里保持完全一致;img_w和img_h用的是xml里的size,如果发现坐标普遍偏移,改用cv2实际读取图像尺寸。
3.3 划分训练集
转换完成后,需要生成train.txt和val.txt。不需要把图像路径写进txt,按ultralytics的目录约定来就行:
mkdir -p Trash-ICRA19/images/train Trash-ICRA19/images/val mkdir -p Trash-ICRA19/labels/train Trash-ICRA19/labels/val把对应图片和标签按8:2比例移动过去,保证同名jpg和txt在相同的train/val子目录下。这一步可以用脚本做,但手动复制时务必检查是否一一对应,漏一张图后面训练时就是一条warning。
3.4 转换后的自检
转换完别急着开训。先随机抽几张图,把txt框画回去看一眼:
import cv2 from ultralytics.utils.plotting import Annotator img = cv2.imread("Trash-ICRA19/images/train/obj0871_frame0000120.jpg") h, w = img.shape[:2] ann = Annotator(img.copy()) for line in open("Trash-ICRA19/labels/train/obj0871_frame0000120.txt"): cls_id, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) ann.box_label([x1, y1, x2, y2], f"{int(cls_id)}") cv2.imshow("check", ann.result()) cv2.waitKey(0)这一步能同时暴露两个问题:坐标是否归一化错误、类别id和名字是否对应。我一般会抽查20张以上,覆盖白天、夜间、浑浊水体等不同场景,再决定要不要进入训练。
4. 训练配置与损失函数调优:三个让 mAP 明显变化的参数
4.1 data.yaml 的写法
用ultralytics框架训练YOLOv8,data.yaml是第一个要写对的配置文件:
path: /home/yourname/Trash-ICRA19 train: images/train val: images/val names: 0: obj 1: biopath填数据集的绝对路径,train和val填相对于path的目录。names的类和顺序必须和第三章转换脚本里的CLASS_NAMES完全一致,这个顺序错了模型直接学错。如果扫描后发现类别超过两个,按实际扫描结果逐个列出来。
4.2 训练命令与超参
环境准备和训练命令:
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ device=0model=yolov8s.pt会自动下载COCO预训练权重,在这个基础上做迁移学习,收敛速度比从零训练快很多。如果显存只有6G,batch降到8,imgsz降到544。水下小目标多,imgsz低于512时塑料瓶那种小物体基本就丢了,不建议再往下压。
超参数取舍,按优先级排序:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| imgsz | 640或768 | 提升小目标召回最直接的手段 |
| epochs | 100~150 | 小数据集150轮足够,配合早停 |
| batch | 16或8 | 由显存决定,影响BN统计量 |
| lr0 | 0.01默认 | 迁移学习时0.01偏保守,够用 |
| patience | 20 | 连续20轮mAP不涨就停 |
4.3 损失函数和训练日志怎么对应
YOLOv8的损失函数由三部分组成:box_loss(边框回归,用的是CIoU加DFL)、cls_loss(分类,二值交叉熵)、dfl_loss(分布焦点损失,控制框边界的置信度分布)。训练日志里三个loss分别输出,看趋势比看绝对值有意义:
- box_loss稳步下降,说明模型在学习框的位置和大小;
- cls_loss下降缓慢甚至震荡,说明分类难学,水下生物和人造垃圾在视觉上确实有相似纹理,这种情况可以把分类损失权重加大,在ultralytics里对应cls=0.7之类的参数;
- dfl_loss过高,往往和标注框质量有关,polygon转矩形时外接框带进了太多背景。
评价指标方面,mAP50是IoU阈值0.5下的平均精度,mAP50-95是把IoU从0.5到0.95按0.05步进取平均。水下垃圾检测场景,mAP50上80就算能用,mAP50-95和mAP50差距如果超过15个点,说明框的位置精度不够,优先怀疑标注框质量问题而不是模型能力。
5. 水下图像常见问题排查:五个绕不开的坑
5.1 训练时报“不存在标签”或标签数组为空
现象:训练启动阶段大量warning,日志里出现“image ... has no labels”,loss一直是nan。
原因:xml里某些标注是polygon节点,而脚本只解析了bndbox,导致这些图片生成的txt是空文件。Trash-ICRA19某些转存版本的标注并不统一。
解决:在转换脚本里加polygon分支,把多边形的所有顶点取最小外接矩形。我给的脚本已经包含这个逻辑,但如果你是从别的来源拿到xml,先跑一下扫描脚本,确认没有漏掉节点类型。
5.2 类别表错乱,两类变四类
现象:训练完了,混淆矩阵里出现一堆看不懂的类别,或者同一个框被预测成两个名字。
原因:xml的name字段里有换行符或前后空格,“obj”和“obj ”被当成了两个独立类别。这类问题转换阶段不会报错,要到训练完画混淆矩阵才发现。
解决:所有name字段统一strip();类别表只从扫描脚本的输出生成,不要手工敲。如果发现训练完的类别数比预期多,回头用第一节的扫描脚本重跑一遍,把带空格的key合并掉。
5.3 框偏了,而且只在部分图上偏
现象:自检画框时,某些图的框位置明显偏离目标,另一些正常。
原因:xml里的size和图像实际尺寸不一致。某些视频帧经过裁剪或缩放后没有同步更新xml里的width和height,归一化按错误的分母算,框自然偏。
解决:转换脚本里不信任xml的size,改用cv2.imread实际读取图像宽高。代价是多读一遍图,但换来的是坐标可靠。从那以后我所有数据集转换都强制走这个逻辑,不嫌慢。
5.4 小目标几乎全部漏检,mAP50被拖垮
现象:mAP50在0.4左右徘徊,统计一下发现漏检的全是小塑料瓶、小渔网团,大目标都框得住。
原因:水下图像里小目标占比高,而imgsz=640时,一个几十像素的塑料瓶在特征图上的响应本来就弱。再加上水下对比度低,小目标的特征更加不显著。
解决:imgsz提到768,显存够用就上;开启mosaic增强,YOLOv8默认开启,不用额外配置;如果还是漏,考虑用SAHI这类切片推理工具,在推理阶段把大图切成小块分别检测再合并,对水下小目标提升非常明显。
5.5 岩石被识别成垃圾,置信度调来调去都压不住
现象:检测结果里海底岩石、珊瑚礁被频繁框成“生物”或“垃圾”,调高conf阈值又会把真目标一起干掉。
原因:水下背景纹理复杂,岩石上的附着物和垃圾在外观上高度相似,模型学到的是纹理特征而不是语义特征。数据量少时,误检几乎是必然的。
解决:从训练集里截取纯背景区域做成负样本,配合训练时不开hsv_augment的强颜色扰动;推理时conf阈值设在0.35~0.5之间,宁缺毋滥。更实际的做法是加一个background类别,把典型误检样本框进去,让模型显式学习背景长什么样。
6. 从权重到实测:验证指标、ONNX 导出的最后一公里
6.1 验证集上到底该看哪几个数
训练结束后不要只盯着best.pt和最后的mAP。先跑一遍验证:
yolo detect val model=runs/detect/train/weights/best.pt data=data.yaml重点看四块:mAP50、mAP50-95、每类AP、混淆矩阵。每类AP能暴露哪一类拖后腿,混淆矩阵能看出哪些类互相混淆。水下垃圾模型常见的陷阱是总体mAP不错,但bio这一类AP只有30不到,因为生物目标运动模糊更严重。
6.2 导出 ONNX,换个推理框架也不慌
PyTorch权重只能在Python环境里跑,部署到机器人上一般要导出ONNX:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640导出后用onnxruntime做一个20行内的推理脚本,验证导出前后结果一致。这里有个小技巧:导出时用dynamic=True保持batch维度动态,这样同一份ONNX既能单帧推理又能批量推理,不用重复导。
6.3 多路视频输入时的常识判断
机器人上接了多个水下摄像头时,加载模型推理不是瓶颈,瓶颈在图像解码。常见做法是每路视频单独开一个线程做拉流和预处理,检测部分共享同一份模型实例。如果用的是TensorRT加速,640分辨率下单张耗时在几毫秒量级,25帧每秒一路很轻松,多路主要看显存和推理队列是否打满。
我自己的习惯是部署前先用一个固定阈值跑一遍训练集外的实际视频,统计误检率和漏检率,再决定置信度阈值。训练指标好不代表水下场景好用,这点吃过亏之后就长记性了。从那以后我每次训完模型,都会强制把验证画框结果截图保存一份,和下一次训练做对比,这样模型是变好了还是变坏了,一眼就能看出来。希望帮到你。
本文还有配套的精品资源,点击获取