
简介面向水下机器人作业与水域环境监测场景这份水下垃圾检测数据集提供5328张真实拍摄图像覆盖水下生物、塑料垃圾、金属垃圾、木头垃圾、橡胶垃圾、布料垃圾及捕鱼工具垃圾7个类别标注精准且分布均匀适合用于课程设计、毕业设计、算法比赛及实际项目中的多类目标检测模型训练与验证。数据集包共包含21313个文件其中jpg原图5328张并同时提供vocxml、yolotxt与json三种标签格式可直接对接主流目标检测框架省去格式转换工作。压缩包整体大小为127.54MB目录结构简洁便于按图像编号快速检索与划分训练集、验证集。当前已有325人浏览学习适用于从入门到进阶的目标检测开发者快速构建水下垃圾识别模型。整体而言该数据集贴近真实水下环境类别划分细致对水环境智能监测、水域垃圾自动识别等应用具有直接参考价值拿到后可直接开展模型训练与效果评估。1. 水下垃圾检测数据集为什么值得先拆开再训练拿到这个水下垃圾检测数据集的第一反应多数人会直接把它丢进训练脚本里跑结果被格式不匹配、类别名错位、标签文件缺失这些问题连续卡住。这份资源本身包含5328张水下机器人拍摄的真实图像覆盖水下生物、塑料、金属、木头、橡胶、布料、捕鱼工具七个类别而且同时提供VOC XML、YOLO TXT和JSON三种标注格式本质上就是把数据整理和格式适配的工作替你做了大半。适合做水域环境监测、课程作业或比赛项目的人也适合刚接触目标检测但不想把时间耗在数据清洗上的从业者。但我的建议是拿到手先别急花半小时拆结构后面能省出两天的排错时间。2. 七类的标注口径和三种格式决定模型上限的细节数据集的价值不在图片数量而在标注是否一致、类别边界是否清晰、格式是否通用。5328张图只能说明工作量真正影响YOLO模型训练效果的是类别怎么定义、边界怎么画、标签以什么结构存储。这三件事没搞清楚之前就开训后续每个环节都在还账。2.1 七类别的实际含义与容易混淆的边界七个类别里水下生物单独作为一类而不是垃圾它的作用在目标检测里接近“干扰项”。水下环境中鱼、蟹、海龟这类生物和垃圾同时出现如果模型把生物也当成垃圾下游的清理决策会出错如果完全不学生物和塑料同时出现时又容易误检。让模型学会区分“要清理的”和“不用动的”是这个类存在的意义。另外六类都属于垃圾塑料垃圾以袋子、瓶子、包装膜为主金属垃圾常见罐体、零件和线缆木头垃圾是树枝、木板、加工废料橡胶垃圾主要是轮胎和密封圈布料垃圾以衣物和渔网碎片为主捕鱼工具垃圾则包括整张渔网、鱼线、笼具和浮标。真正麻烦的是类别边界。湿润状态下塑料和橡胶的反光特征非常接近渔网碎片在图像里和布料纹理几乎一样木头长时间浸泡后会变黑和金属废料的颜色区分度下降。标注人员如果标准不统一同一张图在不同批次里可能出现不同标签模型就会学到互相矛盾的特征。拿到数据集后先做一次标注一致性检查。别急着训练抽几十张图把每类的标注框可视化叠在原图上重点看塑料和橡胶、布料和渔网这两组容易混的类别确认标注口径一致后再进入训练环节。统计一下各类别的分布用脚本读取所有XML文件数一数每个类别出现的次数和标注框数量代码如下import glob import xml.etree.ElementTree as ET from collections import Counter xml_files glob.glob(./UnderwaterGarbage/xmls/*.xml) counter Counter() for xml_path in xml_files: tree ET.parse(xml_path) root tree.getroot() for obj in root.iter(object): name obj.find(name).text.strip() counter[name] 1 print(类别总数:, len(counter)) for cls_name, count in counter.most_common(): print(f{cls_name}: {count})这段代码遍历所有VOC格式的XML文件用iter(object)取出每个标注目标并统计类别出现次数。运行结果能直观反映出类别是否均衡。如果某个类别只占总数5%以下后续训练时就需要考虑针对性增强或调整损失权重否则模型大概率会忽略这个类。顺便加一句统计时直接把XML当作唯一数据源是因为XML里保留了完整的像素坐标和目标名称不依赖额外信息也是最不容易出错的检查路径。统计完分布再看看标注框面积和长宽比水下目标普遍偏小这会直接影响anchor尺寸设置。2.2 三种标注格式的结构差异与选型场景这份数据集同时给出VOC XML、YOLO TXT、JSON三种格式不是简单的重复存储三种格式的适用场景完全不同。VOC XML是树状结构每个object节点下有name、bndbox两个关键字段坐标用像素值表达可读性好打开一个XML文件就能直接看到目标位置。适合做可视化检查、人工复核和手工修正。缺点是文件体积大解析慢训练框架基本不直接读取它。YOLO TXT是逐行存储的归一化坐标每行格式为class_id cx cy w h四个数值全部归一化到0到1区间。训练YOLO系列模型时框架直接读取这种格式是实际训练入口。缺点是人眼不可读坐标对不对很难从文件本身判断。JSON格式要看内部结构。如果按COCO规范组织通常包含images、annotations、categories三个顶层数组annotations里的bbox是像素坐标categories定义类别id到名称的映射。这个结构被pycocotools等评估工具直接支持适合做指标评估和自定义数据管道。三种格式的实际用途可以归纳如下格式文件结构适用场景注意点VOC XML树结构object含像素坐标可视化检查、人工复核每张图对应同名XML文件YOLO TXT每行class 归一化中心点与宽高YOLO系列训练入参文件名必须与图像完全一致JSONimages/annotations/categories数组COCO评估、自定义管道需确认是否严格COCO结构我的使用习惯是用XML做人工排查用JSON做自动化验证用TXT作为训练输入。三种格式互相印证的逻辑在于它们是同一批标注数据的不同投影格式转换过程中如果出现问题比对三种格式能快速定位是原始标注问题还是转换逻辑问题。3. 把数据集接进YOLO训练流程目录重组与配置实操知道类别和格式之后下一步是把下载的原始目录改造成YOLO训练需要的结构。这个环节是翻车重灾区常见的错误是图片划分了、标签没有跟着划分或者目录结构不符合框架要求训练启动就直接报错。3.1 目录重组与train/val划分脚本我拿到一份带多种格式的数据集时一般先看原始目录组织方式。这个数据集大概率分为images、xmls、yolo_labels、jsons四个文件夹。而YOLO系列框架期望的输入结构是一个外层目录下分train和val两大部分每部分里面再分images和labels两个子目录且图片与标签文件同名。手工复制几千个文件很容易出错直接写一个划分脚本一次性解决读取全部图片文件名按比例切分后基于同一个文件名列表同时复制图片和对应的TXT标签文件。import os import random import shutil random.seed(42) img_dir ./UnderwaterGarbage/images txt_dir ./UnderwaterGarbage/yolo_labels val_ratio 0.2 image_files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(image_files) split_point int(len(image_files) * (1 - val_ratio)) train_files image_files[:split_point] val_files image_files[split_point:] def copy_batch(file_list, src_img, src_label, dst_img, dst_label): os.makedirs(dst_img, exist_okTrue) os.makedirs(dst_label, exist_okTrue) for f in file_list: stem os.path.splitext(f)[0] shutil.copy2( os.path.join(src_img, stem .jpg), os.path.join(dst_img, stem .jpg) ) shutil.copy2( os.path.join(src_label, stem .txt), os.path.join(dst_label, stem .txt) ) copy_batch(train_files, img_dir, txt_dir, ./dataset/train/images, ./dataset/train/labels) copy_batch(val_files, img_dir, txt_dir, ./dataset/val/images, ./dataset/val/labels) print(ftrain: {len(train_files)} 张, val: {len(val_files)} 张)脚本的核心逻辑是先用random.seed(42)固定随机种子保证每次运行划分结果一致。然后用列表切片按比例分出train和val两部分再通过copy_batch函数将图片和标签同步复制到目标目录。这里的文件名起到唯一关联作用所有操作都基于文件名stem拼接从根本上避免图片和标签错位。参数说明val_ratio控制验证集比例一般目标检测用0.2比较稳妥seed42是固定随机种子后续想复现实验结果就必须保持这个值不变。划分完成后做一次完整性校验统计train和val目录下的图片数量和标签数量是否一一对应。这一步可以用一段简短的命令行完成echo train images: $(ls ./dataset/train/images/*.jpg | wc -l) echo train labels: $(ls ./dataset/train/labels/*.txt | wc -l) echo val images: $(ls ./dataset/val/images/*.jpg | wc -l) echo val labels: $(ls ./dataset/val/labels/*.txt | wc -l)不需要完全一致才叫通过核心是train下图片等于标签val下图片等于标签。如果出现差异回到划分脚本里检查TXT文件名的后缀是否与图片同名这是最常见的缺失原因。3.2 数据配置文件与训练超参数调整目录就绪后需要在数据集根目录创建data.yaml这个文件被YOLO框架读取用于定位训练、验证数据和定义类别名称。path: ./dataset train: train/images val: val/images nc: 7 names: 0: underwater_bio 1: plastic 2: metal 3: wood 4: rubber 5: fabric 6: fishing_tool这里的path是相对data.yaml所在目录的根路径train和val分别指向images子目录。框架会自动在同一级目录下寻找labels文件夹所以不需要显式写labels路径。类别顺序的注意事项要单独说明data.yaml里的names顺序必须和TXT标签里的class_id一一对应。也就是说如果你发现训练时预测结果里的class 0变成了木头而实际应该对应水下生物说明类别顺序没有对上。我吃过这个亏当时靠逐一打印预测框才发现names列表和训练时的类别映射错位了。训练超参数上针对水下垃圾场景我一般这样设置图像尺寸imgsz取640起步水下目标普遍偏小但图像特征不算极端640能兼顾速度和精度batch大小根据显存决定16是常见选择epochs先跑50到100观察loss曲线不要一上来就配300。数据增强参数需要重点调整。水下图像整体偏蓝绿对比度偏低。YOLO默认的hsv_h增强会大幅改变色相但水下场景的颜色分布本来就特殊色相变化过大会让模型学到错误的颜色映射关系。实际项目里我把增强参数调成了更保守的组合关闭水平翻转可能会损失部分样本泛化能力但考虑到水下机器人拍摄角度相对固定翻转带来的增益有限mosaic增强可以保留能让模型在拥挤目标场景下更鲁棒。对比度增强适当调高这比色相变换更适合水下图像特征。启动训练时建议加载预训练权重来加速收敛。底层特征如边缘、纹理、形状是通用的水下垃圾同样具备这些特征。训练命令用Ultralytics框架的一套标准指令即可yolo train modelyolov8s.pt data/path/to/your/data.yaml epochs80 imgsz640 batch16如果不加载预训练权重5328张图从零开始训练收敛速度会明显变慢而且早期epoch的loss波动会很大不容易判断模型是否在正常学习。加载后第一轮epoch就能看到loss明显下降这个反馈很重要。4. 三种标注格式互转的边界条件从XML到TXT再到JSON验证数据集虽然自带三种格式但实际使用中经常遇到需要转换的场景想换一个不在列表里的类别名称、想过滤掉某些小目标、想合并类别都可能需要从一种格式重新生成另一种格式。这个环节的坑集中在归一化计算和坐标边界处理上。4.1 XML转YOLO TXT的归一化计算与坐标截断VOC XML记录的是像素坐标YOLO TXT需要归一化的中心点坐标和宽高。核心公式是中心点x (xmin xmax) / 2 / 图片宽度中心点y (ymin ymax) / 2 / 图片高度归一化宽 (xmax - xmin) / 图片宽度归一化高 (ymax - ymin) / 图片高度公式本身不难但实际转换中很容易在坐标越界这个问题上翻车。人工标注时偶尔会把框画得超出图像边界一点或者某些转换工具产生负数坐标这些值直接归一化后会出现cx、cy大于1的情况YOLO训练框架会静默丢弃这些标注行。结果就是训练时少数样本缺失标签模型精度莫名其妙地受影响。转换脚本里加上边界截断处理统一把所有坐标clip到图像尺寸范围内import os import xml.etree.ElementTree as ET CLASS_NAMES [underwater_bio, plastic, metal, wood, rubber, fabric, fishing_tool] def convert_xml_to_yolo(xml_path, out_dir, img_width0, img_height0): tree ET.parse(xml_path) root tree.getroot() size root.find(size) if img_width and img_height: width, height img_width, img_height else: width int(size.find(width).text) height int(size.find(height).text) output_lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASS_NAMES: print(f未知类别 {name} 在 {xml_path}) continue cls_id CLASS_NAMES.index(name) box obj.find(bndbox) xmin max(0, min(float(box.find(xmin).text), width)) ymin max(0, min(float(box.find(ymin).text), height)) xmax max(0, min(float(box.find(xmax).text), width)) ymax max(0, min(float(box.find(ymax).text), height)) bw, bh xmax - xmin, ymax - ymin if bw 0 or bh 0: continue cx (xmin xmax) / 2.0 / width cy (ymin ymax) / 2.0 / height w_norm bw / width h_norm bh / height output_lines.append( f{cls_id} {cx:.6f} {cy:.6f} {w_norm:.6f} {h_norm:.6f} ) base os.path.splitext(os.path.basename(xml_path))[0] os.makedirs(out_dir, exist_okTrue) with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(output_lines))这个脚本里最关键的是clip操作。用max(0, min(value, width))把每个坐标值限制在合理范围内防止标注框越界产生非法归一化值。外层支持传入img_width和img_height参数用于处理XML文件里size信息和实际图片尺寸不一致的情况——这种情况在某些标注工具生成的XML里很常见。运行转换后必须做一次反向验证随机挑几张图把TXT里的框画回图片上看对齐情况。这一步是在给自己上一道保险确认转换结果不是黑匣子产物。4.2 JSON标注结构的读取与异常检测JSON格式的用途在这里需要确认它是否遵循COCO规范。COCO结构的JSON里annotations的bbox字段被定义为[x, y, width, height]格式单位是像素。这个定义在评估阶段非常重要因为pycocotools计算mAP时就是直接读这个字段格式错误会导致评估结果诡异。写一段JSON验证脚本检查JSON文件是否存在结构问题和越界标注import json from collections import Counter with open(UnderwaterGarbage/annotations/instances.json, r) as f: coco json.load(f) print(categories 数量:, len(coco[categories])) print(images 数量:, len(coco[images])) print(annotations 数量:, len(coco[annotations])) img_map {img[id]: img for img in coco[images]} cat_map {cat[id]: cat[name] for cat in coco[categories]} orphan_anns [a[id] for a in coco[annotations] if a[image_id] not in img_map] if orphan_anns: print(f警告: {len(orphan_anns)} 条标注没有对应图片) else: print(检查通过: 所有标注都关联到有效图片) for cat_id, count in Counter(a[category_id] for a in coco[annotations]).items(): print(f{cat_map[cat_id]}: {count} 个标注框) out_of_bounds [] for ann in coco[annotations]: img img_map[ann[image_id]] x, y, w, h ann[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: out_of_bounds.append(ann[id]) if out_of_bounds: print(f越界标注数量: {len(out_of_bounds)}) else: print(检查通过: 所有bbox都在图像范围内)这个脚本主要做三件事检查是否有标注关联到不存在的图片ID统计各类别框分布检测bbox是否越界。前两类问题通常是原始标注生成时就产生的第三种则是格式转换时常见的错误。运行得到的结果如果显示越界标注超过总标注量的2%建议回到源标注重新处理而不是在JSON层修复因为越界往往意味着原始的XML或TXT里也存在同样的脏数据。5. 避坑水下垃圾数据集训练时的常见问题与排查这几条问题来自我实际跑这个数据集时的经历每一条都对应过真实的训练翻车按现象、原因、解决三个维度拆开讲。5.1 类别顺序错位导致预测结果全部混乱现象训练正常完成验证loss正常下降但可视化预测结果时发现类别标签对不上塑料预测成金属木头预测成水下生物。原因data.yaml里的names顺序和TXT标签里的class_id编码不一致。比如XML里类别顺序是A、B、C但转换脚本里硬编码的CLASS_NAMES列表顺序不同class_id就会错位。解决统一所有环节使用同一份类别映射。建议把类别映射表单独放在一个文件里XML转换脚本、data.yaml、JSON的categories三处都从这份文件生成不要在多个地方手工维护列表。5.2 XML里的size与实际图片尺寸不一致现象转换后的TXT文件里出现cx、cy大于1的值或者训练时提示大量标注被过滤。原因部分标注工具在XML里写的是固定默认尺寸没有根据实际图片更新导致归一化时分母错误。解决转换脚本增加一个参数强制从真实图片读取宽高不依赖XML里的size节点。用PIL或OpenCV读一下就能解决from PIL import Image img Image.open(image_path) real_w, real_h img.size转换时传入img_widthreal_w, img_heightreal_h覆盖XML里的错误值。5.3 YOLO标签文件出现空文件现象某个TXT文件大小为0字节训练时框架提示labels为空。原因原图里没有标注任何目标但依然生成了XML文件或者XML里没有object节点又或者所有目标在转换时被坐标过滤逻辑剔除。解决先区分是空白样本还是转换丢失。如果图片本身没有目标这种空文件要保留YOLO框架允许背景图存在如果是转换丢失需要检查边界截断逻辑。区分方法是用脚本扫描所有XML记录没有object节点的文件名与空TXT文件列表做对比。5.4 验证精度高但实际水下视频检测效果差现象在val集上mAP到0.8以上拿到真实水下视频测试却大量漏检。原因数据分布漂移。训练集的5328张图可能集中在一定水域深度和光照范围真实环境的浑浊度、色偏、目标尺度不同模型泛化能力自然下降。解决训练时加入针对水下图像的增强策略重点做对比度拉伸、亮度抖动、通道扰动不要只依赖通用增强。模型选型上也可以考虑在YOLO的基础上并联一个浅层特征分支专门负责小目标检测这个在后续章节细说。5.5 划分数据时图片和标签没有同步现象训练启动报错某张图片找不到对应的标签文件。原因划分脚本只处理了图片列表标签文件沿用了另一个列表或者shuffle后两个列表的顺序不一致。解决用文件名作为唯一关联键先构造一个包含完整文件名的列表shuffle之后基于stem名拼接对应路径来同步复制。代码示例见3.1节的copy_batch函数核心是不允许图片和标签分别独立shuffle。5.6 验证集划分不固定导致精度虚高现象每次训练mAP波动很大看起来有0.1以上的差距但代码和数据没变过。原因随机种子没有固定每次启动训练时验证集自动重新划分模型实际上在不一样的验证子集上评估数值没有可比性。解决固定随机种子并保存一份验证集文件名列表到磁盘后续每次实验用同一个列表参与评估。文件列表本身就是一套完整的验证集定义比在代码里依赖随机状态可靠得多。问题关键现象排查思路类别错位预测label与目标不符检查class_id映射尺寸不符cx、cy超界强制用真实图片尺寸空标签文件0字节label文件区分空白样本与转换丢失泛化不足val高、实测差增强对比度与亮度抖动标签不同步图片无对应label按文件名同步复制验证集漂移mAP波动大固定种子并保存列表6. 进阶用法从类别均衡检查到小样本快速验证数据集自带5328张图和七类标注但类别均衡程度不会天然完美。实际使用时要先跑一次长尾分布检查确认是否有类别框数严重偏少。如果发现某类占比低于5%两个方向可以调整一是把这类目标单独复制做几次mosaic增强补足训练样本量二是调整类别损失权重让模型对少样本类别更加敏感。前者的风险是可能过拟合后者的风险是其他类别精度下降实践中两种方法配合使用效果好一些。确定类别分布没有大问题后在我自己的项目习惯里不会直接跑全量80到100个epoch。先用20个epoch、小batch做一轮管道验证目标是确认数据读取、目录结构、标签映射、loss下降趋势都没有异常。这个过程相当于给整个训练流程做冒烟测试能省掉大量等待全量训练结束后才发现配置错误的返工时间。检查点很明确loss曲线是否在前几个epoch明显下降训练分类别指标是否正常验证集mAP是否在合理区间。如果这些都没问题再上全量训练。整个流程跑通之后最后一步是分类别评估。整体mAP是一个平均指标弱类被强类掩盖是常有的事。用pycocotools跑COCO指标时按类别输出AP值找出模型表现最差的两三类针对性做增强或补充样本比盲目调超参数更有效。我这个习惯是被一次比赛经历逼出来的。当时调了一周的参数整体mAP稳步上涨但提交时才注意到模型对“橡胶垃圾”这个类别几乎完全失效原因是训练时忽略了类别均衡检查橡胶类样本少且与塑料类高度混淆模型把所有亮点物体都预测成了塑料。从那以后我每次拿到新数据集都强制先走一遍分布统计、小样本验证、分类别评估这三个步骤再谈调参。希望帮到你。本文还有配套的精品资源点击获取