☰
YOLOV5实战:垃圾桶满溢检测3类别数据集标注与训练全流程
2026/10/14 15:21:50 网站建设 项目流程

简介:这份资源是面向计算机视觉入门与进阶者的YOLOV5目标检测实战项目,聚焦垃圾桶满溢检测这一具体场景,解决从数据准备到模型训练、推理落地的完整流程问题。压缩包共约2000个文件,以1921个txt标签与说明、40个Python脚本、23个yaml配置、9个sh脚本及若干md文档为主,整体约450MB,涵盖数据集、训练代码与训练好的权重参数,代码经测试可直接运行。数据集包含3个类别:满溢的垃圾桶、未满溢的垃圾桶和垃圾,训练集2680张图片及对应标签,验证集669张图片及对应标签。项目迭代100个epoch,最优精度map0.5达0.91、map0.5:0.95达0.73,runs目录下保存了混淆矩阵、PR曲线、F1曲线及推理结果,便于复盘训练过程与调参思路。目前已有366人学习,适合希望快速上手YOLOV5实战、掌握数据标注与模型评估的读者参考。

1. 垃圾桶满溢检测:3 类别数据集到底在标什么

垃圾桶满溢检测这个方向,最近两年在智慧环卫、园区物业、市政巡检场景里被反复提起。它和常规的垃圾分类识别不是一回事:分类关心的是「扔进去的是什么」,满溢关心的是「这个桶还能不能继续装」。落到 YOLOV5 实战项目里,最常见的做法是把它当成一个 3 类别的目标检测任务——空桶/正常、半满、满溢(或溢出)。数据集规模通常不大,几百到两三千张图,标注框画在桶体或溢出物上,类别数固定为 3。

这个标题真正解决的是「从零搭一套可复现的满溢检测训练流程」:数据怎么标、类别怎么定、YOLOV5 怎么配、指标怎么看、误报怎么压。适合两类人——一类是手里已经有一批摄像头截图、想快速跑通 baseline 的算法同学;另一类是接了园区/环卫项目、需要判断这套方案值不值得投入的工程负责人。下面按数据、训练、调参、避坑、进阶的顺序讲透。

2. 3 类别怎么定:从业务语义到标注规范

2.1 类别定义决定模型上限

很多人一上来就标「满/不满」两类,跑完发现误报高得离谱。原因是「满」这个状态在视觉上是连续的:桶口齐平、冒尖、溢出到地面,三种画面差异极大,硬塞进一个类,模型学到的特征被平均掉。我一般会拆成 3 类,但拆法有两种,选哪种取决于你的业务动作。

第一种按「处置动作」拆:normal(无需处理)、half(预警,安排巡检)、full(立即清运)。这种拆法直接对应工单系统,落地最顺。第二种按「视觉形态」拆:empty、partial、overflow。它更适合纯视觉研究,但和业务动作对不齐,工程上要再做一层映射。实战项目里我推荐第一种,因为标注员判断「要不要叫车」比判断「满了几成」更一致,标注一致性直接决定 mAP 天花板。

类别顺序也有讲究。YOLOV5 的data.yaml里names列表的顺序就是类别 id,一旦训练完再改顺序,所有已训权重作废。所以定类别时把出现频率最高的放前面(通常是normal),能让早期 epoch 的正样本更均衡。

2.2 标注框画在哪:桶体还是溢出物

这是最容易翻车的地方。如果框画在桶体上,模型学的是「桶的样子」,那空桶和满桶的桶体外观几乎一样,模型只能靠桶口那一小块区域区分,难度极高。如果框画在溢出物上,满溢样本有框、正常样本没框,那normal类就没有正样本,YOLOV5 会把它当背景,指标直接崩。

常见做法是统一框在桶体上,但要求标注员把框贴紧桶的外轮廓,并且对full类额外标注桶口以上的溢出区域作为同一类的一部分。也就是一个full框可能比normal框高出一截,这个高度差本身就是强特征。下面是一个把标注转成 YOLO 格式的脚本片段,处理的就是这种「框高差异」带来的坐标归一化问题。

import os import cv2 # 把 VOC 风格的 xml 转成 YOLO txt,类别映射固定为 3 类 CLASS_MAP = {"normal": 0, "half": 1, "full": 2} def voc_to_yolo(xml_path, img_dir, out_dir): import xml.etree.ElementTree as ET tree = ET.parse(xml_path) root = tree.getroot() filename = root.find("filename").text img = cv2.imread(os.path.join(img_dir, filename)) h, w = img.shape[:2] # 归一化必须用真实图像尺寸,不能用预设值 lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLASS_MAP: continue # 未定义类别直接丢弃,避免训练时报 index 越界 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) # YOLO 格式:中心点 x,y 和宽高,全部除以图像宽高 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, filename.rsplit(".", 1)[0] + ".txt") with open(out_path, "w") as f: f.write("\n".join(lines))

逻辑说明:这段代码的核心是坐标归一化,cx/cy/bw/bh全部落在 0~1 之间,YOLOV5 训练时再乘回特征图尺寸。参数上,CLASS_MAP必须和data.yaml的names顺序完全一致,差一位就是灾难。h, w一定要从真实图像读,我见过有人图省事写死 1920×1080,结果手机拍的竖图全部错位,训练 loss 不降反升。

2.3 数据集划分与类别均衡

3 类别数据集最常见的坑是长尾:normal占 70%,full只占 10%。直接按 8:1:1 随机划分,验证集里full可能只有几张,mAP 波动巨大。我的做法是先按类别分层抽样,保证每个 split 里每类至少占该 split 的 15%,不足的用重复采样补。划分脚本里加一个stratify逻辑即可,sklearn 的train_test_split支持按标签分层,但多标签场景要自己写。

另外,满溢样本往往来自少数几个摄像头,如果按图随机划分,同一摄像头的相似画面会同时进训练和验证,指标虚高。正确做法是按「摄像头 id + 时间段」分组划分,让验证集覆盖训练时没见过的机位。这一步不做,线下 mAP 0.9、上线掉到 0.5 是常态。

3. YOLOV5 训练配置:从 data.yaml 到超参

3.1 data.yaml 与模型选型

YOLOV5 有 n/s/m/l/x 五个尺寸,垃圾桶满溢这种场景目标大、背景相对固定,yolov5s通常够用,参数量约 7M,单卡 1080Ti 就能训。如果误报压不下去再上yolov5m,但推理延迟会翻倍,边缘盒子要算清楚。data.yaml的写法如下:

# data.yaml path: ./dataset train: images/train val: images/val nc: 3 names: ["normal", "half", "full"]

nc必须等于names长度,多一个少一个都会在加载时抛错。path用相对路径时,训练命令要在项目根目录执行,否则找不到图。我一般写绝对路径,省得换机器就崩。

3.2 训练命令与关键参数

python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data data.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --cache

逐参数说:--img 640是输入分辨率,垃圾桶满溢的桶口细节在 640 下基本够,再高显存吃不消;--batch 16是单卡 8G 显存的稳妥值,显存够可以上 32;--epochs 150是因为数据集小,容易过拟合,早停靠--patience默认 50 兜底;--weights yolov5s.pt用官方预训练权重,小数据集上比从头训收敛快一倍;--hyp选low版本,它的数据增强更温和,适合目标形态固定的场景,用high会把桶体随机裁剪得面目全非;--cache把图缓存到内存,小数据集必开,能省 30% 以上 IO 时间。

训练启动后重点看三个输出:box_loss是否稳定下降、mAP@0.5是否在 50 epoch 后还在涨、val/val_loss是否开始反弹。反弹就是过拟合信号,该停就停。

3.3 推理与阈值调优

训练完拿best.pt跑推理,默认置信度阈值 0.25、NMS IoU 0.45。满溢检测里full类容易和half混淆,因为半满和满溢的桶口高度差可能只有几十像素。我的调法是先把--conf-thres提到 0.4,牺牲一点召回换精度,再看业务能不能接受漏报。如果漏报代价高(比如溢到地面必须处理),就降到 0.2 并加一个后处理:连续 3 帧都检出full才触发告警,用时间维度压误报。

# 简单的多帧确认逻辑 from collections import deque history = deque(maxlen=3) def confirm_full(detections, conf_thres=0.4): # detections: [(cls, conf), ...] has_full = any(c == 2 and conf > conf_thres for c, conf in detections) history.append(has_full) # 3 帧里至少 2 帧检出 full 才确认 return sum(history) >= 2

逻辑说明:deque(maxlen=3)维护最近 3 帧状态,sum(history) >= 2是投票阈值。参数conf_thres和投票窗口长度要按摄像头帧率调,25fps 下 3 帧约 0.12 秒,响应够快;如果帧率只有 5fps,窗口要拉长到 5 帧,否则一个瞬时遮挡就漏报。

4. 避坑与排查:满溢检测最常见的 5 个翻车点

4.1 现象:mAP 很高但上线全是误报

原因通常是验证集和训练集同源。同一摄像头、同一时段的画面被随机分到两边,模型记住了背景而不是桶的状态。解决:按摄像头 id 分组划分,验证集必须包含训练时没出现过的机位。如果机位太少,至少按时间段切,白天训、夜间验。

4.2 现象:full类召回极低

先查标注。很多标注员把「桶口齐平」标成half,把「冒尖」标成full,但两者视觉差异太小,模型学不动。解决:重新定义边界,把full定义为「有可见溢出物或桶盖无法闭合」,并在标注规范里配示例图。边界模糊的样本直接丢弃,宁缺毋滥。

4.3 现象:训练 loss 震荡不收敛

八成是学习率或 batch 问题。小数据集上--batch 16配默认 lr 可能偏大,把--hyp里的lr0从 0.01 降到 0.005 试试。另外检查data.yaml的nc和实际标注类别数是否一致,不一致时 YOLOV5 会把多余类别当背景,loss 表现就是降不下去。

4.4 现象:推理速度不达标

边缘盒子上跑yolov5s640 输入,TensorRT FP16 大概 15ms 一帧,如果要求 30fps 以上,要么降到--img 416,要么换yolov5n。注意--img改了之后,训练和推理必须一致,训练用 640 推理用 416,精度会掉 5~10 个点。

4.5 现象:夜间画面全黑,模型失效

红外摄像头和可见光摄像头的成像差异极大,如果训练集只有白天,夜间必崩。解决:训练集里混入夜间样本,至少占 20%;或者夜间单独训一个模型,按时间切换。别指望一个模型通吃所有光照,这是血泪经验。

5. 进阶:用切片推理和类别权重把full类拉回来

数据集小、full类样本少的时候,除了加数据,还有两个技巧值得试。第一个是切片推理(SAHI 思路):把 640 的图切成 4 块 320 的子图分别推理再合并,小目标召回能提 3~5 个点,代价是推理时间翻倍。垃圾桶满溢的桶口区域在整图里占比不大,切片对full类尤其有效。

第二个是类别权重。YOLOV5 的 loss 里分类部分默认等权,可以在hyp.yaml里手动调cls_pw,或者更直接地在数据集层面做重采样,把full类样本复制 2~3 份。我一般先试重采样,因为它不改代码,效果立竿见影。

验证时别只看 mAP,要单独看full类的混淆矩阵。如果full被大量预测成half,说明两个类的特征空间没拉开,这时候加数据比调参有用。下面是一个按类别统计验证集指标的小脚本:

import numpy as np def per_class_metrics(preds, gts, nc=3): # preds/gts: list of (cls, ...) 简化示意 matrix = np.zeros((nc, nc), dtype=int) for p, g in zip(preds, gts): matrix[g][p] += 1 # 行是真实类,列是预测类 for i in range(nc): tp = matrix[i][i] fn = matrix[i].sum() - tp fp = matrix[:, i].sum() - tp precision = tp / (tp + fp + 1e-6) recall = tp / (tp + fn + 1e-6) print(f"class {i}: P={precision:.3f} R={recall:.3f}") return matrix

逻辑说明:matrix[g][p]统计真实类 g 被预测成 p 的次数,对角线是正确预测。precision和recall的分母都加了1e-6防止除零。跑完看full那一行的分布,如果大量落在half列,就是类别边界问题,回去改标注规范比调模型快。

最后说个习惯:每次改完数据或超参,固定用同一批「难例」图跑一遍推理,肉眼比对框的位置和类别。指标会骗人,但你看一眼桶口那个框有没有盖住溢出物,心里就有数了。这套流程我踩过最深的坑是早期图省事只标了两类,训到一半发现业务要区分「预警」和「清运」,只能全部重标,一周白干。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询