简介本资源为河道垃圾检测数据集采用Pascal VOC与YOLO双格式标注面向从事水域环境监测、计算机视觉目标检测的开发者与研究人员可用于训练河道漂浮物识别模型。包内共约2000个文件以1999个xml标注文件和1个说明txt为主另含2274张jpg图片及对应yolo格式txt压缩包约113.54MBxml与txt分别适配VOC和YOLO训练流程。标注涵盖ball、bottle、branch、grass、leaf、milk-box、plastic-bag、plastic-garbage共8个类别覆盖常见河道垃圾与水草等干扰物。需注意部分垃圾边界模糊作者建议下载后重新审核标注并说明水草仅标注一部分便于在干净河道中检出目标。目前已有782人学习下载适合需要真实河道场景数据、愿意二次清洗标注的检测任务使用者参考。1. 2274 张河道垃圾图片8 个类别这份 VOCYOLO 双格式数据集能省掉多少标注时间上个月帮一个做智慧水务的团队看模型他们卡在数据上已经两周了。模型本身用的是 YOLOv8代码没问题但训练集只有自己用手机拍的 300 多张图类别还混着「塑料瓶」「泡沫」「树枝」分不清mAP 一直在 0.4 上下打转。问题不在网络结构在于样本量和标注质量。河道垃圾检测这个场景有个特点目标小、背景杂、水面反光强靠少量图片根本喂不饱检测头。这份 2274 张、8 个类别、同时提供 VOC 和 YOLO 两种标注格式的数据集正好切中这类项目的痛点——拿到手就能直接进训练流程不用再花几天时间做格式转换和类别对齐。它适合三类人一是做水域环保监测、无人机巡河、水面清洁机器人视觉模块的工程师二是想拿一个真实场景数据集练 YOLO 全流程的学生和转行者三是需要快速搭一个河道垃圾检测 baseline 再往上做改进的研究者。VOC 格式给的是 XML 标注YOLO 格式给的是归一化后的 txt两种格式覆盖了从 PASCAL VOC 系工具链到 Ultralytics 系训练脚本的主流需求。下面按「数据集里到底有什么 → 怎么接进训练 → 哪里容易翻车 → 怎么验证训好了」的顺序拆开讲。2. 拆开压缩包先看什么8 类别标注结构与 VOC/YOLO 双格式的对应关系2.1 目录结构与文件命名习惯拿到一个.7z数据集别急着解压完就往训练脚本里塞。先花五分钟把目录结构摸清楚能避免后面 80% 的路径报错。这类 VOCYOLO 双格式数据集常见做法是解压后出现两个平行目录一个放 VOC 风格的AnnotationsJPEGImages另一个放 YOLO 风格的imageslabels。图片文件名通常是数字编号或带前缀的连续编号标注文件名与图片名一一对应只是扩展名不同.xml对.txt。先跑一遍文件计数确认图片数和标注数对得上# 统计图片数量VOC 和 YOLO 两套目录各数一遍 find ./VOC/JPEGImages -type f \( -name *.jpg -o -name *.png \) | wc -l find ./YOLO/images -type f \( -name *.jpg -o -name *.png \) | wc -l # 统计标注文件数量 find ./VOC/Annotations -name *.xml | wc -l find ./YOLO/labels -name *.txt | wc -l逻辑说明图片数和标注数必须相等如果不等说明有图片漏标或者标注文件命名对不上。参数上注意-type f限定只数文件避免把目录算进去。如果两套格式的图片数不一致优先以 YOLO 目录为准因为训练时用的是它。2.2 8 个类别在 VOC 与 YOLO 里的映射VOC 的 XML 里类别写在name标签内是字符串YOLO 的 txt 里类别是整数索引从 0 开始。这两者之间必须有一张固定的映射表否则训练出来的模型会把「塑料瓶」认成「树枝」。常见做法是在 YOLO 目录下放一个classes.txt或data.yaml里面按行列出类别名行号就是索引。索引类别名示例VOC 中的 name 值YOLO txt 首列0plasticplastic01foamfoam12branchbranch23bottlebottle34bagbag45leafleaf56grassgrass67otherother7注意上表类别名是按河道垃圾场景的常见分类补全的实际以数据集内classes.txt或data.yaml为准。拿到后第一件事就是打开这个文件核对别凭经验猜。如果 VOC 的 XML 里出现了映射表里没有的类别名说明标注有脏数据需要单独挑出来处理。2.3 用脚本校验标注完整性在正式训练前写一个校验脚本把 VOC 的 XML 逐个解析检查三件事类别名是否在映射表内、边界框坐标是否越界、图片尺寸与标注是否匹配。这一步能提前暴露标注错误省得训练到一半 loss 突然炸了还找不到原因。import os import xml.etree.ElementTree as ET from PIL import Image CLASSES [plastic, foam, branch, bottle, bag, leaf, grass, other] VOC_IMG_DIR ./VOC/JPEGImages VOC_ANN_DIR ./VOC/Annotations bad_files [] for xml_file in os.listdir(VOC_ANN_DIR): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(VOC_ANN_DIR, xml_file)) root tree.getroot() # 读取图片真实尺寸 img_name root.find(filename).text img_path os.path.join(VOC_IMG_DIR, img_name) if not os.path.exists(img_path): bad_files.append((xml_file, 图片缺失)) continue w, h Image.open(img_path).size for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: bad_files.append((xml_file, f未知类别 {name})) bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) if xmin 0 or ymin 0 or xmax w or ymax h: bad_files.append((xml_file, f坐标越界 {xmin},{ymin},{xmax},{ymax})) print(f共发现 {len(bad_files)} 个问题文件) for f in bad_files[:20]: print(f)逻辑说明脚本遍历所有 XML用ElementTree解析先核对图片是否存在再检查类别名和坐标范围。参数上CLASSES列表必须和classes.txt完全一致顺序也不能错。跑完如果bad_files为空说明标注干净可以放心进训练如果有问题按文件名定位到具体图片人工修一遍再继续。3. 把 VOC 转成 YOLO 训练格式转换脚本、data.yaml 配置与训练参数3.1 VOC 转 YOLO 的坐标归一化逻辑VOC 的边界框是绝对像素坐标(xmin, ymin, xmax, ymax)YOLO 要的是归一化后的中心点加宽高(x_center, y_center, width, height)且都在 0 到 1 之间。转换公式不复杂但有两个容易写错的地方一是除以的是图片自身的宽高不是固定值二是类别索引要从映射表里查不能直接用 XML 里的顺序。import os import xml.etree.ElementTree as ET from PIL import Image CLASSES [plastic, foam, branch, bottle, bag, leaf, grass, other] VOC_IMG_DIR ./VOC/JPEGImages VOC_ANN_DIR ./VOC/Annotations OUT_LABEL_DIR ./YOLO/labels os.makedirs(OUT_LABEL_DIR, exist_okTrue) for xml_file in os.listdir(VOC_ANN_DIR): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(VOC_ANN_DIR, xml_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(VOC_IMG_DIR, img_name) w, h Image.open(img_path).size lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化中心点与宽高 x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(OUT_LABEL_DIR, out_name), w) as f: f.write(\n.join(lines))逻辑说明核心是x_center、y_center、bw、bh四个值的计算全部除以图片真实宽高。参数上保留 6 位小数足够YOLO 训练时对精度不敏感。如果数据集已经提供了 YOLO 格式的 labels 目录这段脚本可以跳过直接核对即可。3.2 data.yaml 的写法与路径陷阱Ultralytics 系的 YOLOv5/v8 都靠一个data.yaml来定位数据和类别。这个文件写错路径是新手最常见的翻车点尤其是path、train、val三个字段的相对关系。path: ./YOLO train: images/train val: images/val nc: 8 names: 0: plastic 1: foam 2: branch 3: bottle 4: bag 5: leaf 6: grass 7: other逻辑说明path是数据集根目录train和val是相对于path的子路径。注意nc必须等于类别数names的键必须从 0 连续到 7中间不能跳号。如果训练时报No labels found九成是train路径写错了或者 labels 目录和 images 目录没有平行放置。常见做法是把图片和标注按images/train、labels/train的结构组织YOLO 会自动把images替换成labels去找标注。3.3 训练命令与关键参数数据准备好后用 YOLOv8 起训。下面这条命令是河道垃圾检测场景下比较稳的配置yolo detect train \ data./YOLO/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/river_trash \ nameexp1逻辑说明modelyolov8n.pt用 nano 版先跑通流程确认数据没问题再换 s 或 m。imgsz640是默认值河道垃圾目标偏小如果显存够可以提到 800 或 960对小目标召回有帮助。patience20表示 20 轮没提升就早停避免过拟合。batch16在 8G 显存上比较安全显存不够就降到 8。训练过程中重点看mAP50和mAP50-95两个指标前者到 0.7 以上说明基本可用后者反映定位精度。4. 河道垃圾检测的避坑清单从标注脏数据到小目标漏检4.1 类别不均衡导致某些类几乎学不到现象训练完看混淆矩阵foam和leaf这两类的召回率极低几乎全被预测成背景或其他类。原因河道场景里泡沫和树叶本身目标就小加上数据集里这两类的实例数可能远少于plastic和bottle模型倾向于忽略少数类。解决先统计每个类别的实例数如果某类少于总实例的 5%用copy-paste增强或者对少数类做过采样。YOLOv8 本身没有内置的类别加权可以在数据加载阶段对少数类图片重复采样。4.2 水面反光被误标成垃圾现象模型在测试集上把水面高光区域框成plastic或other。原因标注阶段人工看走眼把反光当成了漂浮物这类脏标注会直接教坏模型。解决用第 2.3 节的校验脚本先筛一遍坐标异常再抽样看 50 张图的可视化标注重点检查反光区域是否有框。发现后要么删掉该标注要么把类别改成other并单独控制其权重。4.3 图片尺寸不一致导致训练报错现象训练启动后报RuntimeError: shape mismatch或某些图片加载失败。原因数据集里混了不同分辨率的图片而某些预处理步骤假设了固定尺寸。解决YOLO 本身支持多尺寸输入但如果有损坏的图片文件比如下载不完整会在加载时报错。先跑一遍图片完整性检查from PIL import Image import os bad [] for root, _, files in os.walk(./YOLO/images): for f in files: if f.lower().endswith((.jpg, .png, .jpeg)): p os.path.join(root, f) try: Image.open(p).verify() except Exception as e: bad.append((p, str(e))) print(f损坏图片 {len(bad)} 张) for b in bad: print(b)逻辑说明verify()会检查文件头是否完整能揪出下载或解压过程中损坏的图片。参数上注意verify()之后如果要继续用这个图片对象需要重新open一次。4.4 验证集里混入了训练集图片现象训练时mAP涨得特别快但换一批新图测试就崩。原因划分训练集和验证集时没有按图片去重同一张图或高度相似的连续帧同时出现在两边造成数据泄漏。解决划分前先对图片做感知哈希去重把相似度高的图片分到同一侧。常见做法是用imagehash库算 phash距离小于 5 的视为重复。4.5 小目标漏检严重现象大块塑料能检出小片泡沫和碎叶几乎全漏。原因下采样倍率太高小目标在特征图上只剩几个像素。解决把imgsz提到 960或者在模型里加一个 P2 检测头YOLOv8 支持通过修改 yaml 增加小目标层。另一个低成本做法是切片推理把大图切成 640 的小块分别检测再合并对河道这种长条形场景特别有效。5. 训完之后怎么验证mAP 曲线、混淆矩阵与切片推理的实战技巧训练跑完不是看个最终 mAP 就完事。我一般会做三件事先看results.png里的 loss 和 mAP 曲线确认没有过拟合或欠拟合再打开混淆矩阵看哪些类别之间在互相误判最后拿几张真实河道图做推理肉眼核对框的位置和类别。验证时有个容易被忽略的点YOLO 默认的验证是在val集上算的如果val集和训练集分布太接近指标会虚高。我的习惯是从原始数据里单独留出 10% 作为「从未参与训练和调参」的测试集只在最后跑一次。这一步多花十分钟但能避免上线后翻车。切片推理是河道垃圾检测里很实用的技巧尤其适合无人机航拍的大图。做法是把原图按 640×640 滑窗切块每块单独推理再把结果映射回原图坐标做 NMS 合并。下面是一个简化实现import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/river_trash/exp1/weights/best.pt) img cv2.imread(test_river.jpg) h, w img.shape[:2] tile 640 stride 480 # 重叠 160 像素避免目标被切断 all_boxes [] for y in range(0, h, stride): for x in range(0, w, stride): crop img[y:ytile, x:xtile] if crop.shape[0] tile or crop.shape[1] tile: crop cv2.copyMakeBorder(crop, 0, tile-crop.shape[0], 0, tile-crop.shape[1], cv2.BORDER_CONSTANT, value(114,114,114)) results model(crop, conf0.25, verboseFalse) for box in results[0].boxes: xyxy box.xyxy[0].cpu().numpy() xyxy[0] x xyxy[1] y xyxy[2] x xyxy[3] y all_boxes.append((xyxy, float(box.conf[0]), int(box.cls[0]))) # 对合并后的框做 NMS boxes np.array([b[0] for b in all_boxes]) scores np.array([b[1] for b in all_boxes]) classes np.array([b[2] for b in all_boxes]) indices cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), 0.25, 0.45) print(f切片推理后保留 {len(indices)} 个框)逻辑说明stride小于tile是为了让相邻块有重叠防止目标正好落在切缝上被截断。copyMakeBorder处理边缘不足一块的情况填充值用 YOLO 常用的灰色 114。最后用 OpenCV 的NMSBoxes做跨块去重0.45是 IoU 阈值河道垃圾目标稀疏这个值可以适当调低到 0.3 减少重复框。从那以后我每次拿到新数据集都强制先跑一遍文件计数和标注校验再开始训练。这个习惯帮我省下了至少三次「训到一半发现标注有问题」的返工。希望这份 2274 张、8 类别的河道垃圾检测数据集能帮你把精力放在模型改进上而不是耗在数据清洗里。本文还有配套的精品资源点击获取