简介:面向计算机视觉与环保应用开发者的一份可直接用于训练的目标检测数据集,覆盖电池、纸团、一次性杯子、塑料瓶和积木五类常见垃圾。所有图片均按COCO格式标注,提供详细边界框信息,每类垃圾约350张样本,数量适合训练与验证;也便于学生微调YOLO、SSD、Faster R-CNN等主流检测模型,快速搭建垃圾分类识别实验。资源包共1753个文件,以1743张JPG图片为主体,另有8张PNG图片和2个JSON标注文件,整体压缩包约20.19MB,数据量适中,便于下载与部署。目前已有2199人学习或下载过这份数据集,在相关社区中有一定认可度。使用这份数据可以省去自行采集与标注图像的繁琐流程,直接进入模型训练、验证和调优阶段,适合在环保监控、智能回收等场景下开展算法研究与应用落地。
1. 垃圾目标检测数据集:问题不在“下载”,在标注决策
垃圾目标检测和常规物体检测最大的区别在于,垃圾不是一个“类别”,而是一组“状态”:被压扁的易拉罐、浸水发黑的纸巾、半埋在土里的玻璃瓶、被树叶盖住的烟头。同样的物体在城市道路、海岸线、河道两岸和分类仓里呈现完全不同的外观,导致很多在 COCO 上表现不错的模型一到真实环卫场景就掉点。做这个任务的数据集,核心不是“下载一个现成的 zip”,而是先回答三个问题:类别粒度定多细、小目标占比怎么保证、负样本(干净路面、落叶堆)怎么处理。这篇文章面向正在做智慧环卫、河道巡查、岸线垃圾监测和分类机器人抓取的算法工程师,讲清楚公开集选型、自建标注流程,以及小目标与背景误检的专项优化,最后落到可复现的命令和参数上。
2. 垃圾目标检测数据集选型:公开集、COCO 子类与场景划分
2.1 主流公开数据集清单和适用边界
做垃圾目标检测,大多数团队不会从零开始采集,先看手头有没有能直接用的公开集。这里列几个我实际用过的,按“能不能直接进训练管线”排序,而不是按下载量排序。
| 数据集 | 拍摄场景 | 标注格式 | 类别粒度 | 典型问题 |
|---|---|---|---|---|
| TACO | 野外开放环境 | COCO JSON | 数十类,覆盖塑料袋、瓶罐、烟头 | 遮挡和截断严重,部分类样本极少 |
| TrashNet | 实验室白底拍摄 | 单类标签 | 6 类(glass, paper, cardboard, plastic, metal, trash) | 背景干净,域迁移能力弱 |
| MJU-Waste | 室内外混合 | 框级标注 | 多类,附带 RGB-D 双模态 | 需要深度图预处理,管线重 |
| AquaTrash | 水下场景 | COCO 风格 | 水下生活垃圾 | 水体模糊、颜色偏移大 |
| Drinking Waste | 饮料瓶细分 | 框级标注 | 按颜色和材质分瓶类 | 类别单一,只适合做瓶类分支 |
以 TACO 为例,它是野外垃圾场景里类别覆盖最全的一个,但直接拿来训练会踩两个坑:一是长尾严重,瓶类和塑料袋占了大头,灯泡、废旧电池这类样本少到几乎不参与训练;二是同一地点连续拍摄的帧高度相似,如果随机切 train/val,验证集会“背下”训练集的背景,mAP 虚高得离谱。TrashNet 则相反,白底单物体标注让模型学不到“垃圾在场景里是什么样”,只能用来做类别语义的预热身。
我一般会把公开集拆成两部分用:TACO 这类野外数据负责学“遮挡、形变、环境光”,TrashNet 这类干净数据负责学“类别之间的边界”,两者比例按目标场景来调。如果场景是河道岸线,TACO 的权重就大;如果场景是分类传送带,TrashNet 更接近部署域。公开集的价值不是替代自采,而是让模型在自采数据到位前先有一版可用的基线。
2.2 为什么不能随机划分数据:场景相关性
垃圾检测数据的划分粒度直接影响模型评估的可信度。很多项目组拿 TACO 或自采数据后直接train_test_split按图片随机分,结果在验证集上 mAP@0.5 到 0.9,一上实地就崩。原因很简单:垃圾检测的采集往往是固定点位或连续视频抽帧,同一视角下相邻帧的背景、光照、物体位置几乎没有变化,随机划分等于把同一场景的副本同时放进训练集和验证集,模型记住的是“这块地砖长什么样”,而不是“垃圾长什么样”。
正确做法是按场景或拍摄点位划分,保证同一视角的所有帧只出现在一个集合里。下面这段代码按文件名前缀假设了拍摄点位标识,实战中对应视频文件、采集任务的编号,或 GPS 坐标前缀:
import os import random random.seed(42) images = [f for f in os.listdir('images') if f.endswith('.jpg')] scenes = {} for img in images: scene = img.split('_')[0] scenes.setdefault(scene, []).append(img) val_scenes = random.sample(list(scenes.keys()), k=max(1, int(len(scenes) * 0.2))) val_imgs = [img for sc in val_scenes for img in scenes[sc]] train_imgs = [img for img in images if img not in val_imgs] print(f'train: {len(train_imgs)}, val: {len(val_imgs)}, scenes: {len(scenes)}')这段代码先把图片按点位分组,再整体抽取 20% 的点位作为验证场景。关键在于val_scenes是“场景序号”而不是“图片序号”,这样同一视角的帧不会跨集合。逻辑上,垃圾检测模型要泛化的是“没去过的新点位”,所以验证集模拟的也应该是新点位。如果你手里的数据没有点位 ID,退而求其次用视频片段编号切分,也比逐帧随机切分可靠。
2.3 用 COCO 子类补充域:能做但别贪多
公开集不够时,一个常见的做法是从 COCO2017 数据集结构里抽出 bottle、cup、fork、knife 等子类,当作垃圾检测的补充数据。COCO2017 的标注结构是标准 COCO JSON,类别 ID 在 40 到 60 之间,用 pycocotools 或直接读 JSON 都能按 category_id 过滤。但这里有一个域偏移问题:COCO 里的瓶子多数摆在室内餐桌上,背景干净、形态完整,而垃圾场景里的瓶子被压扁、沾满泥、半埋在土里。直接把 COCO 子类混进训练集,模型会倾向于把“完整的瓶子”当成正样本,对“变形的瓶子”反而漏检。
我的做法是给 COCO 子类单独一个类别映射,不合并进垃圾主类别,然后限制它在每个 batch 中的占比。不论怎么补,统计现有数据集的类别分布是第一步。下面这段代码用纯标准库解析任意 COCO JSON,不依赖 pycocotools 也能跑:
import json from collections import Counter with open('annotations.json', 'r', encoding='utf-8') as f: data = json.load(f) cat_id2name = {c['id']: c['name'] for c in data['categories']} counter = Counter(cat_id2name[a['category_id']] for a in data['annotations']) for name, cnt in counter.most_common(30): print(f'{name}: {cnt}')这段代码把每个类别名及出现次数打印出来。Counter直接统计每张图里每个类别的标注数量,注意这里统计的是标注框数量而不是图片数量,一张图里出现 5 个烟头会算作 5 次。如果你发现头部类别占了全部标注的 70% 以上,说明长尾已经严重到需要专项处理了,这个结论直接决定下一章训练策略怎么定。
3. 从标注到训练:垃圾目标检测数据集的格式转换与长尾增强
3.1 标注工具选型与 COCO JSON 转 YOLO 格式
自建垃圾目标检测数据集,标注工具的选择会影响后续转换成本。常见做法是 labelImg 直接标 YOLO 格式,或者用 X-AnyLabeling 借助 SAM 类模型做半自动分割再转框。我的建议是:如果团队有 2 人以上参与标注,直接用 CVAT 搭 Web 服务,便于审核和多人并行;如果只是个人调试,X-AnyLabeling 的半自动能力能把标注速度提升一倍。
| 工具 | 半自动能力 | 输出格式 | 适合规模 |
|---|---|---|---|
| labelImg | 无,纯手标 | VOC XML / YOLO txt | 千张以内 |
| X-AnyLabeling | SAM 分割辅助 | COCO JSON / YOLO txt | 千到万张 |
| CVAT | 模型辅助、自动追踪 | COCO / VOC / YOLO | 万张以上,多人在线 |
不管用哪个工具,最终进 YOLO 训练管线都需要统一成 txt 格式,一行一个目标:cls cx cy w h,其中 cx、cy、w、h 都除以图片宽高做归一化。很多公开集是 COCO JSON 格式,转成 YOLO 格式是避不开的一步。下面这段脚本接收 COCO 标注文件路径和输出目录,直接产出每个图片对应的 txt:
import json import os def convert_coco(coco_path, label_dir, cls_map=None): os.makedirs(label_dir, exist_ok=True) with open(coco_path, 'r', encoding='utf-8') as f: data = json.load(f) img_info = {img['id']: img for img in data['images']} cat_id = { c['id']: (cls_map[c['name']] if cls_map and c['name'] in cls_map else c['id']) for c in data['categories'] } anns_by_img = {} for ann in data['annotations']: anns_by_img.setdefault(ann['image_id'], []).append(ann) for img_id, anns in anns_by_img.items(): img = img_info[img_id] ih, iw = img['height'], img['width'] lines = [] for ann in anns: x, y, w, h = ann['bbox'] cx = (x + w / 2) / iw cy = (y + h / 2) / ih nw = w / iw nh = h / ih if nw < 1e-4 or nh < 1e-4: continue lines.append(f"{cat_id[ann['category_id']]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") stem = os.path.splitext(os.path.basename(img['file_name']))[0] with open(os.path.join(label_dir, f'{stem}.txt'), 'w') as out: out.write('\n'.join(lines)) convert_coco('train.json', 'labels')代码里cat_id的映射是做类别合并的关键。垃圾场景里同一物体在不同公开集里名字可能不同,比如塑料瓶在 A 数据集叫plastic_bottle,在 B 数据集叫bottle,转换前用cls_map统一名称。nw < 1e-4的过滤条件是防脏数据的:标注工具偶尔会生成宽或高为 0 的异常框,这类框不过滤会导致训练时 loss 计算出现 NaN。
转换完成后,检查一下 labels 目录下的 txt 内容,三五行即可,确认归一化后数值都在 0 到 1 之间,且类别 ID 连续从 0 开始。YOLO 训练时类别 ID 必须从 0 开始编号,如果 COCO JSON 里的类别 ID 是从 1 开始的,没有重映射会直接漏掉第一个类别。
3.2 类不平衡处理:过采样与复制粘贴增强
垃圾数据的标注分布几乎注定是长尾的。烟头、纸巾、塑料瓶这类高频类别可能有几千个框,而灯泡、废旧电池、渔网这类低频类别可能只有几十张图。直接训练,模型对低频类别的召回率会低到不可用,因为前景梯度完全被高频类别主导。
最简单有效的做法是低频类别过采样:按类别统计框数,把低频类别的图片在数据加载时多复制几份进训练集。但过采样只能解决“看到过几遍”的问题,解决不了“目标太小、特征太少”的问题。更强的做法是复制粘贴增强,从原图中把低频目标抠出来,随机贴到干净背景图上。下面是一个简化版的贴图逻辑:
import cv2 import random def paste(obj_img, obj_box, bg_img): x, y, w, h = [int(v) for v in obj_box] roi = obj_img[y:y + h, x:x + w] hh, ww = bg_img.shape[:2] scale = random.uniform(0.8, 1.2) new_h, new_w = int(h * scale), int(w * scale) if new_h >= hh or new_w >= ww: return None roi = cv2.resize(roi, (new_w, new_h)) bx = random.randint(0, ww - new_w) by = random.randint(0, hh - new_h) bg_img[by:by + new_h, bx:bx + new_w] = roi cx = (bx + new_w / 2) / ww cy = (by + new_h / 2) / hh nw = new_w / ww nh = new_h / hh return bg_img, cx, cy, nw, nh这个函数把原图中框出的目标区域裁剪出来,随机缩放后贴到背景图的随机位置,并返回新的 YOLO 格式坐标。随机缩放 0.8 到 1.2 是为了让模型不要只见过固定大小的目标。注意这个简化版本没有做交叠检查,生产环境里需要对新框和已有框算 IoU,超过阈值就重新选位置,否则会出现两个目标叠在一起的情况。贴图用的背景图最好来自负样本池,也就是第 4 章会讲到的、完全没有任何垃圾的场景图。
3.3 用 YOLOv8 训练自己的数据集:参数怎么定
数据准备好之后,训练命令本身并不复杂。以 YOLOv8 为例,一条最常用的命令:
yolo detect train \ model=yolov8m.pt \ data=trash.yaml \ epochs=120 \ imgsz=1280 \ batch=16 \ close_mosaic=10 \ patience=20 \ device=0data 文件 trash.yaml 内容如下:
path: trash_data train: train/images val: val/images names: 0: cigarette 1: plastic_bottle 2: can这里几个参数对垃圾场景特别关键。imgsz=1280是首选,垃圾目标(烟头、瓶盖)在原图中往往小于 32 像素,YOLO 默认 640 输入下这些小目标只占几个特征点,1280 能把它们放大到可学习的尺寸范围,代价是显存占用和推理耗时。close_mosaic=10表示最后 10 个 epoch 关闭 Mosaic 增强,Mosaic 会把四张图拼在一起,小目标可能被切掉一半,最后 10 个 epoch 关掉它让模型在真实分布上稳住。patience=20是在验证集指标连续 20 轮不提升时提前停止,防止长尾数据下过拟合。
模型选择上,yolov8n训练快,但小目标检测能力明显偏弱;yolov8m是精度和速度的平衡点,部署时如果要压帧率再换yolov8s蒸馏。
3.4 训练完先看每类 AP,而不是只看总 mAP
训练结束后跑一次验证,把每个类别的 AP 单独打出来。总 mAP 会掩盖长尾类别的问题,20 个类里 2 个类 AP 是 0,总 mAP 可能只掉 3 个点,看起来还行,但业务上那 2 个类就是失效的。下面这段代码用 ultralytics 的接口跑验证并逐类输出 AP:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') res = model.val(data='trash.yaml', imgsz=1280, conf=0.25, iou=0.5) for i, ap in enumerate(res.box.ap50): print(f'{res.names[i]}: mAP@0.5 = {ap:.4f}')res.box.ap50是一个数组,索引和 names 里的类别 ID 一一对应。跑完这一步,你会直观看到哪几个类拖后腿。如果一个类的 AP 比其他类低 0.3 以上,不要急着调损失权重,先去看这个类的样本量。样本量少于 100 框的类别,模型基本学不到什么稳定特征,优先补数据,而不是调参。
4. 小目标漏检与背景误检:垃圾目标检测数据集的专项调优
4.1 先判断瓶颈在哪
垃圾检测精度上不去,通常是两类错误:小目标漏检和背景误检。它们在外观上很像,但诊断方法完全不同。看两个指标:如果 mAP@0.5 差强人意,而 mAP@0.5:0.95 掉得厉害,说明模型对目标的定位和尺度不够精确,小目标占大头;如果训练集损失收敛但验证集 mAP 波动大,且混淆矩阵里背景那一列的值偏高,说明模型把路面裂纹、落叶、积水反光当成了垃圾。
背景误检在垃圾场景里格外严重,因为垃圾本身没有刚性形状,一块皱着的塑料袋和一片落叶在纹理上极其相似。遇到这种情况,很多人会去调置信度阈值,但这只是掩盖问题,正确做法是给模型喂负样本。
4.2 负样本与难例挖掘:让模型见过“不是垃圾”的东西
目标检测模型训练时,不标注的图片区域都默认是背景。如果你的训练集里每张图都有垃圾,模型学到的背景分布就只是“垃圾周围的背景”,一旦部署到干净的广场或刚扫过的马路,模型会因为没见过这种背景而制造大量假阳性。
做法是往训练集里加 200 到 500 张完全没有垃圾的场景图,例如雨后积水的地面、落叶堆、井盖、树影斑驳的人行道、夜间灯光反射的路面。这些图直接放进train/images,不生成任何标签文件。ultralytics 会把找不到标签文件的图片当作背景样本参与训练,不会额外输出一个背景类别,也不会报错。训练完成后,用验证集跑一轮,导出 confusion matrix:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') res = model.val(data='trash.yaml', imgsz=1280) res.confusion_matrix.plot(save_dir='runs/detect/train/', names=model.names)生成的confusion_matrix.png里,看最右侧背景列。如果某一类对应的背景列数值偏高,说明该类经常和背景混淆。把对应图片捞出来人工看一遍,确认是标注漏框还是背景过像。这个循环重复两轮之后,背景误检通常能降一半以上。
4.3 切图推理:用 SAHI 处理烟头级别的目标
小目标漏检的另一个硬解法是切图推理。把原图切成若干瓦片,每个瓦片单独过模型,再把结果映射回原图坐标。这个思路对垃圾检测尤其有效,因为垃圾目标的绝对像素往往小于 32,但切成 512 瓦片后,它在瓦片中占比显著提升,特征提取自然更充分。
推荐用 SAHI 库做切图推理,它对 YOLO 系列模型有现成适配:
pip install sahi推理脚本:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model = AutoDetectionModel.from_pretrained( model_type='yolov8', model_path='runs/detect/train/weights/best.pt', confidence_threshold=0.25, image_size=1280 ) res = get_sliced_prediction( 'street.jpg', model, slice_height=512, slice_width=512, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) res.export_visuals(export_dir='output/')几个参数说明:slice_height=512和slice_width=512是瓦片尺寸,不建议用 640,垃圾目标太小,瓦片越大放大效果越弱;overlap_height_ratio=0.2和overlap_width_ratio=0.2是重叠率,保证跨瓦片的目标至少在某个瓦片里是完整的,如果目标正好被切在边界上,没有重叠就会漏检。切图推理只建议在推理阶段用,训练阶段不需要切图,因为 Mosaic 增强已经做了类似的事,训练时再切会破坏目标的上下文信息。
4.4 小目标增强:Mosaic 和复制粘贴的配合
训练阶段的小目标处理主要靠增强策略。YOLOv8 默认开启的 Mosaic 增强对中等目标效果好,但小目标在四图拼接后会被进一步缩小。配合第 3 章的复制粘贴增强,把小目标类别单独放大贴到背景图上,效果更可控。一个经验值:烟头这类小于 16 像素的目标,复制粘贴时把目标放大 1.5 到 2 倍再贴,让模型先学会“烟头在场景中的上下文是什么样”,推理时再通过 SAHI 还原小目标检测能力。
5. 验证口径与数据集迭代:垃圾场景还要盯住哪些指标
5.1 mAP@0.5 和 mAP@0.5:0.95 分开看
垃圾目标检测的业务指标通常选 mAP@0.5,因为环卫侧关心的是“这里有没有垃圾”而不是“框有多准”。定位不精确的框,比如框得比实际目标大一圈,在 mAP@0.5 下可能仍然算正确检测,但在 mAP@0.5:0.95 下就掉分了。如果你的模型 mAP@0.5 在 0.85 但 mAP@0.5:0.95 只有 0.5,说明定位质量不足,优先做回归分支的调优,而不是继续堆数据。
顺带提醒:如果往多模态路线走,把可见光和热红外图像做融合检测,评价口径不要混用。红外小目标检测里的评价参数(比如基于信杂比和局部对比度的指标)针对的是弱目标增强效果,和可见光目标检测的 AP 体系不是一回事,混用会导致团队对模型能力产生误判。
5.2 按场景拆验证集,别只报一个总数
垃圾分类的部署场景往往横跨白天、夜间、雨后、逆光四个状态,同一模型在不同光照下的表现差异可能超过 20 个点。建议把测试集按目录拆开:
yolo detect val model=best.pt data=trash.yaml source=val/day yolo detect val model=best.pt data=trash.yaml source=val/night yolo detect val model=best.pt data=trash.yaml source=val/rainsource参数会覆盖 data 里的 val 路径。分别看三个场景的 mAP,你会很容易发现夜间场景的 AP 明显偏低,那就针对性补夜间数据,而不是整体加数据。这个拆分逻辑同样适用于点位类型:河道、路面、景区步道分开评,每个场景的负样本池也要分开建。
5.3 数据质量闭环:标注一致性是上限
当 mAP 连续两轮不再上升,先别急着换网络,回到标注规范上去查一致性。垃圾目标检测最容易出现的问题有:压扁的物体边框怎么定、多个物体挨在一起时要不要分开标、半遮挡物体算正样本还是忽略。三个人标同一张图,同一目标的类别和边框如果差异超过 5%,模型的回归上限就会卡在标注噪声上。另一个常见问题是重复利用公开集时产生的伪标签错误——自动生成的框没有人工复核直接进数据集,这类错误对 AP 的侵蚀比数据量不足更隐蔽。把每轮验证集里置信度在 0.3 到 0.7 之间的样本定期抽出来复核,这部分的标注错误率基本就是模型还能再涨几个点的余量。
本文还有配套的精品资源,点击获取