news 2026/10/10 20:06:11

VOC格式垃圾分类数据集构建与YOLO边缘部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VOC格式垃圾分类数据集构建与YOLO边缘部署实战

简介:本资源是面向计算机视觉初学者与YOLO目标检测实践者的高质量垃圾分类检测数据集,适用于模型训练、算法验证及课程设计等真实场景任务。数据集共15000张真实场景下的JPG图像,涵盖纸张、塑料、果皮、玻璃杯、易拉罐、厨余垃圾等常见类别,标注完整且质量可靠;配套提供19640个VOC格式XML文件与19641个YOLO格式TXT文件,分别存放于独立目录,开箱即用,支持主流检测框架快速接入。压缩包总计58921个文件,大小为994.56MB,结构规整、命名统一,便于批量加载与路径管理。目前已有1336人学习下载,资源作者zhiqingAI同步公开了完整检测流程与效果展示(见CSDN博文参考链接),读者可直接复现训练过程、评估指标及可视化结果,显著降低数据准备与标注门槛。

1. VOC垃圾分类检测数据集:不是Pascal VOC原版,而是专为城市环卫AI落地改造的轻量级标注集合

你手头拿到的“VOC垃圾分类检测数据集”,大概率不是2005年那套经典Pascal VOC(含20类通用物体)的直接复刻——它是一套基于VOC XML标注规范、但类别与场景彻底重构的垂直领域数据集。核心目标很务实:让YOLOv5/v8、Faster R-CNN这类主流检测模型,在真实垃圾转运站、社区回收点、智能垃圾桶前端摄像头拍出的低分辨率、强光照变化、遮挡严重的图像上,能稳定识别出“可回收物、有害垃圾、厨余垃圾、其他垃圾”四类(或进一步细分为塑料瓶、纸箱、电池、菜叶等子类)。它不追求学术SOTA,而卡在“部署后误检率低于8%、单帧推理<80ms、模型体积<15MB”这三条工业红线里。适合正在做智慧环卫硬件集成、城管AI巡检系统、校园垃圾分类督导APP的工程师;不适合拿去刷COCO排行榜——它的验证集只有623张图,但每张都标了遮挡等级、模糊度、光照标签,且附带真实部署时必踩的“反光桶盖误检为金属”“湿纸巾粘连误判为厨余”等bad case清单。


2. 为什么选VOC格式?从标注规范到训练链路的硬性适配逻辑

2.1 VOC XML结构如何支撑垃圾分类的特殊需求

VOC格式看似古老,但在边缘设备部署中反而成了“减负利器”。它的XML文件(如000012.xml)用纯文本描述bbox坐标、类别、难例标记,不依赖二进制序列化或数据库索引。这对三类场景至关重要:

  • 嵌入式设备标注校验:树莓派4B上用xml.etree.ElementTree解析一个XML平均耗时仅3.2ms,比加载JSON+OpenCV解码快4.7倍;
  • 人工审核友好:环卫工人培训时,直接打开XML就能看到<name>plastic_bottle</name>和<bndbox><xmin>124</xmin><ymin>89</ymin><xmax>210</xmax><ymax>175</ymax></bndbox>,无需安装标注工具;
  • 跨平台兼容性:同一份XML,既能喂给LabelImg(Windows)、也能被CVAT(Web)、甚至国产标注平台“极视角”直接导入,避免格式转换丢框。

关键改造点在于<object>节点的扩展:

<object> <name>plastic_bottle</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <occlusion_level>2</occlusion_level> <!-- 新增:0=无遮挡,1=部分遮挡,2=严重遮挡 --> <light_condition>overexposed</light_condition> <!-- 新增:underexposed/normal/overexposed --> <bndbox> <xmin>124</xmin> <ymin>89</ymin> <xmax>210</xmax> <ymax>175</ymax> </bndbox> </object>

提示:occlusion_level和light_condition字段虽非VOC标准,但所有主流训练框架(MMDetection/YOLOv8)的XML解析器都支持自定义字段读取——只要在数据加载器里加两行代码即可提取,不破坏原有流程。

2.2 从VOC目录结构到YOLO训练的最小转换路径

VOC数据集标准目录是:

VOCdevkit/ ├── VOC2007/ │ ├── Annotations/ # 所有XML文件 │ ├── JPEGImages/ # 所有.jpg图像 │ ├── ImageSets/ │ │ └── Main/ # train.txt, val.txt, trainval.txt(存图片ID,不含扩展名) │ └── ...

但直接喂给YOLO会报错——YOLO要求images/和labels/平铺,且label文件需为TXT格式(class_id center_x center_y width height,归一化到0~1)。转换脚本必须解决三个痛点:

  1. 类别ID映射防错:VOC的name是字符串(如"battery"),YOLO需要整数ID,且ID顺序必须与names列表严格一致;
  2. 坐标归一化陷阱:<xmax>-<xmin>是像素宽,但若图像被resize(如训练时统一缩放至640×640),需用原始尺寸归一化,否则bbox偏移;
  3. 难例样本过滤:<difficult>=1的样本在VOC中常被跳过,但垃圾分类中“半埋在垃圾堆里的废电池”恰恰是高价值难例,必须保留。

以下Python脚本完成安全转换(已实测处理12,843张图零丢失):

# voc2yolo.py import os import xml.etree.ElementTree as ET from pathlib import Path # 垃圾分类类别映射表(严格按YOLO训练时names顺序!) CLASS_NAMES = ["plastic_bottle", "paper_box", "battery", "food_waste", "other_garbage"] NAME_TO_ID = {name: i for i, name in enumerate(CLASS_NAMES)} def convert_voc_to_yolo(voc_root: str, output_dir: str): voc_ann_dir = Path(voc_root) / "VOC2007" / "Annotations" voc_img_dir = Path(voc_root) / "VOC2007" / "JPEGImages" output_img_dir = Path(output_dir) / "images" output_label_dir = Path(output_dir) / "labels" output_img_dir.mkdir(exist_ok=True, parents=True) output_label_dir.mkdir(exist_ok=True, parents=True) for xml_file in voc_ann_dir.glob("*.xml"): tree = ET.parse(xml_file) root = tree.getroot() # 获取原始图像尺寸(关键!不用resize后的尺寸) size = root.find("size") img_width = int(size.find("width").text) img_height = int(size.find("height").text) # 构建对应图像路径并复制 img_name = root.find("filename").text img_path = voc_img_dir / img_name if not img_path.exists(): print(f"警告:图像缺失 {img_name}") continue # 复制图像到output_img_dir(保持原名) import shutil shutil.copy(img_path, output_img_dir / img_name) # 生成YOLO label文件(同名,.txt后缀) yolo_label_path = output_label_dir / f"{xml_file.stem}.txt" with open(yolo_label_path, "w") as f: for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in NAME_TO_ID: print(f"跳过未知类别 {name} in {xml_file.name}") continue # 获取bbox(VOC坐标是1-based,YOLO要求0-based归一化) bndbox = obj.find("bndbox") xmin = max(0, int(bndbox.find("xmin").text) - 1) ymin = max(0, int(bndbox.find("ymin").text) - 1) xmax = min(img_width, int(bndbox.find("xmax").text)) ymax = min(img_height, int(bndbox.find("ymax").text)) # 归一化到0~1(用原始尺寸!) x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height # 写入YOLO格式:class_id x_center y_center width height f.write(f"{NAME_TO_ID[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n") if __name__ == "__main__": convert_voc_to_yolo("/path/to/VOCdevkit", "/path/to/yolo_dataset")

参数说明与血泪经验:

  • NAME_TO_ID字典必须与YOLO配置文件中的names列表完全一致,顺序错一位会导致所有battery被识别成paper_box;
  • max(0, ...-1)和min(img_width, ...)是防越界关键——VOC标注工具偶尔会把xmin标成0或xmax标超图像宽,不处理会导致YOLO训练时nan loss;
  • 脚本默认不过滤<difficult>=1样本,因垃圾分类中“难例”正是模型提升的关键;若需过滤,加一行if obj.find("difficult").text == "1": continue即可。

3. 数据集划分与增强策略:针对垃圾图像特性的定制化方案

3.1 按场景分布划分train/val/test,而非随机切分

随机划分在垃圾分类中是灾难性的。我们实测发现:某批“厨余垃圾”样本集中出现在梅雨季拍摄的图像中(高湿度导致颜色泛白、纹理模糊),若随机切分,val集可能全无梅雨样本,导致模型在真实雨天场景下mAP暴跌32%。正确做法是按拍摄时间+地点+设备型号三维分层抽样:

维度分层规则占比(train/val/test)
时间晴天/阴天/雨天/夜间(用XML中light_condition字段)40% / 30% / 20% / 10%
地点社区回收点(A/B/C区)、转运站(D/E区)、学校食堂(F区)每区按图像量比例分配
设备海康DS-2CD3T47G2-L(主摄)、大华IPC-HFW5849T1-ZE(广角)、手机采集(华为P40)60% / 25% / 15%

执行命令(使用scikit-learn的StratifiedShuffleSplit):

# 先生成带元信息的CSV(含light_condition, location, camera_model列) python generate_split_csv.py --voc_root /path/to/VOCdevkit --output meta.csv # 按三维度分层切分(需自定义分层键) python split_dataset.py \ --csv meta.csv \ --stratify_cols light_condition,location,camera_model \ --train_ratio 0.7 \ --val_ratio 0.2 \ --test_ratio 0.1 \ --output_dir /path/to/splits

注意:split_dataset.py需重写StratifiedShuffleSplit的stratify参数为多列组合哈希值,否则sklearn默认只支持单列分层。

3.2 垃圾图像专属增强:对抗反光、污渍、粘连的3个核心操作

通用增强(如RandomHorizontalFlip)对垃圾数据效果甚微——塑料瓶不会左右翻转,湿纸巾粘连无法靠旋转解决。我们验证有效的增强组合:

增强类型参数设置(Albumentations库)解决的问题验证效果(mAP@0.5)
反光抑制CLAHE(p=0.8, clip_limit=2.0, tile_grid_size=(8,8))桶盖反光导致特征丢失+1.8%
污渍模拟RandomShadow(p=0.5, num_shadows_lower=1, num_shadows_upper=3)模拟垃圾桶壁油渍、泥点遮挡bbox+2.3%
粘连分离ElasticTransform(p=0.3, alpha=120, sigma=120 * 0.05, alpha_affine=120 * 0.03)微调粘连物体边缘,防止误判为单一大物体+3.1%

YOLOv8训练配置中启用(data.yaml同级新建augment.yaml):

# augment.yaml albumentations: - name: CLAHE p: 0.8 clip_limit: 2.0 tile_grid_size: [8, 8] - name: RandomShadow p: 0.5 num_shadows_lower: 1 num_shadows_upper: 3 - name: ElasticTransform p: 0.3 alpha: 120 sigma: 6.0 # 120 * 0.05 alpha_affine: 3.6 # 120 * 0.03

提示:ElasticTransform的alpha值必须与图像分辨率匹配——若训练图缩放至640×640,alpha=120合适;若用1280×1280,则需调至alpha=240,否则形变过弱。


4. 训练与评估避坑指南:那些让模型在垃圾场集体翻车的5个致命细节

4.1 现象:训练loss震荡剧烈,val mAP始终卡在21.3%不上升

原因:VOC数据集中的<truncated>字段被误读为“截断物体应忽略”,但垃圾分类中“半截露出的电池”恰恰是关键样本。YOLO默认丢弃truncated=1的bbox,导致正样本锐减。
解决:在YOLOv8的dataset.py中注释掉截断过滤逻辑:

# yolov8/utils/datasets.py 第127行附近 # if obj.find('truncated').text == '1': # ← 删除此行及后续continue # continue

4.2 现象:测试时大量“塑料瓶”被识别为“纸箱”,混淆矩阵显示两类交叉熵高达0.89

原因:原始VOC标注中plastic_bottle和paper_box的RGB直方图高度相似(都是浅色矩形),模型过度依赖颜色而非纹理。
解决:在数据加载阶段强制添加通道扰动:

# 在YOLOv8的dataset.py中,transform函数内加入 if self.augment: # 对塑料瓶和纸箱类别做针对性扰动 if class_id in [0, 1]: # 0=plastic_bottle, 1=paper_box # 随机交换R/G通道(破坏颜色线索,逼模型学纹理) if random.random() > 0.5: img = img[:, :, [1, 0, 2]] # R<->G swap

4.3 现象:模型在测试集上mAP达78%,但部署到海康IPC摄像头时误检率飙升至41%

原因:训练图来自单反相机(高动态范围),而IPC输出为8-bit JPEG(色彩压缩严重),HSV空间V通道信息丢失。
解决:训练前对所有图像做JPEG有损压缩模拟:

# 在数据增强pipeline中插入 import cv2 def jpeg_compress(image, quality=75): encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality] _, encoded = cv2.imencode('.jpg', image, encode_param) return cv2.imdecode(encoded, cv2.IMREAD_COLOR) # 然后在albumentations pipeline中调用 A.Lambda(image=jpeg_compress, p=0.7)

4.4 现象:other_garbage类别召回率仅33%,大量拖鞋、玩具被漏检

原因:“其他垃圾”类别内部差异极大(硬质拖鞋vs软质毛绒玩具),但VOC标注未提供材质属性,模型无法学习共性。
解决:引入类别内聚损失(Intra-class Compactness Loss),修改YOLOv8的loss.py:

# 在ComputeLoss.__call__中,计算cls_loss后追加 if self.cls_loss_type == "compact": # 计算同一类别预测框的特征向量余弦距离均值 cls_feats = pred_cls[targets[:, 1] == 4] # other_garbage的feature if len(cls_feats) > 2: dists = torch.pdist(cls_feats, p=2) compact_loss = torch.mean(dists) * 0.05 # 权重0.05 loss += compact_loss

4.5 现象:训练100轮后loss归零,但所有预测框坐标全为[0,0,0,0]

原因:VOC XML中<xmin>等坐标偶尔为浮点数(如<xmin>124.0</xmin>),而YOLO解析时强制int()导致截断为0。
解决:在XML解析环节加固:

# voc2yolo.py中修正 xmin = int(float(bndbox.find("xmin").text)) # 改为float再int ymin = int(float(bndbox.find("ymin").text)) xmax = int(float(bndbox.find("xmax").text)) ymax = int(float(bndbox.find("ymax").text))

5. 模型轻量化与边缘部署:让YOLOv8n在RK3399上跑出62FPS

5.1 为什么不用YOLOv5s而选YOLOv8n?三组实测数据说话

我们在RK3399(双核Cortex-A72 + 四核Cortex-A53)上对比主流轻量模型:

模型输入尺寸FPS(INT8)mAP@0.5模型体积关键瓶颈
YOLOv5s640×6404168.2%14.2MBNeck层Conv2D计算密集,A53核调度差
YOLOv7-tiny640×6403865.7%13.8MBE-ELAN结构分支多,内存带宽吃紧
YOLOv8n640×6406271.3%11.5MBC2f模块深度可调,A72核利用率超92%

YOLOv8n胜出的核心是C2f模块的梯度流优化:其将传统BottleneckCSP的串行残差改为并行分支,使RK3399的A72大核能同时处理主干与侧支特征,避免流水线阻塞。

5.2 RK3399部署全流程:从ONNX到RKNN,绕过3个官方文档没写的坑

步骤1:导出ONNX(关键参数)
# 必须指定dynamic_axes,否则RKNN转换失败 yolo export model=yolov8n.pt format=onnx \ dynamic=True \ simplify=True \ opset=12 \ imgsz=640 \ batch=1 \ dynamic_axes={"images": {0: "batch", 2: "height", 3: "width"}, "output": {0: "batch"}}

注意:opset=12是RKNN-Toolkit2的硬性要求,opset=13会报错“Unsupported operator”。

步骤2:RKNN转换(避坑代码)
# convert_rknn.py from rknn.api import RKNN rknn = RKNN(verbose=True) rknn.config( target_platform='rk3399', # 必须显式指定,不能用auto mean_values=[[0, 0, 0]], # VOC数据集未做归一化,mean=0 std_values=[[255, 255, 255]], # std=255(因原始图是0~255) quantize_input_node=True, optimization_level=3 ) # 加载ONNX时必须关闭输入检查(VOC图尺寸不固定) ret = rknn.load_onnx( model='yolov8n.onnx', inputs=['images'], input_size_list=[[1, 3, 640, 640]], outputs=['output'] # 注意:YOLOv8 ONNX输出名是'output',非'boxes'/'scores' ) ret = rknn.build(do_quantization=True, dataset='./dataset.txt') # dataset.txt需包含200张校准图路径 rknn.export_rknn('./yolov8n.rknn')

三大坑详解:

  • std_values=[[255,255,255]]:VOC图像未做/255.0归一化,若设std=1会导致量化后数值溢出;
  • outputs=['output']:YOLOv8 ONNX的输出节点名是output,官方文档写成boxes是旧版遗留错误;
  • dataset.txt必须用原始VOC JPEGImages中的图(未resize、未增强),否则校准偏差导致INT8精度暴跌。
步骤3:C++推理代码精简版(仅核心)
// infer.cpp #include "rknn_api.h" // 初始化RKNN上下文 rknn_context ctx; rknn_init(&ctx, "yolov8n.rknn", 0); // 输入预处理:BGR->RGB + resize(注意插值算法) cv::Mat img = cv::imread("test.jpg"); cv::Mat resized; cv::resize(img, resized, cv::Size(640, 640), 0, 0, cv::INTER_AREA); // INTER_AREA抗锯齿 // 推理 rknn_input inputs[1]; inputs[0].index = 0; inputs[0].type = RKNN_TENSOR_UINT8; inputs[0].fmt = RKNN_TENSOR_NHWC; inputs[0].size = 640*640*3; inputs[0].buf = resized.data; // 直接传RGB数据,不需额外转换 rknn_outputs outputs[1]; rknn_run(ctx, inputs, 1, outputs, 1); // 解析output(YOLOv8输出是[1, 84, 8400],需reshape) float* pred = (float*)outputs[0].buf; // 后处理:NMS、坐标反算(此处省略,用rknn_yolo_nms即可)

血泪经验:cv::INTER_AREA比cv::INTER_LINEAR在640×640 resize时快17%,且边缘更锐利——这对小目标(如纽扣电池)检测至关重要。


6. 效果验证与持续迭代:用真实垃圾场数据闭环驱动模型进化

6.1 不是看mAP,而是盯住“误投率下降曲线”

在智慧环卫项目中,业务方唯一关心的指标是误投率(Mis-drop Rate):居民将电池投入可回收桶、将塑料瓶投入厨余桶等错误行为的比例。我们设计了一套端到端验证流程:

阶段工具与方法输出物
线上监控在部署设备上开启--save-txt,每小时上传labels/*.txt到MinIO原始预测日志(含时间戳、设备ID)
人工复核用内部工具garbage-audit加载预测结果,叠加原始视频帧,由环卫督导员标注“是否真误投”audit_20240512.csv(含is_true_misdrop列)
归因分析聚类误投样本:用ResNet18提取图像特征,K-means分5类(反光类、雨天类、粘连类、小目标类、多目标类)misdrop_clusters.json
定向增强对“反光类”误投样本,批量生成CLIP引导的反光消除图(用Stable Diffusion ControlNet)新增500张高质量反光修正图

这个闭环让我们在3个月内将某试点小区的误投率从18.7%降至5.2%。

6.2 一个反直觉技巧:用“错误预测”本身训练新类别

当模型持续将“电子元件”误判为“电池”时,我们不急着加标注,而是:

  1. 收集1000张该类误判样本;
  2. 用CLIP模型(ViT-B/32)提取图文特征,计算image_feat @ text_feat["electronic_component"].T得分;
  3. 筛选得分>0.28的样本(CLIP认为确为电子元件);
  4. 将这些样本送入半监督工具FixMatch,用已有模型伪标签+强增强一致性约束,生成可靠标注;
  5. 将新类别electronic_component加入训练,ID=5,不重训全模型,只微调head层。

这套方法让新增类别在200次迭代内达到63.5% mAP,比从零标注快4.2倍。

我坚持在每次模型上线前,亲自去垃圾转运站蹲点2小时——看真实摄像头画面里模型哪里卡顿、哪里误检、环卫工怎么骂屏幕。那些在实验室里完美的mAP数字,永远比不上转运站师傅一句“这破玩意儿又把泡面盒当厨余了”的吐槽来得真实。把VOC垃圾分类数据集真正用活,不在于格式多标准、增强多花哨,而在于让每一行代码都闻得到垃圾站的潮气和阳光味。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 20:05:19

手写LALR(1)语法分析器:从BNF到可调试action表

简介&#xff1a;本资源是一份面向高校计算机专业本科生的编译原理课程设计实践材料&#xff0c;完整实现基于DFA的词法分析器与基于LALR(1)的语法分析器&#xff0c;覆盖编译前端核心环节&#xff0c;助力理解词法识别、状态转换、分析表构造及自底向上语法分析全过程。压缩包…

作者头像 李华
网站建设 2026/10/10 20:04:57

基于CNN和VGG的图像风格迁移实现与PyTorch实战解析

简介&#xff1a;一套面向计算机相关专业毕业设计、课程设计与深度学习项目实战的Python实现基于CNN卷积神经网络图像风格迁移完整源码项目。压缩包共包含93个文件&#xff0c;总体积约57MB&#xff0c;其中以jpg/png图片素材、py源码文件、pth预训练权重及mp4效果展示视频为主…

作者头像 李华
网站建设 2026/10/10 20:04:32

基于动态分时电价的电动汽车有序充放电实时优化调度Matlab实现

搞充放电优化调度的同行应该都有这种感觉&#xff1a;这个方向看着门槛不高&#xff0c;真做起来却被一堆约束和模型细节拖得头疼。今天分享的这套"基于动态分时电价的电动汽车有序充放电实时优化调度系统"Matlab实现&#xff0c;就是把充电成本最小化、配电网削峰填…

作者头像 李华
网站建设 2026/10/10 20:04:31

动态分时电价下电动汽车有序充放电优化调度与Matlab实现

充电桩刚铺开那两年&#xff0c;很多人觉得“电动汽车充电”这件事很简单&#xff0c;插上充电枪、扫码、充满走人&#xff0c;出一张账单就完了。但做园区充电运营或者电网侧规划的人&#xff0c;很快就会发现问题没那么简单&#xff1a;晚高峰同一批车同时插枪&#xff0c;整…

作者头像 李华
网站建设 2026/10/10 20:03:50

AI编程工具选型实战:两大头部产品的核心差异与避坑指南

这两个数字一出来&#xff0c;圈子里基本都在转。一款年收入25亿美元&#xff0c;一款10亿美元&#xff0c;放在任何一个行业软件品类里&#xff0c;都是金字塔尖的成绩。但真正有意思的不是数字本身&#xff0c;而是这两个数字背后传递的信号&#xff1a;AI 编程工具的付费市场…

作者头像 李华