简介一套面向水下环境垃圾识别与分类任务的数据集同时提供VOC与YOLO两种主流标注格式兼顾复杂图像分割信息与快速实时检测需求适合不同工具链的计算机视觉研究者、海洋工程开发者及环境监测项目使用。资源以7z压缩包形式发布压缩包整体约203.79MB共包含2000个文件其中以1015个XML标注文件和985个TXT标注文件为主并已明确划分出训练集、验证集与测试集便于直接开展模型训练、参数调优和泛化性能评估是典型的监督学习数据集组织方式。除静态图像外资源还保留了真实水下视频数据可用于视频推理、垃圾移动轨迹分析等动态监测任务帮助应对水下光照不足、能见度低、水流扰动等复杂场景下的检测难点。现阶段已有239人学习适合作为目标检测算法对比实验、水下机器人垃圾清理方案验证以及相关课程设计和毕业设计的参考数据集。1. 水下垃圾检测数据格式对齐比模型选型更花时间做过水下目标检测的人都有体会陆地上调参调得顺的检测管线一转到水下就失灵。光照衰减、悬浮颗粒散射、水流导致的形态畸变让同一张图在不同帧里的特征分布差异极大。而真实工程中最先卡住你的往往不是模型是数据本身——拿到手的标注文件是VOC还是YOLO决定了你后续能不能直接进训练管线。这套水下垃圾检测数据集一共23,056个文件同时提供VOC格式与YOLO格式标注静态帧和视频序列混排直接把“能不能喂给YOLO训练”和“能不能拿来做视频推理验证”两件事一起解决了。适合需要快速搭建水下垃圾识别基线、或者要验证检测模型在真实水下场景中鲁棒性的开发者与算法工程师。2. 装备认知VOC与YOLO标注格式的结构差异2.1 文件命名规律与数据集划分逻辑数据集文件名的形如obj1051_frame0000012.txt、bio0015_frame0000041.txt能直接看出两层信息obj与bio前缀对应采集场景或物体来源类别frame后面的数字序列则是原始视频抽帧后的帧序号。这种命名方式对视频推理任务极其友善因为你可以通过前缀恢复视频片段关系通过帧序号恢复时序连续性。训练集、验证集、测试集的划分并没有写在单一CSV里而是通过目录结构或者文件清单体现。标准做法是拿到数据后先统计各目录下的图片与标注文件数量确认各类别样本均衡后再开始训练而不是直接一股脑喂给网络。2.2 VOC格式目录结构与XML标注字段解析VOC格式是Pascal VOC社区确立的标准它把每张图的标注信息放在XML文件中字段包含filename、size图像宽高与深度、object对象列表每个object里有name类别名、pose姿态、truncated截断、difficult难例标志以及bndbox边界框坐标。annotation folderunderwater_garbage/folder filenameobj1051_frame0000012.jpg/filename size width1920/width height1080/height depth3/depth /size object nameplastic_bottle/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin412/xmin ymin318/ymin xmax689/xmax ymax632/ymax /bndbox /object /annotationbndbox里的xmin、ymin、xmax、ymax均为像素绝对坐标左上角为原点。多数VOC格式依赖的检测框架如Faster RCNN的VOC分支直接读取这套字段。需要注意的是difficult1的样本在默认评估时会被忽略水下垃圾数据里这类样本常常是严重遮挡或远距离目标项目里具体怎么处理要看README说明。2.3 YOLO标注格式的归一化坐标原理YOLO格式是纯文本文件一行一个目标五列数字分别是类别索引、归一化中心点X、归一化中心点Y、归一化宽度W、归一化高度H。0 0.2869 0.4398 0.1443 0.2907 2 0.7031 0.1872 0.0924 0.0815category_id从0开始计数必须与配置文件中的names列表顺序严格一致。坐标值除以图像宽高做归一化后全部落在0到1之间。这种格式的优势是训练时读取效率高不用解析XML树尤其适配YOLO系列在训练预处理中进行的马赛克增强、随机缩放——归一化坐标在这些变换下只需要同步缩放数值即可。两种格式的核心差异在于VOC保留绝对像素坐标和丰富的图像元数据YOLO格式牺牲了这部分信息换取极致的加载速度。当你要做数据可视化、人工质检时用VOC更直观当你在批量跑实验、做多尺度训练HRESIZE时用YOLO更顺手。3. 自动化衔接VOC到YOLO的格式转换与数据验证脚本3.1 目录扫描与类别映射的工程化处理拿到数据集后先不要急着转换。第一步是把VOC XML目录和YOLO TXT目录分别扫描一次对照两边的文件名集合找出缺失或多余的标注。常见情况是某张图在VOC目录里有标注、在YOLO目录里缺失反之亦然。这种不一致直接导致训练时dataloader报错读不到对应标注文件。写转换脚本前还需要固定类别映射表。先统计所有XML里出现的name字段按出现频率排序后手工指定索引。注意水下垃圾容易出现同类物品描述不统一的情况比如plastic_bottle和bottle_plastic要先做归一化再分配索引否则两个类别的样本量会被拆分影响检测效果。import os import xml.etree.ElementTree as ET from pathlib import Path class_to_id { bio_plastic: 0, metal_can: 1, glass_bottle: 2, fishing_net: 3, rubber: 4, textile: 5, other: 6 } def voc_to_yolo(xml_path, output_dir, image_width, image_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_to_id: continue cls_id class_to_id[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / image_width y_center (ymin ymax) / 2.0 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) txt_name Path(xml_path).stem .txt with open(os.path.join(output_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: voc_dir ./VOC/Annotations yolo_dir ./labels os.makedirs(yolo_dir, exist_okTrue) # 从图片目录读取实际宽高, 不能假设所有图都是同一个分辨率 for xml_file in Path(voc_dir).glob(*.xml): voc_to_yolo(str(xml_file), yolo_dir, 1920, 1080)脚本里把归一化坐标精确到6位小数避免浮点误差在多次RESIZE后累计。实际项目中不要写死分辨率从每张图的size节点读取宽高再传入函数否则一旦测试集中存在不同分辨率的图像生成的坐标会整体偏移。3.2 转换质量校验边界框面积与坐标合法性检查转换完直接开训是典型的错误姿势。要写一个校验器逐行解析生成的YOLO标注检查坐标是否在合法区间以及边界框是否满足最小面积阈值。水下图像噪声大标注员偶发会把边界框画到图像边缘之外归一化后坐标出现负值或大于1的值这类样本在裁剪增强时直接抛异常。# 检查所有txt的非法坐标 grep -rE ^[0-9] (1\.[0-9]|\-) ./labels | head -20除了坐标合法性重点检查类别索引是否越界。YOLOv8的data.yaml里nc参数如果小于数据集实际类别数训练时会在目标分配阶段静默丢弃越界的框导致mAP结果异常但训练不报错。这种坑最容易浪费一整天实验时间。3.3 训练集与验证集的划分策略网上大部分划分脚本用随机抽样但在水下垃圾场景中同一段视频的连续帧会被同时抽入训练集和验证集产生严重的数据泄露验证集指标虚高。这里必须按视频源划分——根据文件名前缀把完整视频的所有帧归入同一个集合。import random from collections import defaultdict video_frames defaultdict(list) for txt in Path(./labels).glob(*.txt): prefix txt.stem.split(_frame_)[0] video_frames[prefix].append(txt.name) all_videos list(video_frames.keys()) random.seed(42) random.shuffle(all_videos) train_vids all_videos[:int(0.8 * len(all_videos))] val_vids all_videos[int(0.8 * len(all_videos)):int(0.9 * len(all_videos))] test_vids all_videos[int(0.9 * len(all_videos)):] with open(train.txt, w) as f: for vid in train_vids: for frame in video_frames[vid]: f.write(fimages/{frame.replace(.txt, .jpg)}\n)前帧重叠区域大的问题被有效缓解。实际验证下来同样的模型在按视频划分的验证集上的mAP比随机划分低5到8个百分点这才是真实泛化水平。4. 模型落地YOLOv8训练水下垃圾检测的配置清单与参数调优4.1 data.yaml配置与锚定框架选择YOLOv8的配置文件是启动训练的第一个环节data.yaml里最关键的是path、train、val、test字段的路径写法和nc、names的定义。水下垃圾场景类别不多但类间相似度高比如bio_plastic和textile在水下成像后纹理特征接近建议在names里保留完整的类别全称不要用缩写。path: /mnt/data/underwater_garbage train: train.txt val: val.txt test: test.txt nc: 7 names: 0: bio_plastic 1: metal_can 2: glass_bottle 3: fishing_net 4: rubber 5: textile 6: other训练命令直接使用YOLOv8官方CLI即可但有几个参数必须根据水下数据特点调整yolo detect train \ modelyolov8m.pt \ datadata.yaml \ imgsz640 \ epochs120 \ batch16 \ workers8 \ device0 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ warmup_epochs3 \ hsv_h0.02 hsv_s0.6 hsv_v0.4 \ degrees15 translate0.2 scale0.5 shear5 \ fliplr0.04.2 数据增强参数水下光照变化与遮挡的针对性处理这里的参数选择有很强的水下场景针对性。degrees15限制了水平旋转角度因为水下垃圾的物理姿态虽然随意但旋转过大产生的无效背景区域会让模型学到错误的上下文fliplr0.0关闭水平翻转因为玻璃瓶、渔网这类目标左右翻转后并不改变类别语义但翻转会造成锚点分布偏移训练后期收敛变慢。水下最重要的增强是色彩通道扰动。不同深度的水质对蓝绿光吸收程度不同hsv_h控制在0.02附近避免色调偏移过大导致水色变成异常色调hsv_s和hsv_v分别用到0.6和0.4模拟能见度变化下饱和度与亮度的退化这部分对模型在水下场景的泛化贡献最大。我看过一些直接用COCO预训练权重做水下检测的项目效果不佳的主因不在迁移学习本身而是没有打开fraction参数。YOLOv8支持用fraction0.5只用一半数据做训练用来快速验证数据格式和训练管线是否通畅确认无误后再全量训练。4.3 损失函数与收敛趋势的异常诊断水下垃圾目标普遍偏小YOLOv8默认的CIoU损失对微小目标的位置误差惩罚不够敏感。训练时观察box_loss曲线如果验证集上box_loss在第50轮后仍然波动剧烈就要考虑把box_loss_gain调高或者改用SIoU损失——SIoU在水平误差和垂直误差上分别计算惩罚项对小目标回归更友好。修改方式不复杂在模型配置里把loss字段改成SIoU后重新加载权重即可。训练日志里另外一个关键指标是cls_loss。水下垃圾类别间相似分类损失降不下去通常不是模型容量不足而是训练集的类别样本数极度不均衡。用torch.utils.tensorboard追踪每个类别的AP值找到拖后腿的类别后单独做数据重采样而不是盲目加epoch。5. 视频推理验证把静态检测器平滑滑入视频流的工程技巧5.1 帧级检测与序列平滑的差异数据集的视频部分来自真实水下拍摄帧间光照变化平缓但漂浮物的位置位移却有明显的物理连续性。直接对每一帧独立检测再硬显示结果会出现目标框前后抖动、类别标签在相邻帧间跳变的问题体验很差。工程上需要做序列级别的后处理。import cv2 from collections import deque class TemporalSmoother: def __init__(self, window_size5, iou_threshold0.3): self.window deque(maxlenwindow_size) self.iou_threshold iou_threshold def update(self, current_boxes, current_classes): # 和窗口里历史帧的框做IoU匹配, 匹配到的框取坐标中值 if len(self.window) 0: self.window.append((current_boxes, current_classes)) return current_boxes, current_classes smoothed_boxes [] smoothed_classes [] for box, cls in zip(current_boxes, current_classes): candidates [] for hist_boxes, hist_classes in self.window: for hb, hc in zip(hist_boxes, hist_classes): iou compute_iou(box, hb) if iou self.iou_threshold and hc cls: candidates.append(hb) if candidates: x_c sum([c[0] for c in candidates]) / len(candidates) y_c sum([c[1] for c in candidates]) / len(candidates) w max([c[2] for c in candidates]) h max([c[3] for c in candidates]) smoothed_boxes.append([x_c, y_c, w, h]) smoothed_classes.append(cls) else: smoothed_boxes.append(box) smoothed_classes.append(cls) self.window.append((current_boxes, current_classes)) return smoothed_boxes, smoothed_classes窗口大小window_size5对应大约0.2秒的时间跨度——在30帧视频里这个窗口既能滤除单帧误检又不会让真实移动目标的位置产生滞后。5.2 置信度阈值的动态校准视频推理的置信度阈值不能照搬静态图验证阶段的固定值。静态图的静态目标检测可以用0.25甚至0.2的置信度但视频帧间的误检会被时间平滑放大频繁闪现建议先用0.4跑一遍验证集统计每类目标的置信度分布后对类别分别设置阈值。玻璃瓶这种纹理清晰的目标可以降到0.3而水草缠绕的捕捞网这种视觉模糊的目标阈值抬到0.55更合理。把阈值配置放到单独的JSON文件里便于部署时按水域环境切换。python detect_video.py \ --weights runs/detect/train/weights/best.pt \ --source data/videos/obj1618.mp4 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --view-img \ --save-txt \ --save-conf--save-conf参数必须带上保存的TXT文件里每一行会追加置信度得分这是后续跟踪算法ByteTrack关联轨迹的必需信息。只保存类别和坐标不保存置信度跟踪器的匹配分数矩阵构建不起来。5.3 建个评估习惯积累工程判断力拿到这套水下垃圾数据集后先做三件事不会错用脚本统计全部XML中truncated和difficult字段的数量人工抽看50张边界框边缘明显被切断的样本用第3章的转换脚本生成YOLO格式并对齐两个目录确认无遗漏跑一遍YOLOv8在imgsz640、epochs50的短训流程观察loss曲线在哪个阶段开始过拟合。这个基线一旦确立后续换模型结构、改损失函数、加数据增强时所有实验都能横向对比效率提升立竿见影。本文还有配套的精品资源点击获取