简介:这份垃圾箱数据集面向从事目标检测训练与验证的开发者、学生及算法爱好者,提供VOC格式的标注样本,可用于训练或微调垃圾箱识别模型,解决特定场景下目标检测数据不足的问题。资源包共914个文件,包含457张jpg图片与457个对应的xml标注文件,压缩包约26.79MB,采用rar格式打包,无需解压密码,解压后图片与标注分文件夹存放,便于直接导入labelImg等工具查看标注情况。标注类别统一为dustbin,全部由labelImg完成,遵循准确框选目标边界、尽量覆盖图像中所有目标、并通过一致性检查确保标注正确性的原则。目前已有94人学习下载,适合作为目标检测入门练手或小规模实验的数据补充,帮助读者快速搭建训练集、验证标注质量并推进模型迭代。
1. 垃圾箱数据集 VOC 格式目标标注 457 张:小样本检测到底能不能落地
手上只有 457 张标注图,能不能训出一个能用的垃圾箱检测模型?这是我在做环卫场景智能化时被问得最多的问题。答案不是「能」或「不能」,而是取决于你怎么用这批 VOC 格式的目标标注数据。VOC 格式意味着每张图对应一个 XML 文件,里面记录了垃圾箱的边界框坐标、类别名和图像尺寸,这套格式从 PASCAL VOC 时代沿用至今,几乎所有主流检测框架都能直接读。457 张这个量级,放在 COCO 或 CrowdHuman 那种万级数据集面前确实寒酸,但在垂直场景里,如果标注质量过关、场景单一、目标类别少,它完全可以作为冷启动的种子数据。这篇文章面向的是手上有类似规模标注数据、想跑通垃圾箱检测的工程师和研究生,我会把从数据检查、格式转换、增强策略到训练调参的完整路径拆开讲,重点说清楚小样本条件下哪些操作是有效的、哪些是白费力气。
2. 拿到 457 张 VOC 标注先别急着训练:数据体检与格式转换
2.1 为什么 VOC 格式在小样本场景下反而省事
VOC 格式的核心是一个 XML 文件对应一张图,结构清晰:<size>记录宽高,<object>记录每个目标的类别和<bndbox>坐标。相比 COCO 的 JSON 大文件,VOC 的分散式存储在小数据集上有个实际好处——你改一张图的标注不会影响其他文件,版本管理也方便。457 张图的 XML 总量不大,用脚本批量处理几秒钟就跑完。
但 VOC 有个容易翻车的地方:坐标是 1-based 的,而很多训练框架内部用 0-based。如果你直接拿 VOC 的 xmin/ymin 去算 YOLO 格式的归一化中心点,会整体偏移一个像素。单看一个像素无所谓,但在小目标密集的场景里,一个像素的偏移可能让框和真实目标错位。我一般会在转换脚本里统一减 1,再算归一化。
另一个现实问题是标注一致性。457 张图如果经过多人之手,类别名可能写成trash_bin、trashbin、TrashBin三种。VOC 的<name>字段是纯文本,没有 schema 约束,这种不一致在训练时会被当成三个不同类别,直接导致模型学废。所以第一步不是转格式,是先把所有 XML 里的类别名拉出来做一次去重统计。
2.2 用脚本做数据体检:类别分布、框尺寸、异常标注
下面这段脚本做三件事:统计类别名、统计每类目标数量、检查有没有宽高为 0 或超出图像边界的框。
import os import xml.etree.ElementTree as ET from collections import Counter ann_dir = "Annotations" img_dir = "JPEGImages" class_counter = Counter() size_list = [] bad_boxes = [] for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.findall("object"): name = obj.find("name").text.strip() class_counter[name] += 1 bbox = obj.find("bndbox") xmin = int(float(bbox.find("xmin").text)) ymin = int(float(bbox.find("ymin").text)) xmax = int(float(bbox.find("xmax").text)) ymax = int(float(bbox.find("ymax").text)) w = xmax - xmin h = ymax - ymin size_list.append((w, h)) # 检查异常框 if w <= 0 or h <= 0 or xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: bad_boxes.append((xml_file, name, xmin, ymin, xmax, ymax)) print("类别分布:", class_counter) print("异常框数量:", len(bad_boxes)) for b in bad_boxes[:10]: print("异常:", b) # 框尺寸统计 if size_list: ws = [s[0] for s in size_list] hs = [s[1] for s in size_list] print(f"框宽 min/mean/max: {min(ws)}/{sum(ws)/len(ws):.1f}/{max(ws)}") print(f"框高 min/mean/max: {min(hs)}/{sum(hs)/len(hs):.1f}/{max(hs)}")这段脚本的逻辑很直白:遍历所有 XML,累加类别计数,同时把每个框的宽高和边界合法性检查一遍。参数上唯一需要注意的是int(float(...))这个写法——有些标注工具会写出123.0这种浮点字符串,直接int()会报错。跑完之后你会拿到三个关键信息:类别是否统一、每类样本量是否均衡、有没有需要手动修的脏标注。如果发现某个类别只有个位数样本,要么补标,要么在训练时直接合并到相近类别。
2.3 VOC 转 YOLO 格式:转换脚本与四个边界坑
YOLO 系列训练需要的是每张图一个 txt,每行class_id cx cy w h,全部归一化到 0-1。转换脚本本身不长,但边界情况多。
import os import xml.etree.ElementTree as ET ann_dir = "Annotations" label_out = "labels" classes = ["trash_bin"] # 按体检结果确定类别列表 os.makedirs(label_out, exist_ok=True) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in classes: continue cls_id = classes.index(name) bbox = obj.find("bndbox") # VOC 坐标是 1-based,减 1 转 0-based xmin = int(float(bbox.find("xmin").text)) - 1 ymin = int(float(bbox.find("ymin").text)) - 1 xmax = int(float(bbox.find("xmax").text)) - 1 ymax = int(float(bbox.find("ymax").text)) - 1 # 裁剪到图像边界内 xmin = max(0, min(xmin, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) xmax = max(0, min(xmax, img_w - 1)) ymax = max(0, min(ymax, img_h - 1)) # 归一化中心点和宽高 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h if w <= 0 or h <= 0: continue lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") txt_name = xml_file.replace(".xml", ".txt") with open(os.path.join(label_out, txt_name), "w") as f: f.write("\n".join(lines))四个边界坑分别是:第一,坐标减 1 不能忘,否则整体偏移;第二,裁剪到边界内,有些标注框会超出图像尺寸一两个像素;第三,宽高为 0 的框直接跳过,不然归一化后是 0,训练时算 loss 会出 NaN;第四,类别名映射要用体检后确定的统一列表,不要用set()动态生成,否则每次跑出来的 class_id 顺序可能不一样。转换完成后,建议随机抽 5 张图用可视化脚本画框确认一遍,这一步花两分钟,能省掉后面几小时的排查。
3. 457 张图怎么训:增强策略、模型选型与训练参数
3.1 小样本下哪些增强真正有用
457 张图做检测,数据增强不是可选项,是必选项。但增强手段不能乱加,有些增强在通用数据集上有效,在垃圾箱这种场景反而有害。我按实际效果排个序。
Mosaic 增强是 YOLO 系列自带的,把四张图拼成一张,等效于增加了场景多样性。对垃圾箱检测来说,这个增强几乎必开,因为它能让模型见到更多背景组合。但要注意,Mosaic 会让小目标变得更小,如果你的垃圾箱在图中占比本来就小,Mosaic 后可能缩到十几个像素,反而增加学习难度。我一般会把 Mosaic 的概率设在 0.5 到 0.8 之间,而不是默认的 1.0。
HSV 色彩抖动是第二有用的。垃圾箱场景的光照变化大,白天、傍晚、阴天的色温差异明显,HSV 增强能提升模型对光照的鲁棒性。参数上,色调偏移控制在 0.015 左右,饱和度 0.7,明度 0.4,这个范围不会把颜色改得面目全非。
随机缩放和随机平移也有用,但翻转要谨慎。水平翻转对垃圾箱通常没问题,但垂直翻转要看你场景里垃圾箱是否总是正立。如果数据里有倒置的垃圾箱(比如翻倒的),垂直翻转可以开;如果没有,开了反而引入噪声。
不适合的增强:Cutout 和 Random Erasing。这两种随机遮挡在样本充足时能提升泛化,但 457 张图的条件下,遮挡会让本就不多的目标信息进一步丢失,模型容易学不到完整特征。旋转增强也要小心,大角度旋转会让边界框变大,引入大量背景,对小样本不友好。
3.2 模型选型:YOLOv8n 还是更大模型
在 457 张图的量级上,模型参数量不是越大越好。我做过对比,YOLOv8n(nano)和 YOLOv8s(small)在这个数据量上的最终 mAP 差距通常在 1 到 2 个点以内,但 nano 的训练速度快一倍以上,显存占用也低得多。如果你只有一张消费级显卡,nano 是更务实的选择。
选 nano 的另一个理由是过拟合风险。457 张图,如果模型参数量到几十 M,训练不到 100 轮就会在训练集上接近完美、验证集上开始抖动。nano 的容量有限,反而起到了一种隐式正则化的作用。当然,如果你能用更强的增强和更长的训练周期,s 版本也可能追上来,但投入产出比不划算。
预训练权重一定要用。COCO 上预训练的 YOLOv8n 已经学到了通用的边缘、纹理特征,这些特征对垃圾箱检测同样有效。从零开始训 457 张图,收敛会慢很多,而且容易卡在局部最优。加载预训练权重后,通常 50 到 100 轮就能看到验证集指标趋于稳定。
3.3 训练参数怎么设:一份可直接抄的配置
下面这份配置基于 Ultralytics 的 YOLOv8 训练接口,针对 457 张 VOC 转 YOLO 的数据集调过。
from ultralytics import YOLO model = YOLO("yolov8n.pt") # 加载 COCO 预训练权重 results = model.train( data="trash_bin.yaml", # 数据集配置文件路径 epochs=150, # 小样本建议 100-200 轮 imgsz=640, # 输入尺寸,与标注时分辨率匹配 batch=16, # 根据显存调整,nano 模型 16 通常够 lr0=0.01, # 初始学习率 lrf=0.01, # 最终学习率系数 momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, # 预热轮数,小样本建议 3-5 mosaic=0.7, # Mosaic 概率,不设 1.0 hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, # 不开启旋转 translate=0.1, scale=0.5, fliplr=0.5, # 水平翻转概率 flipud=0.0, # 垂直翻转关闭 patience=30, # 30 轮无提升则早停 save_period=10, # 每 10 轮存一次权重 device=0 # 显卡编号 )参数说明几个关键点。lr0=0.01是 SGD 的初始学习率,如果你换成 AdamW,这个值要降到 0.001 左右。warmup_epochs=3.0在小样本上很重要,前几轮学习率从很低慢慢升上来,避免一开始就把预训练权重带偏。patience=30是早停,457 张图的验证集波动可能比较大,30 轮不提升再停,比默认的 50 轮更节省时间。mosaic=0.7而不是 1.0,原因前面说过,给模型留一部分原始图像分布。
数据集配置文件trash_bin.yaml的写法:
path: ./dataset train: images/train val: images/val nc: 1 names: ["trash_bin"]这里nc是类别数,names要和转换脚本里的classes列表完全一致。训练集和验证集的划分,457 张图我一般按 8:2 分,也就是 365 张训练、92 张验证。如果类别极不均衡,要用分层抽样,保证验证集里每类都有足够样本。
4. 训练过程中最容易翻车的五个地方
4.1 现象:loss 降到很低但验证集 mAP 一直是 0
原因通常有两个。一是验证集的标注路径写错了,YOLO 找不到验证集的 label 文件,算 mAP 时全是背景,自然为 0。二是类别名映射不一致,训练时 class_id 是 0,验证时因为 names 列表顺序不同变成了别的 id。排查方法:先确认val路径下的 images 和 labels 一一对应,再打印一次训练集和验证集的类别分布,看是否一致。
4.2 现象:训练到一半 loss 突然变成 NaN
小样本训练里 NaN 最常见的原因是学习率过大加上某个 batch 里有异常框。异常框的来源可能是转换时没过滤掉的宽高为 0 的框,也可能是标注里 xmax 小于 xmin。解决办法:在转换脚本里加断言,确保每个框的 xmax > xmin 且 ymax > ymin;训练时把lr0降到 0.005 再试。如果还出 NaN,检查输入图像有没有损坏文件,用 PIL 打开每张图验证一遍。
4.3 现象:模型只检测大垃圾箱,小目标全漏
这是小样本检测的典型问题。457 张图里如果小目标占比低,模型会倾向于忽略小目标。解决手段有三个:一是提高输入分辨率,从 640 提到 800 或 1024,让小目标占据更多像素;二是在增强里降低 Mosaic 概率,避免小目标被进一步缩小;三是检查 anchor 配置,YOLOv8 是 anchor-free 的,但如果你用的是 YOLOv5,需要重新聚类 anchor。另外,验证集里小目标的 mAP 要单独看,不要只看总体 mAP。
4.4 现象:验证集 mAP 波动很大,每次跑结果差好几个点
457 张图的验证集只有 90 多张,统计噪声本来就大。如果每次训练结果差异超过 5 个点,说明验证集划分可能不合理。检查方法:看验证集里是否包含了训练集里没有的场景或光照条件。如果是随机划分导致的,可以固定随机种子,或者做 5 折交叉验证取平均。另一个原因是 batch size 太小,梯度估计噪声大,把 batch 从 8 提到 16 或 32 能缓解。
4.5 现象:推理时框的位置整体偏移
如果训练时指标正常,推理时框偏移,大概率是预处理不一致。训练时 YOLO 会把图像 resize 到 640x640 并做 padding,推理时如果你自己写的预处理没有做同样的 padding,坐标映射就会错。用 Ultralytics 的model.predict()接口不会出这个问题,但如果你把模型导出成 ONNX 再自己写后处理,就要手动复现 letterbox 的缩放和 padding 逻辑。检查方法:拿一张训练集里的图做推理,看框是否和标注重合,如果不重合就是预处理问题。
5. 457 张之后:用半自动标注把数据滚到 2000 张
457 张能训出一个 baseline,但要让模型真正稳定,数据量还得往上走。我的习惯是训完第一版之后,用模型去推理未标注的图片,把置信度高的预测框转成 VOC 格式,人工只做修正和删减。这个流程能把标注效率提升三到五倍。
具体操作:先用训好的模型对一批新图做推理,导出置信度大于 0.5 的框,写成 XML。然后人工过一遍,删掉误检、补上漏检、修正偏移的框。修正后的数据加入训练集,重新训一版,再用新模型去推理下一批。两到三轮之后,数据量通常能从 457 涨到 1500 到 2000 张,mAP 会有明显提升。
这里有个技巧:推理时把conf阈值设低一点,比如 0.25,让模型多输出一些候选框,人工删比人工画快得多。但conf太低会引入大量误检,增加人工负担,0.25 到 0.35 之间是个平衡点。另外,半自动标注的框质量参差不齐,加入训练集前要用前面说的体检脚本再跑一遍,确保没有异常框混进去。
验证半自动标注是否有效的方法:留出一批完全人工标注的图作为测试集,不参与训练。每轮半自动标注后,在这个测试集上评估,看 mAP 是否持续上升。如果某一轮之后 mAP 反而下降,说明自动标注引入的噪声超过了新增数据的收益,需要提高conf阈值或增加人工修正的比例。
我自己踩过的坑是贪快,把conf设到 0.15,结果自动标注里一半是误检,人工修正花的时间比从头标还多。后来固定在 0.3,每轮只加 200 到 300 张,虽然慢一点,但数据质量可控。小样本检测这件事,数据质量永远比数量重要,457 张干净标注比 2000 张脏标注管用得多。希望帮到你。
本文还有配套的精品资源,点击获取