水下垃圾检测数据集:5328张图、7类目标、三种标签格式实战指南
发布时间:2026/9/28 16:43:36来源:尧图网络
简介这份水下垃圾检测数据集面向从事目标检测的开发者、学生与科研人员适用于课程作业、毕业设计、算法竞赛及水域环境智能监测等实际项目可解决水下垃圾识别与水质AI监测中样本不足、标注不规范的问题。资源包共21313个文件包含5328张水下机器人实拍JPG图像以及对应的VOC格式XML、YOLO格式TXT和JSON三种标注文件压缩包约127.54MB三种标签可分别对接不同检测框架省去格式转换环节。数据集划分七类目标水下生物、塑料垃圾、金属垃圾、木头垃圾、橡胶垃圾、布料垃圾与捕鱼工具垃圾图像来源多样、分布均匀、标注精准算法拟合效果较好。目前已有325人学习下载读者可直接获得一套开箱即用的多格式标注数据用于模型训练、对比实验与迁移学习验证快速搭建水下垃圾检测基线并评估不同算法性能。1. 水下垃圾检测数据集5328 张图、7 类目标、三种标签格式到底怎么落地做水域环境智能监测的项目最卡脖子的往往不是模型结构而是数据。你手里有一台水下机器人、一个水质 AI 监测的需求甚至只是课程设计要交一份能跑通的目标检测作业第一步都会撞上同一个问题去哪找一批标注精准、类别清晰、格式齐全的水下垃圾图像。这份水下垃圾检测数据集就是冲着这个场景来的——5328 张水下机器人实拍图7 个类别同时提供 VOCxml、YOLOtxt、JSON 三种标签格式意味着你不管走 TensorFlow Object Detection、YOLO 系列还是 COCO 系训练框架都能直接对接不用自己写转换脚本从零折腾。它适合三类人赶课程作业和毕设的学生、做水域垃圾检测算法验证的工程师、以及要给水质 AI 监测系统做原型验证的团队。下面我按「这份数据长什么样 → 怎么喂进模型 → 哪里会翻车」的顺序拆一遍都是我实际跑数据集时会走的流程。2. 先看清数据底子7 类目标与三种标签格式的对应关系拿到一个数据集我习惯先不急着训练而是把目录结构、类别分布、标签格式三件事摸清楚。这一步花二十分钟能省掉后面几小时的报错排查。水下垃圾检测这类任务有个特点目标尺度差异大塑料瓶可能只占几十像素捕鱼工具却可能横跨半张图所以类别定义和标注框质量直接决定模型上限。2.1 七个类别到底怎么分为什么这么分数据集包含七类水下生物、塑料垃圾、金属垃圾、木头垃圾、橡胶垃圾、布料垃圾、捕鱼工具垃圾。这个划分不是随便凑的它对应的是水域清理和监测里最常见的打捞对象与生态干扰物。塑料、金属、橡胶、布料是按材质分的因为不同材质的垃圾在回收处理环节走的是不同流程木头垃圾和捕鱼工具垃圾单列是因为这两类在水下形态特殊——木头容易和水下生物、沉船残骸混淆捕鱼工具渔网、笼具则常常缠绕生物是生态监测的重点目标。把「水下生物」也作为一个类别而不是背景是为了让模型学会区分活体和垃圾避免把鱼群误判成塑料碎片。实际训练时你会发现水下生物和布料垃圾在浑浊水域里颜色接近是最容易混的一对后面讲混淆矩阵时会再提。2.2 VOC、YOLO、JSON 三种格式各管什么用三种格式对应三套主流训练管线理解它们的差异能帮你少走弯路格式文件形态坐标表示典型对接框架VOC每图一个 xml左上角 右下角绝对像素TensorFlow OD API、Pascal VOC 系YOLO每图一个 txt归一化中心点 宽高YOLOv5/v8/v11、Ultralytics 系JSON单文件或按图依 COCO 或自定义结构COCO 系、Detectron2、MMDetectionVOC 的 xml 里存的是绝对像素坐标好处是直观、可读坏处是换分辨率就得重算YOLO 的 txt 存归一化后的中心点和宽高天然适配多尺度训练这也是为什么现在绝大多数目标检测项目首选 YOLO 格式JSON 则通常承载 COCO 风格的 annotations适合需要 instance segmentation 或复杂元信息的场景。这份数据集三种都给等于把格式转换这道坎替你填了。我一般会先用 YOLO 格式快速跑通 baseline再用 VOC 或 JSON 做交叉验证确认标注一致性。2.3 目录结构核对与快速统计脚本下载解压后先别急着改代码跑一段统计脚本确认图片数、类别分布和标签完整性。下面这段是我常用的核对脚本import os import glob from collections import Counter # 按实际解压路径修改 img_dir UnderwaterGarbage/images label_dir UnderwaterGarbage/labels # YOLO txt 目录 imgs glob.glob(os.path.join(img_dir, *.jpg)) print(f图片总数: {len(imgs)}) # 统计每个类别的框数量 class_names [水下生物, 塑料垃圾, 金属垃圾, 木头垃圾, 橡胶垃圾, 布料垃圾, 捕鱼工具垃圾] counter Counter() missing [] for img in imgs: base os.path.splitext(os.path.basename(img))[0] txt os.path.join(label_dir, base .txt) if not os.path.exists(txt): missing.append(base) continue with open(txt) as f: for line in f: line line.strip() if not line: continue cls_id int(line.split()[0]) counter[class_names[cls_id]] 1 for name in class_names: print(f{name}: {counter[name]} 个标注框) print(f缺失标签的图片数: {len(missing)})这段脚本做三件事统计图片总数、按类别累计标注框数量、找出没有对应标签文件的图片。参数上img_dir和label_dir要按你解压后的真实路径改class_names的顺序必须和数据集标注时的类别索引一致如果顺序错了统计结果会整体错位。跑完你就能判断数据分布是否均匀——如果某一类只有几十个框训练时就要考虑过采样或类别加权。缺失标签的图片要么补标要么从训练集剔除别留着让 dataloader 报错。3. 把数据集喂进 YOLO从格式转换到训练配置的完整链路数据核对没问题接下来就是让它跑起来。这一章我按 YOLO 系列的流程走因为这份数据集的 YOLO txt 格式可以直接用省去转换步骤。如果你用的是其他框架思路一样只是配置文件写法不同。3.1 生成 data.yaml 与训练集验证集划分YOLO 训练的第一步是准备data.yaml它告诉框架去哪找图、有几类、类名是什么。同时要把 5328 张图按比例划分训练集和验证集。我一般用 8:2水下场景数据量不算特别大验证集留够 1000 张左右比较稳。import os import random import shutil random.seed(42) # 固定随机种子保证可复现 img_dir UnderwaterGarbage/images label_dir UnderwaterGarbage/labels out_root dataset for split in [train, val]: os.makedirs(f{out_root}/images/{split}, exist_okTrue) os.makedirs(f{out_root}/labels/{split}, exist_okTrue) imgs sorted(os.listdir(img_dir)) random.shuffle(imgs) split_idx int(len(imgs) * 0.8) train_imgs imgs[:split_idx] val_imgs imgs[split_idx:] for split, files in [(train, train_imgs), (val, val_imgs)]: for f in files: shutil.copy(os.path.join(img_dir, f), f{out_root}/images/{split}/{f}) base os.path.splitext(f)[0] src_label os.path.join(label_dir, base .txt) if os.path.exists(src_label): shutil.copy(src_label, f{out_root}/labels/{split}/{base}.txt) print(f训练集: {len(train_imgs)} 张, 验证集: {len(val_imgs)} 张)random.seed(42)是为了让每次划分结果一致方便复现实验split_idx控制 8:2 比例你可以按需改成 7:3 或 9:1。复制而不是移动是为了保留原始数据万一划分有问题还能重来。跑完后dataset目录下就是标准 YOLO 结构。对应的data.yaml这样写path: ./dataset train: images/train val: images/val nc: 7 names: 0: 水下生物 1: 塑料垃圾 2: 金属垃圾 3: 木头垃圾 4: 橡胶垃圾 5: 布料垃圾 6: 捕鱼工具垃圾nc是类别数必须和 names 的条目数一致names 的索引顺序必须和标注文件里的类别 id 对齐这是最容易出错的地方顺序错了模型学到的就是错位的类别。3.2 训练参数怎么设从 batch size 到学习率YOLO 训练命令本身不复杂难的是参数怎么调。下面是我在这份数据集上常用的起手配置yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience20 \ device0 \ projectruns/underwater \ nameexp1逐个说参数modelyolov8n.pt是 nano 版本参数量小、训练快适合先跑通 baseline如果显存够、追求精度可以换yolov8s.pt或yolov8m.pt。imgsz640是输入分辨率水下小目标多如果塑料碎片经常漏检可以提到 960 或 1280但显存占用会明显上升。batch16是常见起手值显存不够就降到 8 或 4。lr00.01是初始学习率lrf0.01是最终学习率系数YOLO 默认余弦退火这两个值配合决定学习率曲线。patience20表示验证指标 20 轮不提升就早停避免过拟合。device0指定第一块 GPUCPU 训练去掉这个参数但会很慢。训练过程中重点看三个指标box_loss是否稳定下降、mAP50是否在涨、cls_loss有没有异常波动。如果 box_loss 震荡厉害多半是学习率偏高或 batch 太小如果 mAP50 早早卡住不涨可能是类别不平衡或标注有问题。3.3 用混淆矩阵定位类别混淆训练完 YOLO 会自动生成混淆矩阵这是排查类别问题的利器。水下垃圾检测里最常见的混淆是「水下生物 ↔ 布料垃圾」和「木头垃圾 ↔ 水下生物」。混淆矩阵的对角线是正确分类非对角线就是误判。如果发现某一对类别互相误判严重通常有两个原因一是这两类在视觉上确实接近二是标注时边界模糊。解决办法包括增加这两类的难例样本、在标注规范里明确区分标准、或者干脆合并成一个大类如果业务允许。我一般会导出混淆矩阵后挑出误判最多的几十张图人工看一遍往往能发现标注不一致的问题。4. 三种标签格式互转VOC、YOLO、JSON 的转换脚本与边界坑虽然这份数据集三种格式都给了但实际项目里你经常需要转换——比如框架只吃 COCO JSON或者你要把 YOLO 格式转回 VOC 做可视化。这一章给两段我常用的转换脚本并说清坐标换算里最容易翻车的地方。4.1 YOLO 转 VOC归一化坐标还原成绝对像素YOLO 的 txt 存的是归一化中心点和宽高转 VOC 的 xml 需要还原成绝对像素的左上角和右下角。关键是要读对图片的真实宽高不能想当然用 640。import os from PIL import Image import xml.etree.ElementTree as ET class_names [水下生物, 塑料垃圾, 金属垃圾, 木头垃圾, 橡胶垃圾, 布料垃圾, 捕鱼工具垃圾] def yolo_to_voc(img_path, txt_path, out_xml): img Image.open(img_path) w, h img.size # 必须用真实尺寸不能用固定值 root ET.Element(annotation) ET.SubElement(root, filename).text os.path.basename(img_path) size ET.SubElement(root, size) ET.SubElement(size, width).text str(w) ET.SubElement(size, height).text str(h) with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) # 归一化中心点转绝对坐标 x_center, y_center cx * w, cy * h box_w, box_h bw * w, bh * h xmin int(x_center - box_w / 2) ymin int(y_center - box_h / 2) xmax int(x_center box_w / 2) ymax int(y_center box_h / 2) obj ET.SubElement(root, object) ET.SubElement(obj, name).text class_names[int(cls_id)] bnd ET.SubElement(obj, bndbox) ET.SubElement(bnd, xmin).text str(max(0, xmin)) ET.SubElement(bnd, ymin).text str(max(0, ymin)) ET.SubElement(bnd, xmax).text str(min(w, xmax)) ET.SubElement(bnd, ymax).text str(min(h, ymax)) tree ET.ElementTree(root) tree.write(out_xml, encodingutf-8, xml_declarationTrue)核心逻辑是cx * w把归一化中心点还原成像素再减去半个宽高得到左上角。max(0, xmin)和min(w, xmax)是边界裁剪防止标注框超出图像范围导致某些框架报错。这里最大的坑是图片尺寸如果你用固定 640 去还原而原图是 1920×1080所有框都会错位。所以一定要Image.open读真实尺寸。4.2 YOLO 转 COCO JSONcategories 与 annotations 的对应COCO JSON 结构比 VOC 复杂核心是 images、annotations、categories 三个数组靠 id 关联。转换时最容易错的是 id 映射和 bbox 格式——COCO 的 bbox 是[x_min, y_min, width, height]不是左上右下。import os import json from PIL import Image class_names [水下生物, 塑料垃圾, 金属垃圾, 木头垃圾, 橡胶垃圾, 布料垃圾, 捕鱼工具垃圾] coco {images: [], annotations: [], categories: []} for i, name in enumerate(class_names): coco[categories].append({id: i, name: name, supercategory: garbage}) ann_id 1 img_dir UnderwaterGarbage/images label_dir UnderwaterGarbage/labels for img_id, fname in enumerate(sorted(os.listdir(img_dir))): if not fname.endswith(.jpg): continue img_path os.path.join(img_dir, fname) w, h Image.open(img_path).size coco[images].append({id: img_id, file_name: fname, width: w, height: h}) base os.path.splitext(fname)[0] txt_path os.path.join(label_dir, base .txt) if not os.path.exists(txt_path): continue with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) # COCO bbox: [x_min, y_min, width, height] box_w, box_h bw * w, bh * h x_min (cx * w) - box_w / 2 y_min (cy * h) - box_h / 2 coco[annotations].append({ id: ann_id, image_id: img_id, category_id: int(cls_id), bbox: [round(x_min, 2), round(y_min, 2), round(box_w, 2), round(box_h, 2)], area: round(box_w * box_h, 2), iscrowd: 0 }) ann_id 1 with open(annotations.json, w, encodingutf-8) as f: json.dump(coco, f, ensure_asciiFalse, indent2)category_id必须和 categories 里的 id 对应image_id必须和 images 里的 id 对应这两条链断了整个 JSON 就废了。area字段有些框架会用来做小目标过滤别漏。iscrowd0表示单实例标注如果有多实例重叠才设 1。转完建议用pycocotools加载验证一遍能提前发现结构错误。5. 避坑与排查水下垃圾检测数据集最常见的五个翻车点这一章是我踩过的坑按「现象 → 原因 → 解决」写你对照排查能省不少时间。现象一训练 loss 正常下降但 mAP 一直很低。原因多半是类别索引错位——data.yaml里 names 的顺序和标注文件里的类别 id 不一致模型学的是错位的类别。解决用第 2 章的统计脚本核对每个 id 对应的类别确保 names 顺序和标注时完全一致。现象二某些图片训练时报「label out of bounds」。原因是标注框坐标超出图像范围可能是标注时图片被裁剪过或者转换脚本没做边界裁剪。解决在转换脚本里加max(0, ...)和min(w, ...)裁剪或者写个校验脚本批量检查所有 txt 的坐标是否在 [0,1] 区间内。现象三小目标塑料碎片大量漏检。原因是输入分辨率太低640 的输入下几十像素的目标经过下采样后特征几乎消失。解决把imgsz提到 960 或 1280或者用带 P2 小目标检测层的模型结构。代价是显存和训练时间上升权衡着来。现象四验证集 mAP 高但实际部署到水下机器人上效果差。原因是训练集和实际场景的域差异——数据集是特定水域、特定光照拍的换一片水域或换一种浑浊度分布就变了。解决在实际部署环境补采一批图做微调或者用颜色增强、模糊增强模拟不同水质。现象五JSON 格式加载报KeyError: categories。原因是转换时 categories 数组为空或字段名拼错。解决检查 JSON 顶层是否同时有 images、annotations、categories 三个键且 categories 里每项都有 id 和 name。用json.load后打印 keys 确认结构。6. 进阶技巧用预训练权重加速收敛与验证标注质量跑通 baseline 之后想再往上提一档有两个方向值得花时间一是用预训练权重做迁移学习二是反过来用模型验证标注质量。先说预训练。YOLO 系列在 COCO 上预训练的权重如yolov8n.pt已经学到了通用边缘、纹理特征直接拿来微调水下垃圾检测收敛速度比从零训练快很多通常 50 到 100 轮就能到不错的 mAP。我一般会先用 nano 权重跑一轮看 baseline再用yolov8s.pt或yolov8m.pt做精度提升。如果显存紧张可以用yolov8n.pt配合更高分辨率小目标效果往往比大模型低分辨率更好。再说用模型反查标注。训练完一轮后把验证集里模型预测和真实标注差异大的图挑出来人工看一遍。如果模型预测明显更合理那很可能是标注漏标或框偏了。这个「模型辅助标注审核」的流程在 5000 张量级的数据集上能揪出不少隐藏的标注问题。具体做法是导出预测结果和 ground truth 做 IoU 匹配把 IoU 低于 0.3 的样本单独列出来复查。# 伪代码思路加载预测结果和真实标签按 IoU 筛选低匹配样本 def iou(box1, box2): # box 格式 [x_min, y_min, x_max, y_max] xi1 max(box1[0], box2[0]) yi1 max(box1[1], box2[1]) xi2 min(box1[2], box2[2]) yi2 min(box1[3], box2[3]) inter max(0, xi2 - xi1) * max(0, yi2 - yi1) area1 (box1[2]-box1[0]) * (box1[3]-box1[1]) area2 (box2[2]-box2[0]) * (box2[3]-box2[1]) return inter / (area1 area2 - inter 1e-6) # 对每张图把预测框和真实框两两算 IoU # 若某真实框找不到 IoU0.3 的预测框标记为疑似漏标这段逻辑不复杂但很实用。我靠这个流程在几个数据集上都发现过漏标的目标尤其是水下生物这类形态多变的类别。最后说一个我自己的习惯每次拿到新数据集我都会先跑一遍统计脚本、再可视化 20 张带框的图、然后才开训练。可视化这一步用matplotlib把框画到图上肉眼扫一遍能发现坐标错位、类别错标、图片损坏这些脚本查不出来的问题。从那以后我每次接新数据集都强制走一遍「统计 → 可视化 → 训练」三步再也没出现过训练到一半才发现标注整体偏移的翻车。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网