简介:面向YOLO目标检测与海洋垃圾识别任务,Trash-ICRA19 Dataset提供了1144张真实海洋场景图像及一一对应的XML标注文件,标注框完整,适合计算机视觉学习者、高校学生以及目标检测研究者直接用于模型训练、评估,或用于课程设计、期末大作业和毕业设计实验。整个RAR压缩包共2288个文件,其中1144个JPG图像和1144个XML标注文件各占一半,压缩后仅26.21MB,轻量易保存。目前已有259人学习/下载,说明该数据在目标检测项目中具有一定参考价值。由于标注信息完整,可直接对接YOLOv5、YOLOv8等主流框架,使用者可集中精力于模型调优与效果改进;文件命名规范,便于批量处理与数据集划分,小巧体积也适合本地快速迭代,尤其能为需要真实数据集支撑的本科毕业设计、期末大作业提供便利。
1. YOLO目标检测遇上Trash-ICRA19:1144张海洋垃圾标注图直接能用
做水下目标检测的人大概都经历过同一个噩梦:在昏暗的深水视频里一帧一帧画框。画到第800张的时候,眼睛看什么都是带边框的。如果你正在做毕业设计或者课程设计,主题恰好是YOLO目标检测、海洋垃圾识别,那手头这份Trash-ICRA19 Dataset标注包就是来救命的——1144张图像全部来自真实水下视频拆帧,每一张都带对应的XML标注文件,类别、坐标、图像尺寸都写好了,解压就能用来训练YOLOv5/YOLOv8。它不是那种网上流传的残缺版本,而是整理过、可以直接进训练流程的完整标注集。适合三类人:赶毕设节点、需要真实数据支撑论文的本科生,打算用海洋目标检测练手的算法入门者,以及想快速验证YOLO训练管线是否跑通、不想在标注环节浪费时间的研究生。
2. 数据和标注格式:先把Trash-ICRA19的底细摸清
2.1 文件名里的三个前缀:obj、bio与帧号
这份数据集的图像命名是有讲究的,不是随便拍的编号。打开压缩包后你会看到类似obj0871_frame0000120.jpg、bio0003_frame0000185.jpg这样的文件,前缀obj和bio分别对应不同的内容来源:obj段通常是从水下机器人拍摄对象视频里截取的帧,bio段偏向生物背景区域。中间的数字是视频段编号,frame后面的数字是具体帧号。
文件名不负责标注类别,类别在对应的XML里。但建议你在解压后用一句话统计一下图像总数,确认不是缺帧或者混入了重复文件。我一般会先跑一个最基本的调研命令:
cd Trash-ICRA19 ls *.jpg | wc -l ls *.xml | wc -l这里ls *.jpg列出所有图像,wc -l统计行数,两个数字应该一致,都是1144。如果图像数量和标注文件数量对不上,后面训练时会出现「找不到标签」或者「图像没有标注」的运行时错误。另外建议把jpg和xml的文件名批量比对一遍:
for f in *.jpg; do xml="${f%.jpg}.xml" [ -f "$xml" ] || echo "missing xml: $f" done这个循环遍历每张 jpg,把后缀换成 .xml 后检查是否存在,缺哪个立刻打印出来。排查数据完整性这一步花不了两分钟,却能把后面训练阶段的坑提前填掉一大半。
2.2 打开一个XML:VOC风格标注的关键字段
这份资源的标注文件是VOC格式的XML,和Pascal VOC、ImageNet检测任务用的结构完全一致。随便打开一个XML,核心字段就这些:
<annotation> <folder>TrashICRA19</folder> <filename>obj0871_frame0000120.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>plastic</name> <bndbox> <xmin>412</xmin> <ymin>308</ymin> <xmax>638</xmax> <ymax>461</ymax> </bndbox> </object> </annotation>稍微解释一下:filename字段和实际图像文件名必须完全一致,很多转格式报错就是因为两张图像的filename写反了;size记录原始图像的宽高和通道数,转YOLO格式时需要靠它做坐标归一化;object可以出现多个,表示图像里有多个目标,每个name是类别字符串,bndbox是左上角和右下角的像素坐标。
调研的时候建议把整个数据集里出现的所有name值去重打印一遍,确认类别总数。Trash-ICRA19 这个数据集的常见类别包含生物、织物、渔网、塑料、绳索等。类别名是字符串,还没转数字ID,所以在转YOLO格式时要自己维护一张「字符串 → 数字ID」的映射表。
2.3 三分钟看清数据分布:类别统计与图像尺寸
在动手转格式前,先花一分钟统计类别分布和图像尺寸,这一步能直接决定你是否需要在训练时开启letterbox或者调整输入分辨率。边做边想:如果这1144张图里大部分都是1920x1080的大图,而少数是低分辨率帧,训练时统一缩放到640x640会让小图被严重拉伸。
import xml.etree.ElementTree as ET import glob from collections import Counter xml_files = glob.glob('*.xml') class_counter = Counter() size_counter = Counter() for xml_file in xml_files: tree = ET.parse(xml_file) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) size_counter[(w, h)] += 1 for obj in root.iter('object'): name = obj.find('name').text class_counter[name] += 1 print('类别统计:', class_counter) print('尺寸统计:', size_counter.most_common(10))这段代码用xml.etree.ElementTree解析XML,glob.glob拿到所有XML文件,Counter做累加统计。输出的类别分布能让你判断类别是否均衡——如果plastic占了800个而bio只有90个,训练时就要考虑对少数类别加权,或者用focal loss。尺寸统计则告诉你是否需要统一分辨率预处理。
3. 把XML转成YOLO训练格式:转换脚本与四个边界坑
3.1 为什么必须转:XML是人看的,txt是YOLO吃的
VOC格式的XML虽然信息完整,但YOLO系列训练时并不直接读取XML。Ultralytics YOLO、Darknet YOLO 期望的标注是一行一个目标的txt文件,每行五个数字:类别ID center_x center_y width height,其中坐标全部归一化到0到1之间。你拿到的资源只提供XML,所以训练前的第一道工序就是格式转换。
转换关系并不复杂,但一对一的坐标换算只是表面工作,真正的坑集中在类别映射、越界裁剪、空文件处理和文件名对齐上。先说结论:XML转txt不是简单的字符串替换,而是一个需要校验的过程。VOC的bndbox记录的是绝对像素坐标,YOLO需要的是相对于图像宽高的比例,同时XML里允许出现目标完全在图像外的脏数据,YOLO不吃那一套。
3.2 转换脚本逐行拆解
下面这个脚本是我处理类似数据集时最常用的一版,参数全部集中在文件头部,直接按你的数据集实际情况修改即可。资源本身也强调「参数化编程、注释明细」,下面的写法延续同样的风格:
import os import xml.etree.ElementTree as ET from glob import glob # ===== 参数区:根据实际数据集修改 ===== IMG_DIR = 'images' # 图像存放目录 XML_DIR = 'annotations' # XML存放目录 OUT_DIR = 'labels' # 输出的YOLO txt目录 CLASS_NAMES = ['bio', 'fabirc', 'fishnet', 'plastic', 'root', 'rope'] # ↑ 这里必须和最终训练时 data.yaml 里的 names 顺序一致 os.makedirs(OUT_DIR, exist_ok=True) # ===== 转换函数 ===== def convert(xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find('size/width').text) h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in CLASS_NAMES: print(f'跳过未知类别: {name} in {xml_path}') continue cls_id = CLASS_NAMES.index(name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 边界裁剪:把完全越界或部分越界的框拉回图像内 xmin = max(0, min(xmin, w - 1)) xmax = max(0, min(xmax, w - 1)) ymin = max(0, min(ymin, h - 1)) ymax = max(0, min(ymax, h - 1)) if xmax <= xmin or ymax <= ymin: print(f'跳过无效框: {xmin},{ymin},{xmax},{ymax} in {xml_path}') continue # 归一化到0~1 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f'{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}') if len(lines) == 0: # 该图没有有效目标,写一个空文件,训练时YOLO会自动跳过 open(out_path, 'w').close() return False with open(out_path, 'w') as f: f.write('\n'.join(lines)) return True # ===== 主流程 ===== xml_list = glob(os.path.join(XML_DIR, '*.xml')) ok = 0 for xml_path in xml_list: base = os.path.splitext(os.path.basename(xml_path))[0] convert(xml_path, os.path.join(OUT_DIR, base + '.txt')) ok += 1 print(f'转换完成: {ok}/{len(xml_list)} 个标注文件已处理')代码逻辑分三段:头部参数区集中维护所有可调项;转换函数里先读图像宽高,再遍历每个object,算出归一化的中心坐标和宽高;主流程把所有XML逐个处理。参数说明如下:
CLASS_NAMES:类别列表的先后顺序就是类别ID,bio对应0,rop对应1,这个顺序必须和你稍后写的data.yaml中的names完全一致,否则训练出来的模型预测类别全部错位。- 越界裁剪操作:
max(0, min(xmin, w-1))把坐标钳制在0到宽高减1的范围内。实战中发现部分框会越出图像边界100像素以上,不裁剪的话归一化坐标会出现负数或大于1的值。 - 空文件处理:有些帧里确实没有任何目标,XML里没有
object节点,脚本会生成一个空txt。YOLO训练时遇到空文件会直接跳过该图像,不会报错,但前提是不能缺失这个txt——缺失会让数据加载器不知道这张图有没有标注,直接报assertion错误。
3.3 验证转换结果:把txt画回图像上
转完格式别急着训练,第一件事是把txt标注画回图像上,肉眼扫一遍。这一步能发现坐标错位、框偏移、类别错乱等一眼可见的翻车问题。我常用的验证代码只有十几行:
import cv2 for line in open('labels/obj0871_frame0000120.txt'): cls_id, cx, cy, bw, bh = map(float, line.split()) cx_px = int(cx * 1920) cy_px = int(cy * 1080) bw_px = int(bw * 1920) bh_px = int(bh * 1080) x1 = int(cx_px - bw_px / 2) y1 = int(cy_px - bh_px / 2) x2 = int(cx_px + bw_px / 2) y2 = int(cy_px + bh_px / 2) img = cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite('check.jpg', img)这只是一段快速抽查脚本,反算回去画框。关键点是:如果框整体偏移了半个身位,多半是归一化时把宽高比搞混了;如果类别显示成数字而不是名字,这只是显示问题,不影响训练;如果框被压缩变形,那说明XML里的size和你实际读图的尺寸不一致,需要检查cv2.imread出来的宽高和XML记录是否匹配。
3.4 四个边界坑
第一坑:类别ID必须从0开始连续编号。不少网友从网上拷的类别列表中间缺了一位,比如['bio', 'fabirc', 'plastic']跳过了2,结果训练时YOLO内部把label张量的维度按类别数分配,ID为3的类别会越界报错。
第二坑:越界框必须裁剪或丢弃。数据集中真实存在目标框右侧超过图像宽度的情况,不处理的话归一化后bw + cx可能大于1,推理时NMS阶段会出现异常,表现为预测框位置莫名偏移。
第三坑:xml文件名和jpg文件名必须完全同前缀。这份资源已经保证了这一点,但如果你自己往数据集里补图,新加的XML命名一旦对不上,转换脚本会静默生成空txt,训练时这张图被跳过,你根本不知道数据少了。
第四坑:空XML要保留而不是删除。删除空XML会导致图像的txt缺失,Ultralytics YOLO在训练时遇到图像但找不到对应txt会直接抛异常,比空txt更严重。
4. 用YOLOv8跑通训练:环境、配置与损失曲线怎么读
4.1 环境配置:miniconda + Python 3.10 + Ultralytics
如果要跑YOLOv8,第一步是把环境装干净。网上关于YOLOv8 anaconda环境配置要求的提问很多,其实核心就三步:装好CUDA版PyTorch,装Ultralytics包,确认GPU驱动版本和PyTorch匹配。我通常用miniconda建独立环境,避免把系统Python搞乱:
conda create -n yolo python=3.10 -y conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics nvidia-smiconda create新建名为yolo的环境,Python版本固定3.10是因为Ultralytics对3.10兼容性最稳。--index-url指定从PyTorch官方源安装CUDA 11.8版本的torch,这一步最容易翻车——如果只执行pip install torch,装出来的是CPU版,训练慢到怀疑人生。最后nvidia-smi查看显卡驱动支持的CUDA版本,只要驱动版本比11.8新,cu118的torch就能正常用。
显卡很关键。如果手头是V100或者更老的显卡,显存只有16G甚至更少,训练batch size就往下调。这个数据集是1920x1080的大图,训练时YOLO会在内部做letterbox缩放,但显存占用依然比常规的小图数据集高不少。
4.2 数据yaml怎么写
转换完标注后,还需要一个数据集配置文件告诉YOLO去哪里找图像、去哪里找标签、类别有哪些。在数据集根目录新建trash.yaml:
path: /home/user/Trash-ICRA19 # 改成你的数据集绝对路径 train: images/train val: images/val names: 0: bio 1: fabirc 2: fishnet 3: plastic 4: root 5: ropepath建议写绝对路径。train和val是相对于path的目录,指向你存放训练图像和验证图像的文件夹。names的序号和顺序必须和第3章转换脚本里的CLASS_NAMES完全一致,一个字母都不能差。比如转换脚本里bio是0,yaml里bio也必须出现在序号0的位置上。
还需要手动划分训练集和验证集。常见的做法是84%训练、16%验证,约960张训练、184张验证。别用随机划分就完事,要保证验证集里每个类别都出现至少一次。可以直接用random.sample画一个划分脚本,把图像和标签文件同步移动:
import os import random from shutil import move files = [f for f in os.listdir('images') if f.endswith('.jpg')] random.seed(42) random.shuffle(files) val_count = int(len(files) * 0.16) val_files = files[:val_count] os.makedirs('images/train', exist_ok=True) os.makedirs('images/val', exist_ok=True) os.makedirs('labels/train', exist_ok=True) os.makedirs('labels/val', exist_ok=True) for f in val_files: move(f'images/{f}', f'images/val/{f}') move(f'labels/{f.replace(".jpg", ".txt")}', f'labels/val/{f.replace(".jpg", ".txt")}') for f in files[val_count:]: move(f'images/{f}', f'images/train/{f}') move(f'labels/{f.replace(".jpg", ".txt")}', f'labels/train/{f.replace(".jpg", ".txt")}')固定随机种子42,保证每次划分结果一致,方便复现。val_count这里算出184张,val_files切片获取验证集。注意标签文件也要跟着移动,否则训练集有图没标注,加载器直接报错。
4.3 训练命令与关键参数
环境配好、yaml写好后,训练命令其实很短:
yolo detect train data=trash.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 workers=4 patience=20这行命令的核心参数拆开看就是这样:
data=trash.yaml:数据集配置文件路径。model=yolov8s.pt:加载YOLOv8s的预训练权重。第一次跑的时候会自动下载yolo预训练模型,网不好就手动下载后放到本地指定路径。yolov8s是small版本,速度和精度的折中,显存有限的情况下比yolov8l稳得多。epochs=100:训练轮数。这个规模的数据集100轮足够收敛,再多收益不大。imgsz=640:训练输入分辨率。虽然原图是1080p,但YOLO内部会做缩放,640是比较常规的设置,太低丢失小目标,太高显存不够。batch=16:一次迭代喂入16张图。显存8G的建议改到8或4,别硬撑,后面避坑部分会细说。workers=4:数据加载线程数。Windows下这个值建议设0,否则容易报BrokenPipeError。patience=20:连续20轮验证集mAP没有提升就提前终止,帮你省时间。
训练开始后,终端会实时打印进度条、每个batch的损失值、当前学习率,以及每一轮结束后的验证指标。训练结束会在runs/detect/train/目录下生成weights/best.pt(验证集mAP最高的权重)和weights/last.pt(最后一轮的权重),后续推理用best.pt。
4.4 读训练日志:损失曲线和混淆矩阵
这里重点看Ultralytics训练结束后自动保存的三张损失图——box_loss、cls_loss、dfl_loss。box_loss是边界框回归损失,下降越快说明模型学框位置的速度越快;cls_loss是分类损失,这个值如果迟迟不降,大概率是类别不平衡或标注错误;dfl_loss是Distribution Focal Loss,负责边界框细粒度回归。
混淆矩阵那块提醒一句:YOLO的混淆矩阵每一行之和不是100%,因为预测结果存在漏检和误检,背景类也会占掉一部分。如果你看到的混淆矩阵对角线数字之和和mAP对不上,不必怀疑训练出了问题,这是正常的。
5. 避坑:五个翻车现场与定位思路
5.1 显存不够:batch=16直接OOM
现象:训练跑完第一个batch就报CUDA out of memory,进程被掐掉,机器直接卡死。
原因:1920x1080的原始图像即使缩放到640,中间特征图占用的显存仍然比普通小图数据集大得多,batch=16对8G显存的显卡来说就是天文数字。不少网友第一次用V100以外的显卡训练都是栽在这里。
解决:把batch从16改成4,workers改成0,imgsz从640改成512,三者组合通常能把显存占用压到6G以内。如果还是爆,就优先降batch,它是显存占用的最大头。
5.2 训练完mAP全是0:类别名没对上
现象:训练正常跑完,验证时每个类别的mAP都是0,但loss在下降。
原因:转换脚本里的CLASS_NAMES和trash.yaml里的names顺序不一致。比如转换脚本里bio是0、fabirc是1,但yaml里bio被排到了序号2的位置,模型学到的标注ID和它认为的类别ID错位,训练出来的模型在验证时对不上号。
解决:写一个小脚本,输出CLASS_NAMES和trash.yaml中names的逐项对比,或者干脆只维护一份类别列表,由它同时生成转换脚本和yaml,从源头杜绝不一致。
5.3 预测框全部偏大,真实目标被大框套小框
现象:训练结束,推理时每个目标都被一个大框包住,框边界明显超出了目标本身。
原因:这是最坑的一个。你刚解压出来的XML可能涉及两种坐标系——有的框是bndbox像素坐标,有的来自外部工具是整数坐标但训练时被当作归一化坐标处理了。转换脚本虽然做了归一化,但没有检查bndbox的数值范围是否和size匹配。比如XML里图像宽是1920,但某个框的xmax是960,你以为这是半球,实际上它可能是另一套坐标系下的归一化数值。
解决:在转换脚本里加一个尺寸合理性检查,凡是xmin、xmax的取值范围超出图像宽度的20%以上,直接打印警告让你人工复核。
5.4 空标注文件导致训练直接中断
现象:训练开始没两分钟,数据加载器报AssertionError: Label file ... not found。
原因:有些帧确实没有目标,XML里没有object节点,转换脚本虽然生成了空txt,但你在整理目录时可能把空文件删了,或者move的时候漏掉了空文件,导致图像存在但没有对应txt。
解决:训练前跑一个检查循环,遍历每一张图像,确认对应的txt文件存在。空txt保留着没问题,YOLO训练时会跳过它所在图像,但缺失txt是硬错误。
5.5 水下图像整体偏蓝,数据增强越增强越离谱
现象:训练出来的模型在真实水下视频上表现差,但在训练集上mAP很高,过拟合迹象明显。
原因:这份数据集是水下场景图像,色调偏蓝且光照分布特殊。YOLO默认开启的HSV色域增强会把图像颜色拉得很奇怪,比如把水底的蓝色拉伸成紫色,模型学到的颜色分布和真实场景脱节。
解决:训练时关闭颜色相关的增强,在命令里加hsv_h=0 hsv_s=0 hsv_v=0,只保留轻微的平移和缩放增强。这是我实际用过且效果最大的一个参数调整,能直接把验证集mAP提升2到3个点。
6. 验证与部署:把模型接到自己的测试视频上
6.1 用已有标签算指标:验证集脚本
训练产生的best.pt到底行不行,别靠眼睛看,用你预留的184张验证集图像跑一遍官方评估脚本:
yolo detect val data=trash.yaml model=runs/detect/train/weights/best.pt这个命令输出mAP@0.5和mAP@0.5:0.95两组数字。对这个数据集,mAP@0.5在0.7以上就算可用的效果,mAP@0.5:0.95相对低一些是正常的,因为水下垃圾目标偏小,小目标的IoU要求更苛刻。
6.2 调节置信度门限
如果你拿模型去跑一段新的水下视频,会发现默认0.25的置信度门槛输出了一堆置信度只有0.3的噪声框。这个场景下0.3到0.4之间的阈值比较合理,过滤掉大部分水底噪点。推理时直接指定:
yolo detect predict model=runs/detect/train/weights/best.pt source=test_video.mp4 conf=0.35conf=0.35让模型只输出置信度大于35%的检测框。如果目标是丢到课程设计演示里,想看到识别效果又不显杂乱,conf=0.4更干净。
6.3 视频流检测与帧率观察
保存预测结果时加一个save=True,输出视频会带检测框。如果要在自己的环境里跑实时摄像头或在线视频流,Ultralytics也支持直接对rtsp://地址做推理,命令不变,把source换成流地址即可。有一点要提醒:CPU推理1080p视频大概只有每秒5到8帧,想实时跑得把imgsz降到416,并且换一张支持半精度推理的显卡。
从那以后我每拆一份目标检测数据集,都强制走一遍「统计类别 → 转换格式 → 回显检查 → 单卡小batch试跑」的流程。这套流程救过我太多次了,希望帮到你。
本文还有配套的精品资源,点击获取