ARTICLE DETAIL

资讯详情

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

水下垃圾检测数据集实战:VOC/YOLO/JSON三格式解析与YOLOv8训练避坑指南

水下垃圾检测数据集实战:VOC/YOLO/JSON三格式解析与YOLOv8训练避坑指南 简介这份水下垃圾检测数据集面向从事水域环境智能监测、水下机器人视觉与目标检测算法实践的开发者与学习者可用于课程作业、学科设计、竞赛及实际项目中的模型训练与验证。数据集共5328张水下机器人实拍图像标注精准、分布均匀涵盖水下生物、塑料垃圾、金属垃圾、木头垃圾、橡胶垃圾、布料垃圾、捕鱼工具垃圾七类目标贴合真实水下场景的多样性。压缩包内文件总数达21313个包含5328张jpg原图以及voc格式xml、yolo格式txt和json三种标注文件各5328份可满足YOLO、Faster R-CNN等主流检测框架的直接读取与训练需求整体约127.54MB目录组织清晰便于检索。目前已有325人学习下载适合希望快速搭建水下垃圾识别基线、开展算法对比实验或完成相关课题的读者参考使用。1. 水下垃圾检测数据集怎么选5328 张、7 类、三种标签格式意味着什么做水下垃圾检测第一个卡住大多数人的不是模型结构而是数据。真实水下图像普遍偏色、浑浊、光照不均公开可用的标注数据少得可怜自己下水拍一圈再标一遍成本高到劝退。所以当有人整理出一份「7 类、5328 张、同时带 VOC(xml)YOLO(txt)JSON 三种标签」的水下垃圾检测数据集时它解决的其实是三件事省掉采集和标注的人力、直接对接主流检测框架、以及给多任务检测/分割/可视化留出扩展口。这份数据集的核心价值在于「一份图、三套标签」。VOC 的 xml 是 Pascal VOC 时代的通用格式很多老脚本、可视化工具、标注软件默认吃它YOLO 的 txt 是归一化后的class cx cy w hUltralytics 系YOLOv5/v8/v11训练直接读JSON 则常见于 COCO 风格或自定义结构方便做统计分析、格式转换和跨框架迁移。7 个类别通常覆盖塑料瓶、塑料袋、渔网、金属罐、玻璃瓶、橡胶、其他杂物这类水下常见垃圾具体类别名以数据集自带的classes.txt或data.yaml为准不要凭猜。适合谁想快速跑通一个水下垃圾检测 baseline 的学生和工程师、需要做水下机器人/无人船视觉模块的团队、以及想拿真实脏数据练手数据清洗和格式转换的人。下面从格式拆解讲到训练落地再讲踩坑尽量让你照着能复现。2. 三种标签格式拆开看VOC、YOLO、JSON 各自怎么读怎么写2.1 VOC xml 的结构与解析要点VOC 格式一张图对应一个 xml核心节点是filename、size宽高、以及若干object每个 object 里有name和bndboxxmin/ymin/xmax/ymax绝对像素坐标左上右下。水下数据集的 xml 常见坑是size里的宽高和真实图片对不上或者bndbox出现负值、越界。解析时一定要以实际图片尺寸为准做校验。import xml.etree.ElementTree as ET from PIL import Image def parse_voc(xml_path, img_path): tree ET.parse(xml_path) root tree.getroot() # 以真实图片尺寸为准别信 xml 里的 size w, h Image.open(img_path).size boxes [] for obj in root.findall(object): name obj.find(name).text.strip() bb obj.find(bndbox) xmin float(bb.find(xmin).text) ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text) ymax float(bb.find(ymax).text) # 裁剪到图像范围内防止越界框污染训练 xmin, ymin max(0, xmin), max(0, ymin) xmax, ymax min(w, xmax), min(h, ymax) if xmax - xmin 1 or ymax - ymin 1: continue # 丢弃退化框 boxes.append((name, xmin, ymin, xmax, ymax)) return w, h, boxes逻辑说明先读真实尺寸再解析框是为了防止 xml 里size写错导致后续归一化全错。裁剪和退化框过滤是水下数据的必备动作因为浑浊场景下标注员容易画出贴边甚至越界的框。参数上xmin/ymin/xmax/ymax是绝对像素转 YOLO 时要除以宽高。2.2 YOLO txt 的归一化规则与类别索引YOLO 每张图一个 txt每行class_id cx cy w h全部是相对图像宽高的 0~1 浮点。class_id从 0 开始必须和data.yaml里names的顺序严格一致——这是最常见的翻车点类别顺序错一位模型学出来的全是错的。转换时用下面的公式注意cx/cy是框中心不是左上角。def voc_to_yolo(boxes, w, h, class_map): lines [] for name, xmin, ymin, xmax, ymax in boxes: if name not in class_map: continue # 未登记类别直接跳过避免索引错乱 cid class_map[name] cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 归一化后仍要 clamp浮点误差可能让值略超 1 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines参数说明class_map是从类别名到 0 基索引的字典务必和data.yaml同源生成别手写两遍。保留 6 位小数足够YOLO 官方脚本也是这个精度。clamp 是后悔药能挡住个别脏标注。2.3 JSON 标签的两种常见形态与转换JSON 在水下数据集里通常是两种一种是 COCO 风格images/annotations/categories三个数组框是[x, y, w, h]绝对坐标另一种是自定义的每图一个对象。COCO 的bbox是左上角加宽高和 VOC 的左上右下不一样转换时别搞混。import json def coco_to_yolo(json_path, class_map): data json.load(open(json_path, r, encodingutf-8)) img_info {im[id]: im for im in data[images]} # COCO 的 category_id 往往不是 0 基需要重映射 cat_id_to_name {c[id]: c[name] for c in data[categories]} out {} for ann in data[annotations]: im img_info[ann[image_id]] w, h im[width], im[height] name cat_id_to_name[ann[category_id]] if name not in class_map: continue x, y, bw, bh ann[bbox] # 左上角 宽高 cx (x bw / 2) / w cy (y bh / 2) / h out.setdefault(im[file_name], []).append( f{class_map[name]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) return out逻辑说明COCO 的category_id经常是 1 基甚至跳号直接拿来当 YOLO 的class_id会错位所以必须经过cat_id_to_name再映射到自己的class_map。bbox是左上角加宽高转中心点要加半个宽高。这一步是 json 转换里最容易出错的地方。3. 从零跑通训练目录组织、data.yaml 与 YOLOv8 最小命令3.1 目录结构与划分脚本YOLO 训练要求固定的目录images/train、images/val、labels/train、labels/val图片和标签同名不同后缀。划分时按 8:2 或 9:1注意同一段视频抽帧的图要放同一侧否则验证集泄漏指标虚高。# 假设原始数据images/ 下所有 jpglabels/ 下同名 txt mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val # 用固定种子打乱保证可复现 ls images/*.jpg | shuf --random-source(yes 42) all.txt n$(wc -l all.txt); nval$((n/5)) head -n $nval all.txt | while read f; do b$(basename $f .jpg) mv $f dataset/images/val/; mv labels/$b.txt dataset/labels/val/ done tail -n $((nval1)) all.txt | while read f; do b$(basename $f .jpg) mv $f dataset/images/train/; mv labels/$b.txt dataset/labels/train/ done参数说明--random-source(yes 42)用固定种子让shuf结果可复现团队协作时这点很重要。nval$((n/5))是 20% 验证集数据量小可以调到 10%。移动而不是复制能避免同一张图同时出现在两侧。3.2 data.yaml 的写法与类别顺序path: /abs/path/to/dataset train: images/train val: images/val nc: 7 names: 0: plastic_bottle 1: plastic_bag 2: fishing_net 3: metal_can 4: glass_bottle 5: rubber 6: othernc必须等于names的条目数names的顺序必须和生成 txt 时的class_map完全一致。建议把class_map和这份 yaml 用同一个脚本生成从源头杜绝错位。path用绝对路径相对路径在不同工作目录下容易找不到。3.3 训练命令与关键参数yolo detect train \ data/abs/path/to/data.yaml \ modelyolov8n.pt \ epochs100 imgsz640 batch16 \ lr00.01 lrf0.01 \ mosaic1.0 hsv_h0.015 hsv_s0.7 hsv_v0.4 \ degrees10 translate0.1 scale0.5 \ projectruns nameunderwater_trash参数说明modelyolov8n.pt是轻量 backbone水下数据量 5000 出头n 或 s 足够别一上来上 x。imgsz640是默认水下小目标多可以试 960但显存翻倍。hsv_h/s/v是颜色抖动水下偏色严重适当加大hsv_h能提升泛化。mosaic1.0对小目标友好但训练后期可以降到 0.5 减少拼接伪影。degrees10做小角度旋转水下目标朝向随机旋转增强有效。lr00.01配lrf0.01是余弦退火的常见组合。提示第一次跑先用epochs10验证流程通不通确认 loss 正常下降、验证集能出图再拉满 100 轮省得白等几小时。4. 水下场景的避坑与排查颜色、类别、格式三类翻车4.1 现象训练 loss 正常但验证 mAP 极低原因验证集和训练集来自同一段视频的相邻帧内容高度相似模型记住了背景而不是目标或者类别索引在 train/val 之间不一致。解决按视频/场景划分而不是随机划分检查两侧 txt 的class_id分布是否一致用脚本统计每类框数量对比。4.2 现象模型把背景的蓝色水体框成目标原因水下图像整体偏蓝绿颜色增强hsv_h开太大反而让水体纹理被当成特征或者负样本无垃圾的纯水图太少。解决把hsv_h降到 0.01~0.015加入一定比例的无目标背景图作为负样本让模型学会「什么都没有」也是一种输出。4.3 现象xml 转 yolo 后框整体偏移原因xml 里的size宽高和真实图片不一致转换时用了 xml 的尺寸做归一化。解决一律用PIL.Image.open(img).size取真实尺寸转换后随机抽 20 张用可视化脚本画框核对别信标注文件里的元数据。4.4 现象JSON 转出来的类别数比预期多原因COCO 的categories里可能包含数据集里实际没出现的类别或者category_id跳号。解决先统计annotations里实际出现的category_id再决定class_map不要直接照抄categories数组。4.5 现象训练中途 BN 崩溃、loss 变 NaN原因batch 太小水下图分辨率高时显存吃紧被迫降到 4 甚至 2BN 统计不稳或者学习率过高。解决换yolov8n或降imgsz保证batch8或改用带 GroupNorm 的变体把lr0从 0.01 降到 0.005 再试。这类玄学问题先怀疑 batch 和 lr。5. 进阶用混淆矩阵和格式互转脚本把数据集吃透跑通训练只是开始真正决定模型上限的是你对这份数据集的理解程度。我一般会做两件事一是画混淆矩阵看哪两类在互相误判二是写一个三格式互转的统一脚本方便随时切换框架。混淆矩阵在 Ultralytics 训练完会自动生成confusion_matrix.png但要注意归一化方式。行归一化看「真实类被预测成什么」列归一化看「预测类里混进了什么」两个都要看。水下数据里塑料瓶和玻璃瓶、塑料袋和渔网最容易混前者因为透明材质在浑浊水里轮廓相似后者因为都是网状/片状。看到这两组高误判就该针对性补样本或加类别区分特征而不是盲目加轮数。import json, xml.etree.ElementTree as ET from pathlib import Path def yolo_to_voc(txt_path, img_w, img_h, class_names, out_xml): root ET.Element(annotation) ET.SubElement(root, filename).text Path(txt_path).stem .jpg size ET.SubElement(root, size) ET.SubElement(size, width).text str(img_w) ET.SubElement(size, height).text str(img_h) for line in open(txt_path): cid, cx, cy, bw, bh line.split() cid int(cid); cx, cy, bw, bh map(float, (cx, cy, bw, bh)) xmin (cx - bw/2) * img_w; ymin (cy - bh/2) * img_h xmax (cx bw/2) * img_w; ymax (cy bh/2) * img_h obj ET.SubElement(root, object) ET.SubElement(obj, name).text class_names[cid] bb ET.SubElement(obj, bndbox) for k, v in zip((xmin,ymin,xmax,ymax), (xmin,ymin,xmax,ymax)): ET.SubElement(bb, k).text str(int(round(v))) ET.ElementTree(root).write(out_xml, encodingutf-8)逻辑说明反向转换时class_names必须是列表且索引和class_id对齐int(round())是因为 VOC 的 bndbox 惯例是整数像素。这个脚本配合前面的voc_to_yolo和coco_to_yolo就能在 VOC/YOLO/JSON 之间自由切换接不同框架时不用重新标。验证数据集质量还有一个笨但有效的办法随机抽 50 张把三种格式各自解析一遍画在同一张图上比对。如果三种格式画出来的框位置一致说明转换链路没问题如果有偏差问题一定出在坐标定义左上右下 vs 左上宽高或归一化基准上。这个自检我每次拿到新数据集都会做能省掉后面几小时的 debug。最后说个习惯拿到任何带多格式标签的数据集先写一个stats.py统计每类框数量、每图平均框数、框的宽高分布再决定用不用、怎么增强。水下垃圾检测里如果某一类只有几十个框别指望模型学好要么合并类别要么针对性过采样。数据这关过了模型的事才好谈。希望帮到你。本文还有配套的精品资源点击获取
返回列表