简介:面向水下机器人、海洋环境监测与计算机视觉算法研究者,这套YOLO海洋水下垃圾检测数据集提供了真实水域场景的高质量图像资源。压缩包共含7667张jpg原图,配套7667个xml(VOC格式)与7668个txt(YOLO格式)标注文件,整体约174MB,标签文件与图像一一对应,按两种格式分目录存放,便于直接切换训练框架。数据针对金属、木材、塑料、橡胶、布料等多类海洋垃圾目标进行框选标注,涵盖不同光照、浑浊度及水底杂物背景,场景丰富且贴近实战;用户既可直接基于YOLO系列模型训练,也可将VOC格式转换后用于其他检测框架。目前已有2080人学习下载,适合作为算法课程设计、毕业论文实验或海洋环保竞赛的基准数据集,能显著减少水下垃圾图像采集与标注的时间投入,将更多精力集中在模型优化与检测精度提升上。
1. YOLO水下垃圾检测数据集:双格式标注与训练流程解析
做水下机器人或近海监测项目时,最先卡住进度的往往不是模型结构,而是训练数据。水下光照偏色、悬浮颗粒遮挡、目标形变严重,一个矿泉水瓶在陆地上是清晰的矩形框,到了水下就变得半透明、扭曲,颜色和背景几乎融在一起。这份YOLO算法海洋水下垃圾检测数据集的做法比较省事:同一批图片同时给出VOC和YOLO两套标注,分别保存在独立文件夹里,几千张经过labelimg逐一核对过的真实场景图片,类别覆盖metal、wood、plastic、rubber、cloth这几类常见垃圾材质。它解决的问题很直接——让你跳过硬啃VOC转YOLO的格式转换,直接进入yolov5或yolov8的训练流程;也适合需要快速验证检测算法在水下场景表现的研究人员做基准测试。
2. 数据集结构与标注格式:把VOC和YOLO两套标签一次看清
2.1 图片、XML与TXT三种文件的对应关系
下载解压后第一件事,别急着开训练,先把目录结构摸清楚。常见做法是数据按如下方式组织:
dataset/ ├── images/ # jpg原图,真实水下场景 ├── VOC/ │ └── label/ # VOC格式标注,每张图对应一个xml ├── YOLO/ │ └── label/ # YOLO格式标注,每张图对应一个txt └── classes.txt # 类别清单,按id顺序排列注意这只是最常见的一种划分方式,不同打包者可能把文件夹命名为Annotations、labels之类,但逻辑一致:图片、VOC标注、YOLO标注三套文件并列,classes.txt写明类别顺序。项目正文里那串obj1602_frame0000087.txt就是YOLO标注文件,文件名与图片名完全一致,仅后缀不同。这个命名规律在写数据加载脚本时可以直接利用:扫描图片目录后做一次后缀替换,就能拿到对应标注路径,不用再额外维护一张映射表。
每张jpg在VOC目录里对应同名xml,在YOLO目录里对应同名txt。xml记录物体类别和绝对像素坐标(xmin、ymin、xmax、ymax),txt记录归一化后的类别id和中心点坐标(class_id、x_center、y_center、width、height),这就是YOLO系框架原生读取的格式。两套格式的差异我用一张表列出来:
| 维度 | VOC格式 | YOLO格式 |
|---|---|---|
| 文件后缀 | xml | txt |
| 坐标记录 | xmin、ymin、xmax、ymax绝对像素 | x_center、y_center、width、height归一化值 |
| 类别记录 | 标签名,如metal、wood | 类别id整数,从0开始 |
| 读取方 | labelimg可复核,xml解析直观 | YOLO训练脚本原生读取 |
为什么作者要同时保留两套格式?因为labelimg在标注时就能同步导出这两种格式,顺手把两份都放进了包里。VOC格式的价值在复核效率:打开xml能看到类名和坐标范围,适合人工抽查;YOLO格式的价值在训练效率:直接把txt目录填进数据配置就能跑,不用每次先转换。对做数据清洗的人来说,我建议以VOC为主、YOLO为辅——检查标注对不对、要不要删掉某个框,用VOC更直观;确认没问题后,训练时直接用YOLO那份。
还有个容易忽视的检查点:三个目录下的文件名集合必须一致。常见做法是写个小脚本对比一下,找出缺xml或缺txt的图片,因为有些工具在标注为空时会生成空文件,有些干脆不生成。这个动作能在训练前暴露数据缺失问题,省得后面训练报出“found no labels”才回头找。
2.2 五个类别的标签分布与场景特点
目标类别是metal、wood、plastic、rubber、cloth,覆盖水下最常见的垃圾材质。从实际拆包经验看,metal类大多是易拉罐、铁丝、瓶盖,wood类多见断木和板材,plastic类以瓶子、包装袋、塑料袋碎片为主,rubber类是轮胎和橡胶管,cloth类主要是渔网、绳索和布料碎片。这五个类别有个共同的训练难点:形态差异极大。金属反光强烈,塑料半透明且容易变形,布料柔软所以框通常很紧,橡胶颜色偏深容易淹没在背景里。同一个“plastic”类目下,硬塑料瓶和软塑料袋的外观差别可能比塑料和布料的差别还大,这也是水下检测mAP上不去的一个重要原因。
场景方面,数据来自真实水下拍摄而不是合成图,因此包含光照偏色、悬浮颗粒、目标部分遮挡等真实干扰。“场景丰富”不是客套话:不同水深、不同水质、不同底质(沙地、礁石、海草区)下,同一类物体的外观差异相当明显。我见过一些合成数据集做得再逼真,模型迁移到实拍视频上mAP还是掉一大截,问题就出在训练分布和真实分布差太远。这份数据在这点上让人放心,至少不用担心训练集和测试集之间存在明显的风格鸿沟。
需要提醒的是,五个类别的实例数大概率不均衡。水下垃圾检测的常见现象是plastic类大量出现,rubber和cloth偏少。类别不均衡会直接反映在mAP上——少数类在验证集里可能只有几十个框,任何一个误检都能让它的AP掉好几个点。所以训练前先做一次统计,心里有数比闷头调参有用得多。
2.3 用脚本盘点数据规模与类别平衡
训练前我先跑一个统计脚本,看每类标注框数量和各图片的标注密度。直接用YOLO格式的txt统计,逻辑最简单:
import os from collections import Counter label_dir = "YOLO/label" file_count = 0 box_count = 0 cls_counter = Counter() for fname in sorted(os.listdir(label_dir)): if not fname.endswith(".txt"): continue file_count += 1 with open(os.path.join(label_dir, fname), encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue # 跳过空行或残缺标注,避免int()报错 cls_counter[int(parts[0])] += 1 box_count += 1 print("标注文件数:", file_count) print("总框数:", box_count) print("类别分布:", dict(sorted(cls_counter.items())))这段代码的核心是逐行解析txt,用Counter统计每个类别id出现的次数。len(parts) < 5 的判断在过滤异常行:一个完整目标行必须有类别id、中心点x、中心点y、宽、高五个字段,少于五个说明标注文件有问题,直接跳过比让程序崩溃好。跑完后如果发现某个类别的数量比最多的那类少一个数量级,后续训练就要重点盯它的AP,必要时单独补充该类数据,或用loss权重平衡。
统计的同时,我还会顺手算一下每张图的平均框数和框的面积分布。平均框数能反映图片拥挤程度——水下垃圾经常多个目标堆在一起,如果大部分图都有三四个以上的框,增强策略里就不要把mosaic开得太大,否则小目标被裁掉一半的概率很高。
3. 把VOC转成YOLO格式:转换脚本与四个边界坑
3.1 为什么同时保留两套格式,以及什么时候必须自己转
这份数据集已经备好双格式,直接能用。但真实项目里常遇到的情况是:队友给了一份纯粹的VOC标注,或者你自己在labelimg里标注时只导出了xml。这时候就得自己转。所以转换脚本不是白写的——它是你的后悔药,拿到任何VOC格式数据都能用同一套流程处理。
转换的原理一句话就能讲清:把VOC里的绝对像素框(xmin、ymin、xmax、ymax)除以图片宽高,变成0到1之间的归一化值,再转成YOLO需要的中心点和宽高(x_center、y_center、width、height)。类别名也要映射成整数id。注意YOLO的类别id从0开始,和classes.txt里的行号对齐,这个细节和后面要讲的坑一直接相关。
3.2 一个可复用的批量转换脚本
我通常把转换脚本写成这样,支持批量处理整个目录:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_list): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in class_list: continue cls_id = class_list.index(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) # 过滤宽或高小于1像素的异常框 if xmax - xmin < 1 or ymax - ymin < 1: continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: with open(out_path, "w") as f: f.write("\n".join(lines)) class_list = ["metal", "wood", "plastic", "rubber", "cloth"] xml_dir = "VOC/label" out_dir = "converted_labels" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(xml_dir): if not fname.endswith(".xml"): continue xml_path = os.path.join(xml_dir, fname) out_path = os.path.join(out_dir, fname.replace(".xml", ".txt")) voc_to_yolo(xml_path, out_path, class_list)逻辑上分三块:第一块解析xml,拿到图片宽高和每个目标的类别与框坐标;第二块做坐标换算,并把异常框过滤掉;第三块遍历整个目录批量执行。参数说明里值得注意的几点:类名在比较前做了strip(),防止xml里混入空格或换行符导致匹配不上;归一化保留6位小数,精度足够训练;过滤条件写在换算之前,既能避免宽高为负,也能避免生成除零错误。
3.3 四个边界坑:路径、越界、类别id、空文件
这个脚本一开始写的时候我也翻过车,踩过几个比较典型的坑,列出来供参考。
坑一:类别id从1开始数。有同事的习惯是从1开始编号类别,转出来的txt第一列是1、2、3,训练时YOLO强制从0开始,等于所有类别整体错位一位。现象就是训练能跑、loss能降,但预测时把metal的框标成wood。解决方法是每次转换后随机抽几个txt,用labelimg的YOLO模式打开核对类别id是否与classes.txt对齐。
坑二:坐标越界和宽高为零。xml里偶尔会出现xmax小于xmin的反向框,或者宽高只有0.5像素的碎框。转换后w或h为负数时,YOLO训练会直接报错,或者静默地把它当背景忽略,导致标注白白丢失。解决方法是像脚本里那样,在转换时就把宽高小于1像素的框过滤掉,再对归一化结果做一次clip(0, 1)兜底,防止某个坐标写成1.05。
坑三:路径分隔符在Windows和Linux间不一致。在Windows上生成的路径是反斜杠,写进data.yaml后在Linux服务器上跑,反斜杠会被当成转义字符,轻则路径解析失败,重则直接找不到图片。解决方法是统一用正斜杠,或在代码里用os.path但最后打印一次解析后的路径确认。
坑四:空标注文件处理。有些工具对没有目标的图片生成一个空txt,有些则直接不生成tar。yolov8对空文件会报warning然后跳过,但如果你在划分train/val时按文件名匹配,空文件会导致图片没有标注,训练时该图被当成纯背景,数量多的话会明显误导模型。解决方法是统计后手动决定这些空标注图片是删除还是保留,我一般只保留有目标的图片参与训练。
4. 用YOLOv8训练水下垃圾检测模型:环境配置到损失函数观察
4.1 环境配置:CUDA版本与ultralytics安装
yolov8训练自己的数据集,环境配置是第一道坎。“yolo v8 anaconda环境配置要求”这个搜索词出现频率很高,卡住的人大多栽在同一个地方:没先装PyTorch就直接装ultralytics,结果装上了CPU版本。我的固定流程是用conda建独立环境、固定Python版本、先装PyTorch再装ultralytics:
conda create -n yolo python=3.9 -y conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics安装完成后先验证GPU是否可用,这一步别跳过:
python -c "import torch; print(torch.cuda.is_available())"输出True说明CUDA版PyTorch装好了;输出False则多半是CUDA驱动版本与PyTorch的cu118不匹配,或者机器上根本没有NVIDIA显卡。这类问题排查起来比较费时间,我一般先用nvidia-smi看驱动支持的CUDA版本,再选择对应的PyTorch安装源,而不是随便装一个版本。ultralytics和onnxruntime、opencv这些依赖包之间偶尔也有版本冲突,如果import时报依赖错误,优先看报错堆栈里是哪个包不兼容,用pip install “包名==指定版本”回退即可。
4.2 数据集yaml的写法与路径陷阱
ultralytics训练前需要把数据组织成它期望的目录形态。常见做法是分成train和val两个子集,图片放images目录、标注放labels目录:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml然后写data.yaml:
train: dataset/images/train val: dataset/images/val nc: 5 names: ["metal", "wood", "plastic", "rubber", "cloth"]train和val的路径我建议在yaml里写相对路径,但要确保从终端启动训练时的工作目录能解析到。更稳妥的做法是写绝对路径,但注意整个项目移动后要同步修改yaml。nc和names是绑定的,nc必须等于names列表的长度,否则训练会直接报错或者类别数对不上;names的顺序必须和classes.txt保持一致,否则检测出来的类别名就是错位的。划分train/val时保证同一张图片的jpg和txt在对应目录里同名,不要把一张图同时放进train和val,这会导致验证集虚高。
注意:labels目录下的txt文件名必须与images目录下的jpg文件名完全一致,只差后缀。yolov8按文件名匹配图片和标注,多一个空格或少一个下划线都会导致该图变成无标注的纯背景样本。
4.3 训练命令与损失函数曲线的观察重点
数据准备好后,用一条命令启动训练:
yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16解释一下参数:model=yolov8n.pt是官方预训练权重,适合在已有视觉特征上微调,比从零训练收敛快很多;imgsz=640是训练输入尺寸,水下小目标多的话可以升到1280;batch=16是批大小,显存不够就降到8。训练时终端会打印每一轮的box_loss、cls_loss和dfl_loss,很多人只盯着总loss看,实际上要分开看:box_loss反映定位误差,cls_loss反映分类误差。水下场景里cls_loss迟迟不降,最常见的原因是难分类别太多——半透明的塑料和布料的边界在低光下本来就模糊,如果标注框又松,模型根本学不到可区分的特征。
mAP曲线也能看到同样的趋势,但mAP是最终结果,loss是过程信号。我一般会在训练跑到一半时看一眼P曲线和R曲线,P高R低说明模型偏保守,宁可漏检也不误检;R高P低说明模型偏激进,框出大量背景。水下垃圾检测的业务场景通常希望R优先——漏掉一个塑料瓶比误检一块石头代价更大。这时候可以在训练结束后调整置信度门限,把模型的行为推向目标方向,这个技巧在最后一章展开。
epoch的设置也是新手容易踩的点。水下数据集几千张图,100个epoch通常足够,但如果你发现val_loss在50轮以后反而回升,说明过拟合了,应该减少epoch或加大数据增强。yolov8默认开了mosaic和mixup,对水下这种背景复杂的场景很有帮助,但如果目标本身很小,过强的mixup会把目标混没,这种情况建议适当降低增强强度。
5. 训练避坑指南:标注错位与mAP异常的5条排查记录
这一章是我反复在同一个数据集上折腾积累下来的排查记录,按现象、原因、解决的顺序写,每条都是实际踩过的坑。
5.1 现象:训练中途报错,提示标签文件读取失败
原因:数据里有空txt或只有一列数据的文件。yolov8读取标注时要求每行至少五个数字,遇到空文件会报warning或跳过,遇到残缺行可能直接抛错中断。这个情况在labelimg打标后只保存了类别、没画框的文件里很常见,也可能是转格式时脚本漏输了坐标列。
解决:训练前统一做一次清洗。扫描所有txt,把行数不足5列的行删掉,把空文件单独拎出来计数。我习惯写一个清洗脚本,同时把归一化坐标clip到0到1之间,防止某个框的坐标写成1.05导致训练时越界报错。
5.2 现象:训练能跑完,但预测时所有类别整体错位一位
原因:类别id对照表错位。最典型的就是classes.txt里塑料类被拼成platstic——这个拼写错误在真实数据里出现的频率比想象中高很多,或者VOC标注里类名一会儿是wood一会儿是Wood,大小写不统一。如果转换脚本里的class_list按标准拼写写,那拼错的类别就会被静默跳过,导致某些图缺标注。更隐蔽的情况是class_list顺序和训练yaml里的names顺序不一致,txt里的id对应上另一个类。
解决:统一拼写和顺序。用脚本扫描所有xml里的name标签,打印出所有出现的类别名,再用统计图核对五类各自的框数。改完后重新生成txt,并在训练前打印一次txt第一行的类别id和names对照,确认id 0对应metal、id 1对应wood这样的映射没有错位。
5.3 现象:mAP@0.5不低,但实拍水下视频里漏检一大片
原因:验证集分布和实际使用场景分布不一致。训练时mAP是在同一批分布的数据上算的,如果验证集里目标都比较大、遮挡少,分数自然好看;实际水下视频里光照不同、目标小、很多目标半埋在沙里,模型没见过这种分布,自然检测不到。另一层原因是置信度门限设得太高,默认0.25在清晰场景下问题不大,但在水下低对比度场景里,许多正确框的置信度只有0.2左右,被直接滤掉了。
解决:先别动网络结构和训练参数,把置信度门限降到0.1或0.05再看检测结果。如果降门限后大量目标浮现,说明模型本身学到了特征,只是输出分数偏低;如果降了也没反应,说明训练分布确实缺这类样本,需要补充数据。这一步能帮你快速区分是模型不会还是门限卡掉。
5.4 现象:小目标完全检测不到,大目标框得不错
原因:输入尺寸太小或下采样过深。imgsz=640时,一个直径只有10像素的小塑料片经过多次下采样,特征图上只剩不到1个像素,检测头根本找不到它。水下垃圾里有大量这样的碎片目标,这也是很多人用默认配置训练水下数据时小目标AP接近于0的原因。
解决:把imgsz提到1280,训练和预测保持一致;显存不够就降batch。另一个方案是启用yolov8的P2检测层,或用带更强小目标特征的模型做辅助。实测在相同数据集上,imgsz从640提到1280,小目标AP通常能提升5个点以上,代价是训练时间翻倍。
5.5 现象:loss曲线震荡不收敛,mAP在某个值附近反复横跳
原因:学习率太高加batch太小,或者标注里混入了不少错误框。yolov8默认的学习率对多数场景是稳的,但水下数据标注质量参差,如果某张图里10个框中有3个框标偏了,模型就会在正确和错误的梯度方向上来回拉扯。训练日志里box_loss忽高忽低,val mAP跟着一起抖,就是典型的被脏数据带偏的信号。
解决:先用小学习率(比如0.001)跑一遍确认loss能平缓下降,再逐步调回去。同时抽检loss最高的那批图片,按loss从大到小排序打印图片路径,人工看一眼是不是标注问题。这个按loss找脏数据的方法比全量复核高效得多,我后来对每一份新数据集都会跑一遍。
6. 验证技巧:置信度门限调整与可视化检测
6.1 用conf-thres把漏检和误检调到业务可接受的状态
训练完的模型默认置信度门限是0.25,对水下垃圾场景通常不合适。推理时显式指定门限:
yolo detect predict model=runs/detect/train/weights/best.pt \ source=test_images/ imgsz=640 conf=0.1 iou=0.5conf=0.1表示只保留置信度高于0.1的框,iou=0.5是NMS去重时的IoU阈值。水下低对比度场景里,把conf从0.25降到0.1常能让召回率显著回升,代价是误检增多。我一般先跑一遍0.25,再跑0.1,对比两批结果里的目标数量,就能估算出模型的置信度分布区间。比如0.25检出80个框、0.1检出140个框,说明有近一半的正确框落在0.1到0.25之间,那业务上就该用0.1附近的门限。
6.2 可视化输出与置信度分布二次校验
光调门限还不够,我会用一个脚本统计验证集所有预测框的置信度分布,而不是只看几张效果图。分布图能直接反映模型是犹豫型还是自信型:如果大部分正确框的置信度都集中在0.8以上,说明模型学得很好;如果集中在0.3附近,说明训练数据里类别混淆严重,光调门限只能缓解一时,根本上还是要补充难例。从那以后,我每次拿到新的水下数据集,都会强制走一遍“统计类别分布、清洗标注、转换核对、小学习率试跑、按loss抽检脏数据、推理时对比两个门限”这个流程,整套下来基本没再出过岔子。这份数据集本身就带好了VOC和YOLO两套标注,拿到手先按第2章的脚本盘一遍,再走第4章的流程训练,最磨人的预处理阶段可以省掉大半。希望这些能帮到你。
本文还有配套的精品资源,点击获取