ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

水下垃圾检测实战:YOLOv8数据集训练与标注全流程解析

水下垃圾检测实战:YOLOv8数据集训练与标注全流程解析 简介面向水下环境监测与深度学习目标检测研究者这份数据集围绕水下垃圾自动识别与定位任务构建提供了一套结构完整的训练与验证语料。压缩包整体约173.21MB共2000个文件主要包含jpg原始图像、xml标注文件和txt标签说明其中xml记录目标类别及边界框位置txt便于在YOLO等框架中直接读取文件按test、train、val划分为三个目录分别用于模型评估、参数学习和训练调优。目前已有659人学习下载。数据集内容贴近真实水下场景可帮助使用者减少数据采集与清洗成本直接投入到垃圾检测模型的实验比对中。结合YOLO、Faster R-CNN等主流算法能够系统练习数据预处理、模型训练、验证集调参和测试集泛化评估的完整流程适合环境保护领域的研究人员、学生以及计算机视觉开发者作为标准化训练基准。1. 从标注文件到自动清理这个数据集解决了水下感知的哪个关键缺口做水下垃圾检测的人往往先被数据劝退公开的 KITTI 数据集以道路场景为主水下环境缺少带标注样本而自己下水拍摄又涉及设备、光照和标注成本。这份水下垃圾检测数据集.zip把 train/val/test 三部分和 YOLO 格式的 txt 标注直接打包解压后就能用于训练。第一次接触时真正需要花时间的不是模型结构而是理解标注的归一化坐标、调整水下图像增强策略以及正确配置 YOLOv8 的训练流程。下面从标注文件本身拆起给出检查脚本、训练命令和部署验证路径让拿到压缩包之后可以直接动手复现。2. YOLO 标注格式与数据集目录剖析先别急着开训2.1 解压后先看目录文件分布与命名规则拿到 zip 后我一般会先把它解压到一个不含中文和空格的路径例如~/datasets/underwater_garbage然后使用tree或find快速看清顶层结构。这个数据集的压缩包内部通常遵循images与labels分离的布局train、val、test 三个子集要么平铺在各自目录下要么再按images/train、labels/train分两层。从文件名列表来看标注文件形如obj1663_frame0000562.txt其中obj1663表示原始视频采集中某一段目标的编号frame0000562表示帧序号这种命名保留了下水采集源的时间信息方便后续在视频上做序列级验证。# 解压到指定目录-d 保证不散落当前路径 unzip underwater_garbage_detection.zip -d ~/datasets/underwater_garbage cd ~/datasets/underwater_garbage # 查看所有子目录确认 train/val/test 是否齐备 find . -type d | sort # 统计标注文件与图片数量先做初步匹配 find . -name *.txt | wc -l find . \( -name *.jpg -o -name *.png \) | wc -l第一条命令的-d指定解压目录避免解压后文件堆在当前工作目录随后cd进入数据目录。第三条命令按字母顺序打印所有子目录能立刻看出是否存在 test、train、val 三个集合。第四条统计 txt 总数第五条统计 jpg 和 png 图片总数两者数量接近才说明标注基本配套。如果图多标少常见原因是某些视频帧没有垃圾对象对应的空 txt 被省略这需要在训练时让 YOLO 接受空标签图。还需要检查同一个文件前缀在images和labels下是否都存在。若只有obj1663_frame0000562.txt而没有对应图片通常是压缩包不完整或者切分脚本误删了样本这种文件会导致后续训练时 YOLO 报出类似image not found的错误。常见做法是写一个循环# 遍历 labels 下的每个 txt验证是否有同名图片 for f in labels/*.txt; do base$(basename $f .txt) if [ ! -e images/$base.jpg ] [ ! -e images/$base.png ]; then echo missing image for $base fi done命令中basename $f .txt去掉后缀得到obj1663_frame0000562这样的前缀然后分别检查.jpg和.png是否存在。若输出内容过多可以先重定向到文件再人工抽查因为单张缺失图并不会立刻中断训练只会在某个 epoch 报错提前排查能节省大量时间。2.2 txt 标注中的归一化坐标四字段的完整解读YOLO 格式的 txt 每一行代表一个目标格式与 COCO 的[xmin, ymin, xmax, ymax]完全不同很多新手会在这里被卡住。打开任意一个标注文件内容大概是这样0 0.482812 0.523611 0.184375 0.252778 1 0.317188 0.659722 0.109375 0.136111第一列是类别编号必须从 0 开始第二、三列是目标框中心点的横纵坐标都除以了图像宽高第四、五列是框的宽和高也做了同样的归一化所以后四个值理论上都在 0 到 1 之间。这种设计的好处是与图像绝对尺寸无关模型不需要在输入层再做一次像素映射。字段含义值域第1列类别编号0 ~ (类别数-1)第2列中心点 x / 图像宽0 ~ 1第3列中心点 y / 图像高0 ~ 1第4列目标框宽 / 图像宽0 ~ 1第5列目标框高 / 图像高0 ~ 1这里需要特别注意类别编号是否连续、是否从 0 开始直接决定了后面 YAML 文件里的nc值。如果某个 txt 中出现类别编号 9那么至少要有 10 个类别即使其中有些类别在训练集中一个样本都没有也必须把空位保留否则模型会无法对齐输出层。有的工具导出的坐标是像素绝对值例如[xmin, ymin, xmax, ymax]这时需要转换# COCO/像素坐标 转 YOLO 归一化坐标 x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height w (xmax - xmin) / image_width h (ymax - ymin) / image_height转换公式的逻辑是先把左上角与右下角坐标取平均得到中心点再用像素宽度除以图像宽度做归一化最终结果必须落在 0~1 区间内。如果不确定image_width从哪来用cv2.imread读图后取shape[1]和shape[0]即可。2.3 写一个检查脚本把坏标注挡在训练前训练前最值得做的一件事是批量校验标注文件的合法性。我一般会在数据根目录下放一个check_labels.py用几秒钟跑完所有文件避免训练中途才发现标注格式错误。脚本如下# check_labels.py校验 YOLO txt 标注的字段数量与取值范围 from pathlib import Path labels_dir Path(labels) totals {} errors [] for txt in labels_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: errors.append(fwrong field count: {txt.name} - {line}) continue try: cls, x, y, w, h map(float, parts) except ValueError: errors.append(fnot float: {txt.name} - {line}) continue if not (0 cls 100 and 0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): errors.append(fout of range: {txt.name} - {line}) continue totals[int(cls)] totals.get(int(cls), 0) 1 print(class frequency:, dict(sorted(totals.items()))) print(error count:, len(errors)) for e in errors[:20]: print(e)脚本分为三部分第一部分读入整个 txt用read_text().strip().splitlines()拿到非空行若文件内容为空则不会进入循环因此空标注不会报错第二部分对每一行按空格拆分字段数不是 5 就记录错误再通过map(float, parts)做类型转换转换失败说明混入了标点或特殊字符第三部分做范围检查cls的界限放宽到 100只排除明显异常四个坐标则严格要求在 0~1 之间。最后打印每个类别的样本次数和错误列表可以快速评估类别分布是否均匀。要注意labels_dir.glob(*.txt)只覆盖当前目录如果数据按 train/val/test 拆分最好改用labels_dir.rglob(*.txt)递归查找子目录。另外不要只看错误数还要看 class frequency 是否为 0。比如类别 3 完全没有样本可能是原始标注里该物体太少也可能是切分时把包含该类别的图片全分到了测试集这时候你需要在全部数据范围内重新统计一遍而不是靠训练集样本数去猜。3. YOLOv8 训练水下垃圾检测模型从 yaml 到完整流程3.1 按 YOLOv8 的预期重新组织数据目录YOLOv8 的训练流程和 YOLOv5 非常接近都需要一个 YAML 文件来描述数据根目录、训练集图片路径、验证集图片路径、测试集图片路径、类别数和类别名。许多自带标注的数据集压缩包并不一定按这个规范组织常见的问题是图片和标注混放在同一个目录或者 train/val/test 是按原始采集日期分的而不是随机分配。如果你之前只跑过YOLOv5 训练自己的数据集那这里可以直接沿用相同的目录结构。推荐的结构如下datasets/ └── underwater_garbage/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/images下放图片labels下放同名 txt训练时 YOLO 会根据图片路径自动查找对应标签。如果原始目录不是这个结构比如所有图像在images/下、所有标注在labels/下且没有按 train/val/test 划分可以用下面的 shell 快速按 8:1:1 比例划分# 创建目标子目录 mkdir -p images/{train,val,test} labels/{train,val,test} # 遍历 images 下所有 jpg按随机数划分到三个子集 for f in images/*.jpg; do base$(basename $f .jpg) n$((RANDOM % 10)) if [ $n -lt 8 ]; then splittrain; elif [ $n -lt 9 ]; then splitval; else splittest; fi mv $f images/$split/ mv labels/${base}.txt labels/$split/ done这里用$RANDOM % 10生成 0 到 9 的随机数小于 8 进 train等于 8 进 val等于 9 进 test。比例约为 8:1:1但随机划分不能保证类别平衡对类别不平衡的数据集后续要再统计一下。划分后需要定义 YAML# 数据配置文件路径建议写绝对路径 path: /home/user/datasets/underwater_garbage train: images/train val: images/val test: images/test nc: 4 names: 0: plastic 1: net 2: bottle 3: canpath是数据根目录train和val分别填写相对path的路径nc要与检查脚本统计到的最大类别编号 1 相等。names列表的下标就是标注文件里的数字类别编号不能打乱顺序。比如原数据集的类别定义可能是0为bg、1为fish你需要在names中保留bg的索引位置即使模型最终不预测背景。3.2 训练命令与关键超参数如何兼顾精度与速度完成目录组织后安装最新的 ultralytics 包然后运行pip install ultralytics # 使用 yolov8n 起步epochs 设 150patience 为 20 yolo detect train dataunderwater_garbage.yaml modelyolov8n.pt \ epochs150 imgsz640 batch16 patience20 \ projectruns/underwater exp_nameexp_1这里选用yolov8n.pt是因为水下机器人通常部署在嵌入式设备上n 的参数量约 3.2M、计算量约 8.7 GFLOPs中等 GPU 也能很快跑完几百轮。imgsz640表示输入边长如果原图像素较高且显存充足提高到 1280 可以改善小目标检测但训练时间接近翻倍。batch16在 24GB 显存上没有压力如果是 8GB 显卡就降到 8再小的话利用参数累积模拟更大的 batch。参数默认值说明epochs100总训练轮数水下数据集样本少可用150batch16每个batch的图片数按显存调整patience10当验证指标连续N轮不提升时早停lr00.01初始学习率loss震荡时降到0.001imgsz640输入图像边长边做letterbox缩放workers8数据加载进程数Windows下设为0很多教程不会强调patience但对水下视频帧这种高度相关的数据早停很有用。因为相邻帧之间相似度太高验证集指标可能在前几轮虚高后期才会回落。如果patience设置太小模型会在真正收敛前停掉我一般用 20配合save_period10保留中间权重这样即使某轮指标波动也不会丢失前期的有效权重。训练时还要看终端里打印的参数数量。如果看到类似Model summary: 225 layers, 3157216 parameters的输出且参数总量明显少于预训练权重说明的体量可能是模型文件被误用成了其他格式。这个检查只需要一秒钟但能避免浪费一整轮训练。3.3 从训练日志中提前发现过拟合YOLOv8 训练时会在终端同时输出train/box_loss、train/cls_loss和val/box_loss等指标。最常见的陷阱是只盯着train loss看到它下降到很低就以为模型很好。需要把训练损失和验证损失放在同一张图上判断。训练结束后runs/underwater/exp_1/results.csv里已经保存了每个 epoch 的所有指标用 Python 直接画# 绘制训练与验证 box_loss 曲线 import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/underwater/exp_1/results.csv) print(df.columns.tolist()) # 确认列名 cols [c for c in df.columns if box_loss in c] df[cols].plot(logyTrue) plt.savefig(loss_curve.png, dpi150)先打印列名避免不同版本的 ultralytics 命名不一致然后用logyTrue对数坐标因为 box_loss 通常呈指数下降普通线性坐标下前期下降速度会掩盖后期差异。如果验证损失持续上升而训练损失下降说明过拟合。应对手段包括增大patience后的早停、把dropout0.1调大、增加在线增强强度、或换成参数量更小的模型。另一个常被忽略的信号是mAP50很高但mAP50-95很低。mAP50要求预测框与真实框的 IoU 超过 0.5 就算正确而mAP50-95会在 0.5 到 0.95 之间取多个阈值对定位精度更敏感。水下垃圾的形状不规则如果出现这种差距优先检查标注框是否存在外包太多背景或坐标错位而不是盲目换模型。可以用yolo detect val在验证集上生成预测可视化逐张查看误差大的样本。4. 水下低照度与后向散射用图像增强提升泛化4.1 水下图像退化的物理模型与常见预处理选型水下图像主要受到吸收与散射两类影响水对不同波长的光吸收率不同红光衰减最快蓝绿光衰减慢所以深水照片往往偏绿或偏蓝悬浮颗粒造成后向散射使图像蒙上一层雾或噪点。对比陆地上用 COCO 预训练权重模型在海面以上学到的大量颜色和纹理线索在水下失效这时单靠调超参数很难继续提升精度。最有效的办法是在进入 YOLO 之前做一次颜色校正和对比度增强。常见的预处理选型包括直方图均衡化HE可以对整图拉伸对比度但当图像有局部过曝或阴影时容易破坏细节限制对比度自适应直方图均衡CLAHE则在局部小块上做均衡并用截断值限制增强幅度更适合水下不均匀光照。还有灰度世界算法假设全局平均色接近灰色通过缩放 R/G/B 通道来消除色偏。下表列出各自的适用场景方法适用场景主要缺点直方图均衡 (HE)整体对比度低会放大偏色CLAHE局部光照不均需要调 clipLimit灰度世界全局色偏大面积水体时估计不稳UDCP浑浊水体计算量大在线增强不现实具体使用哪个可以在小样本上训练两三轮看验证集趋势不必一开始就追求复杂的恢复算法。至少我处理这份数据时CLAHE 和灰度世界的组合在低成本场景下提升最直观。4.2 CLAHE 离线增强不改变标注的预处理实操如果决定离线增强需要注意增强只作用于图片不修改 txt 标注。由于归一化坐标与像素分辨率无关只要图片尺寸不变标注就完全不需要动。下面这个脚本遍历所有训练集图片转换为 LAB 色彩空间后只对亮度通道做 CLAHE再转回 BGR 输出# 对训练图像批量做 CLAHE 增强保留色度通道 import cv2 from pathlib import Path src_dir Path(images/train) dst_dir Path(images_enhanced/train) dst_dir.mkdir(parentsTrue, exist_okTrue) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) for img_path in src_dir.glob(*.jpg): bgr cv2.imread(str(img_path)) if bgr is None: print(fread fail: {img_path}) continue lab cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) l_enhanced clahe.apply(l) # 只增强亮度 merged cv2.merge((l_enhanced, a, b)) enhanced cv2.cvtColor(merged, cv2.COLOR_LAB2BGR) cv2.imwrite(str(dst_dir / img_path.name), enhanced)逻辑说明createCLAHE(clipLimit2.0, tileGridSize(8, 8))创建增强器clipLimit是直方图截断上限值越大对比度越强tileGridSize是局部块大小。对 BGR 图像先转 LAB是为了把亮度与颜色分离只对亮度通道L做增强保留a和b色度通道避免色彩过度饱和。最后写回与源文件同名的图片。你可以在val上同样做一遍但测试集最好保持原始状态用于评估预处理在真实部署时是否介入。有一个容易踩的坑cv2.imread读不到路径时会返回None脚本会直接异常退出所以循环里加了if bgr is None判断。在批量处理前最好先打印任意一张图的大小确认为(height, width, 3)。如果图片带 alpha 通道COLOR_BGR2LAB会报错需要先转成 BGR。4.3 在线数据增强配置让模型自己适应多变水体离线增强只能覆盖一个固定颜色空间变换实际部署时水下机器人的光源、深度、水体浊度都会变化更稳妥的方式是利用 YOLOv8 自带的在线增强在每次读取训练图片时动态施加随机扰动。下面这条命令在之前基础上调整了hsv_h、hsv_s、hsv_v和degrees# 加大亮度和饱和度扰动模拟不同水体 yolo detect train dataunderwater_garbage.yaml modelyolov8n.pt \ epochs150 imgsz640 batch16 \ hsv_h0.02 hsv_s0.7 hsv_v0.5 degrees10解释一下关键参数hsv_h是色调扰动幅度0.02 相当于在色环上轻微偏移能模拟不同水色下微小的色调差异调太大可能会改变物体原本颜色语义hsv_s是饱和度扰动水下图像饱和度通常偏低设到 0.7 可以让模型见到从灰白到高饱和的渐变hsv_v是亮度扰动0.5 是较大范围因为水下闪光灯会产生高亮区域深水区域又可能存在阴影。degrees10表示最多随机旋转 10 度水下机器人姿态相对稳定旋转太大会制造不合理的姿态。YOLOv8 的mosaic增强默认开启它把 4 张训练图拼接成一张用于提升模型在不同背景下的泛化能力。但在水下垃圾检测中很多目标是小尺寸垃圾拼接后目标会被切到边界甚至产生大量半截目标。如果训练 50 轮后box_loss下降很慢可以尝试mosaic0.5# 将 mosaic 启用概率降到一半保留部分原始图 yolo detect train dataunderwater_garbage.yaml modelyolov8n.pt \ epochs150 imgsz640 batch16 mosaic0.5mosaic0.5表示一半的训练样本使用马赛克增强另一半用原始图这样既保留拼接带来的背景多样性也降低目标被截断的影响。跨 batch 的训练结果很难严格对比所以每次改参数时建议单独建一个exp_name方便后续比较。5. 模型验证与导出用 mAP50-95 判断是否真正可用5.1 val 模式下的指标怎么看训练结束后首先执行# 在验证集上评估输出 Precision/Recall/mAP yolo detect val modelruns/underwater/exp_1/weights/best.pt dataunderwater_garbage.yaml这个命令会跑一遍验证集并在终端打印Precision、Recall、mAP50、mAP50-95。我通常把mAP50-95作为第一参考值。如果它过低而mAP50很高说明框不够精确可以尝试增大imgsz到 960 或 1280或者从yolov8n换到yolov8m。不要一上来就动模型复杂度先确认数据划分有没有问题。5.2 导出 ONNX 并在边缘设备上推理部署时最常见的转换格式是 ONNX# 导出 ONNXopset 12 对老设备兼容性更好 yolo export modelruns/underwater/exp_1/weights/best.pt formatonnx imgsz640 opset12导出的best.onnx可以用onnxruntime运行输入是[1, 3, 640, 640]的归一化图像输出通常两到三个 tensor需要做非极大值抑制。这里不展开全部推理代码但要注意预处理要和训练时一致包括 letterbox、BGR 转 RGB、归一化到 0~1。若部署在 Jetson 上可以用 TensorRT 进一步提速但第一次导出先用 ONNX 验证精度对齐避免引入新的不确定性。5.3 一个容易被忽略的坑zip 解压与文件配对最后补充一个处理压缩包本身的细节。下载完水下垃圾检测数据集.zip后先用unzip -t校验完整性# 测试 zip 是否完整不实际解压 unzip -t underwater_garbage_detection.zip输出末尾有No errors detected in compressed data之类的信息才算通过。如果提示error read zip archive或crc error通常是下载过程中文件损坏此时不要强行解压否则后续训练会出现莫名奇妙的缺图问题。解压后还要清理隐藏文件# 删除 macOS 的元数据目录和索引文件 find . -name __MACOSX -type d -exec rm -rf {} find . -name .DS_Store -type f -delete这两条命令把 macOS 打包压缩时带入的元数据目录和索引文件删掉否则find统计文件数量时会混入无关内容训练脚本也可能因为读取了._*.txt这种 AppleDouble 文件导致标签格式解析失败。做完这些再回到 5.1 的 val 流程重新跑一次yolo detect val看看指标是否稳定并对比mAP50-95有没有下降。本文还有配套的精品资源点击获取
返回列表