简介:一套面向水下环境垃圾识别与分类任务的数据集,同时提供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> <folder>underwater_garbage</folder> <filename>obj1051_frame0000012.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>plastic_bottle</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>412</xmin> <ymin>318</ymin> <xmax>689</xmax> <ymax>632</ymax> </bndbox> </object> </annotation>bndbox里的xmin、ymin、xmax、ymax均为像素绝对坐标,左上角为原点。多数VOC格式依赖的检测框架(如Faster RCNN的VOC分支)直接读取这套字段。需要注意的是,difficult=1的样本在默认评估时会被忽略,水下垃圾数据里这类样本常常是严重遮挡或远距离目标,项目里具体怎么处理要看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', encoding='utf-8') as f: f.write('\n'.join(lines)) if __name__ == '__main__': voc_dir = './VOC/Annotations' yolo_dir = './labels' os.makedirs(yolo_dir, exist_ok=True) # 从图片目录读取实际宽高, 不能假设所有图都是同一个分辨率 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(f"images/{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 \ model=yolov8m.pt \ data=data.yaml \ imgsz=640 \ epochs=120 \ batch=16 \ workers=8 \ device=0 \ optimizer=AdamW \ lr0=0.001 \ lrf=0.01 \ warmup_epochs=3 \ hsv_h=0.02 hsv_s=0.6 hsv_v=0.4 \ degrees=15 translate=0.2 scale=0.5 shear=5 \ fliplr=0.04.2 数据增强参数:水下光照变化与遮挡的针对性处理
这里的参数选择有很强的水下场景针对性。degrees=15限制了水平旋转角度,因为水下垃圾的物理姿态虽然随意,但旋转过大产生的无效背景区域会让模型学到错误的上下文;fliplr=0.0关闭水平翻转,因为玻璃瓶、渔网这类目标左右翻转后并不改变类别语义,但翻转会造成锚点分布偏移,训练后期收敛变慢。
水下最重要的增强是色彩通道扰动。不同深度的水质对蓝绿光吸收程度不同,hsv_h控制在0.02附近,避免色调偏移过大导致水色变成异常色调;hsv_s和hsv_v分别用到0.6和0.4,模拟能见度变化下饱和度与亮度的退化,这部分对模型在水下场景的泛化贡献最大。
我看过一些直接用COCO预训练权重做水下检测的项目,效果不佳的主因不在迁移学习本身,而是没有打开fraction参数。YOLOv8支持用fraction=0.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_size=5, iou_threshold=0.3): self.window = deque(maxlen=window_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_size=5对应大约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在imgsz=640、epochs=50的短训流程,观察loss曲线在哪个阶段开始过拟合。这个基线一旦确立,后续换模型结构、改损失函数、加数据增强时,所有实验都能横向对比,效率提升立竿见影。
本文还有配套的精品资源,点击获取