☰
YOLOV5实战:垃圾桶满溢检测数据集训练与部署避坑指南
2026/9/28 17:02:26 网站建设 项目流程

简介:一套面向目标检测初学者与智慧环卫场景开发者的YOLOV5垃圾桶满溢检测实战项目。项目包含完整数据集、训练代码与已收敛权重,覆盖满溢、未满溢、垃圾三个类别,直接修改配置即可训练或推理。资源共2000个文件,以txt标签、py训练脚本、yaml模型配置、sh辅助脚本为主,压缩包约450MB,结构清晰便于快速上手。已有363人学习。数据集包含2680张训练图片与669张验证图片及各自标注标签;模型迭代100个epoch后最优mAP0.5达0.91,mAP0.5:0.95达0.73,附有混淆矩阵、PR曲线、F1曲线等可视化分析结果,runs/detect目录还保存了对推理结果的完整展示,方便复现与对比实验。

1. YOLOV5 垃圾桶满溢检测:一个 3 类别数据集能省掉多少现场巡检

物业、园区、车站这种人流密集区域,垃圾桶满溢是投诉高发点。传统做法是安排巡检员定时翻桶,既低效又容易漏检。“垃圾桶满溢检测”要解决的,就是用摄像头替代人眼,实时判断桶是否装满并触发清理工单。

这里说的“YOLOV5 实战项目:垃圾桶满溢检测数据集(3类别)”,是典型的目标检测落地场景:数据按 3 个类别组织好,配合 YOLOV5 把“yolov5 训练自己的数据集”完整链路跑通。核心问题不是算法选型,而是“3 类怎么定义、数据怎么组织、参数怎么调、部署怎么避坑”。

适合正在学目标检测的开发者、做智慧园区 demo 的工程师,以及拿毕业设计练手的学生。一份类别清晰、标注规范的数据集,配合正确训练流程,比自己从零攒数据少踩一半坑。

2. 满溢检测为什么选 YOLOV5:3 类别定义与数据采集选型

2.1 先定 3 个类别,再谈算法:满溢检测的标签语义

垃圾桶满溢检测本质上是一个目标检测任务,但业务上最关键的决策不在算法,而在“3 类是谁”。常见做法是按“正常 / 半满 / 满溢”三类划分,也就是 normal、half、full 三个语义级别。为什么是 3 个而不是 2 个?因为“未满 / 满溢”二分类看起来省事,但现场视频是连续状态,桶从空到满的中间态占绝大多数时间。如果只有二分类,系统会在桶装到 70% 时就开始满溢报警,保洁还没赶到,漏口已经堆起小山;或者反过来,为了避免误报把阈值调得很高,结果真满溢时又漏报。加入 half 这个中间态之后,报警策略可以做成“half 状态进入待处理队列,full 状态才推工单”,调度体验会正常得多。

标签语义必须落到标注规则上,不能靠标注员感觉。我一般会把三类定义写在标注规范里:normal 指桶内垃圾低于桶口、盖子可以正常闭合;half 指垃圾接近桶口、桶盖已经无法完全闭合但垃圾没有高出桶沿;full 指垃圾高出桶口沿,或者溢出物已经占据桶周围视野。这套定义同样适用于那种没有桶盖、只靠传感器判断的智能垃圾桶,只是视觉上把“盖子能否闭合”换成“垃圾平面是否超过桶口沿”。如果今后业务想做得更细,把 full 再拆成“轻微溢出 / 严重溢出”就是 4 类甚至 5 类,但那会直接抬高标注成本,3 类是这个场景里性价比比较高的折中。

一句话总结选型逻辑:YOLOV5 对这类“单目标多状态”任务非常顺手。垃圾桶是刚性轮廓,状态特征集中在顶部区域,YOLOV5 的 anchor 机制和 640 输入尺寸能很好覆盖;同时它的生态和资料比 YOLOV8 更全,很多一键部署方案都以 V5 为基准。当然如果你的项目从零开始且不依赖老代码,YOLOV8 也可以,但本实战方案以 YOLOV5 为主,因为它对新手更友好、排查经验更好找。

2.2 YOLOV5 环境配置:conda、依赖与权重文件的选择

训练前先把环境搭干净。常见做法是用 conda 建独立环境,Python 版本按 YOLOV5 官方 requirements 的要求来,一般 Python 3.8 到 3.10 之间都能跑。创建命令如下:

conda create -n yolo python=3.8 -y conda activate yolo pip install -r requirements.txt

requirements.txt 是 YOLOV5 仓库根目录下自带的依赖清单,把 torch、torchvision、opencv-python、numpy、matplotlib、pyyaml、tqdm 等都列好了,不需要自己一个个猜版本。如果本机有 NVIDIA 显卡,装依赖前先把 CUDA 版 PyTorch 装好,再装 requirements 里的其他包,否则 pip 会默认拉 CPU 版 torch,训练速度能差出十倍。

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt

权重文件的选择我有自己的习惯:先用 yolov5s.pt 跑通流程,确认数据和标注没大问题之后,再根据精度瓶颈决定是否换 yolov5m 或 yolov5l。s 模型训练速度最快,一个 3000 张的数据集在单张 3060 显卡上 80 轮大约两小时能跑完;m 模型精度更高但训练时间接近翻倍。不要一上来就用 l 或 x,满溢检测不是超高难度任务,s 或 m 通常够用,盲上大模型只会让调参周期变长。

2.3 数据采集:把现场拍成能训练的素材

采集阶段决定了后面所有工作的上限。我一般要求自己或客户架机位时遵守三个原则:覆盖完整、状态多样、时段覆盖。覆盖完整指摄像头要能看到垃圾桶顶口和周围约一米的落地区域,挂得过高会压缩垃圾溢出物的像素面积,挂得过低又会被路过的人遮挡;状态多样指同样一个桶,要拍它空桶、半桶、满溢、被塞进大纸箱、旁边堆了杂物等各种状态,模型才不至于只认识“干净的垃圾桶”;时段覆盖指至少要混合白天、傍晚、夜间三种光照。垃圾桶满溢检测最常见的翻车就是夜间,桶口反光、垃圾袋表面高光会让模型把普通塑料反光当成溢出物。

采集数量上,3 类别的项目我给出的参考区间是每类 800 到 1500 张,总计 3000 到 5000 张。这个量配合预训练权重足够收敛,也足够支撑 85% 左右的训练/验证划分。如果你是从零训练不加载预训练权重,数量最好再翻倍,否则收敛过程会很折磨人。采集时不要只拍静态照片,尽量从视频里抽帧,同一场景相邻帧之间的视角差异很小,抽帧间隔保持在 5 到 10 帧,能有效扩充数据量又不会让数据过于相似。

另外在数据合规上提醒一句:公共区域采集的视频要避开人脸和车牌特写,如果必须拍到,用打码工具统一处理。抽帧之后用感知哈希比较相邻帧的相似度,去掉重复度极高的帧。一套 3000 张的数据集里经常会掺着几百张几乎静止的重复帧,它们不会带来新信息,只会拉长训练时间,还会让模型对特定视角过拟合。

3. 把现场照片整理成 YOLO 训练集:目录结构、标注导出与清洗脚本

3.1 数据集目录结构:images/labels 与 train/val 划分

YOLOV5 训练自定义数据集时,默认会找两个关键目录:images 和 labels,这两个目录下再各自分 train 和 val。labels 目录里存放与图片同名的 .txt 标注文件,图片是 a.jpg,标注就是 a.txt。目录结构建议如下:

data/ ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ ├── val/ └── full_dataset.yaml

YOLOV5 在训练前只会检查目录存在性,不会自动帮你创建 images/labels 的 train/val 子目录,所以先把空目录建好,再用脚本把图片和对应 txt 成对拷贝进去。另一个容易忽视的点是:如果某张图片没有任何目标被标注,它对应的 txt 文件是空文件,YOLOV5 会把这图视为背景图。如果满溢检测数据集中混入了一批“空垃圾桶但没标注”的图片,模型会把空桶学成背景,导致现场空桶也被误检,这种隐性错误比越界标注更难排查。

3.2 用 LabelImg 或 CVAT 标注并导出 YOLO 格式

标注工具选择:小项目、单机标注用 LabelImg,界面简单,矩形框拖拽顺手;项目多人协作标注用 CVAT,有 Web 端和标签校验流程。两者都支持导出 YOLO 格式,导出的 .txt 每一行是:类别 ID、归一化的中心点 x、中心点 y、框宽 w、框高 h。这个格式是 YOLO 系列统一的,后续训练不用再做格式转换。

标注满溢状态有一个容易绕进去的坑:full 类别的框到底框住什么?我的规则是“框住桶口沿以上溢出的垃圾主体,并把桶口外沿包含进去四分之一”。只框溢出物会导致标注框过小、位置偏高,模型学到的特征过于碎片化;把整个桶身都框进去又会让 full 和 normal 的框形状几乎一致,分类器难以区分。半满桶的框则框住整个桶口平面。标注时把这两条写进规范,比事后返工省时间得多。

批量导出后,建议顺手做一次统计:每个类别的框数量、每张图的平均框数。满溢检测场景中一张图通常只有一个桶,少数情况下有两个桶并排。如果统计结果出现某张图有 8 个框,多半是标注了背景里的杂物而不是垃圾桶,这类脏数据要优先清掉。

3.3 数据清洗与划分脚本:跑通最小可训练数据集

我标注完从来不直接开训,先用一段短脚本扫一遍标签文件的常见问题。下面这个脚本检查三类问题:行格式、坐标越界、负值。

import os label_dir = "labels/train" for f in os.listdir(label_dir): if not f.endswith(".txt"): continue with open(os.path.join(label_dir, f), encoding="utf-8") as fp: lines = fp.readlines() for line in lines: line = line.strip() if not line: continue parts = line.split() # YOLO 行的合法结构: class_id, cx, cy, w, h if len(parts) != 5: print(f"{f}: 列数异常 -> {line}") continue cls, cx, cy, w, h = parts try: cx, cy, w, h = float(cx), float(cy), float(w), float(h) except ValueError: print(f"{f}: 含非数字坐标 -> {line}") continue if min(cx, cy, w, h) < 0: print(f"{f}: 坐标出现负值 -> {line}") # 归一化框的右边界 = cx + w/2, 不能越过 1.0 if cx + w / 2 > 1.0 or cy + h / 2 > 1.0 or cx - w / 2 < 0.0 or cy - h / 2 < 0.0: print(f"{f}: 框越界 -> {line}")

这段脚本的逻辑很简单:先检查每行是否为 5 个字段,再转 float,最后判断归一化坐标是否落在 0 到 1 区间。其中“框越界”是标注工具最常见的坑,尤其是手拖矩形框时拖出了图像边缘,导出的 cx、cy 可能轻微越界。YOLOV5 训练时会对越界框做裁剪,但如果越界幅度很大,模型学到的是半个完整目标的特征,这类样本多了会拖低 mAP。

检查完标签后做数据划分。划分脚本要解决的核心问题是:不能让同一个垃圾桶的多张照片同时出现在训练集和验证集,否则验证结果没有参考价值。按采集时记录的桶编号分组划分,而不是直接对图片文件名随机划分:

import os import random import shutil # 图片文件名格式约定: bucket_id_frame.jpg, 例如 03_0005.jpg src_img = "all_imgs" bucket_map = {} for f in os.listdir(src_img): if not f.endswith(".jpg"): continue bucket_id = f.split("_")[0] bucket_map.setdefault(bucket_id, []).append(f) bucket_ids = list(bucket_map.keys()) random.seed(42) random.shuffle(bucket_ids) split = int(len(bucket_ids) * 0.85) train_buckets = set(bucket_ids[:split]) val_buckets = set(bucket_ids[split:]) for f in os.listdir(src_img): if not f.endswith(".jpg"): continue bucket_id = f.split("_")[0] target = "data/images/train" if bucket_id in train_buckets else "data/images/val" shutil.copyfile(os.path.join(src_img, f), os.path.join(target, f)) txt_path = os.path.join("all_labels", f.replace(".jpg", ".txt")) if os.path.exists(txt_path): shutil.copyfile(txt_path, os.path.join(target.replace("images", "labels"), f.replace(".jpg", ".txt")))

这段脚本按文件名的第一个下划线前的编号作为桶 ID,同一个桶的照片全部进同一个集合。训练集和验证集的划分是整个工程里最容易被忽略的一步,直接 random shuffle 图片会让验证集里出现和训练集几乎同一视角的照片,mAP 虚高 3 到 5 个点,部署现场立刻打回原形。按桶分组划分后,验证集的难度更接近真实“没见过的桶”。

4. 用 YOLOV5 训练 3 类别垃圾桶数据集:data.yaml、超参数与命令判读

4.1 data.yaml 的写法:类别顺序与路径相对化

训练前必须写一个 data.yaml 描述数据集位置和类别信息。YOLOV5 的 data 参数接受 .yaml 文件路径,内容如下:

path: ./data train: images/train val: images/val nc: 3 names: 0: normal 1: half 2: full

这里最需要注意的是 names 的索引顺序必须和标注 txt 里的类别 ID 完全一致。如果你在标注工具里把 full 定义成 ID 0,但 yaml 里 names 的 0 是 normal,模型会把满溢学成正常,训练过程不会报任何错,验证集上 mAP 也正常,但类别语义全是乱的。所以写完 yaml 后我习惯跑一个小脚本,随机打印 10 张图及其标签,肉眼核对一下标签和图片内容是否对应。

path 字段我建议写成相对路径,配合训练命令里的相对位置。不要写死 /home/xxx 这种绝对路径。YOLOV5 在读取 train/val 字段时,如果 train 写的是绝对路径就优先用绝对路径,如果是相对路径就基于 path 拼接。把 path 设置为当前项目根目录,train 写成 images/train,这样整份 yaml 可以随着项目文件夹迁移,直接换机器训练,不用改配置。这一点会在后面避坑章节详细展开,先按这个习惯写就好。

4.2 三个必调超参数:epoch、batch-size、img-size

YOLOV5 的超参数分成两块:train.py 命令行里的训练超参数,以及 data/hyps/hyp.scratch-low.yaml 里的优化器超参数。训练一个 3 类别检测任务,命令行里最需要关心三个参数。

epoch:单卡 3060 级别,3000 张数据,我从 80 轮起步。加载预训练权重时,80 轮足够让模型收敛到实用的精度;如果你的数据集是用手机现拍的、标注也有噪声,可以适当加到 120 轮,因为数据噪声大时模型需要更多轮次来吸收有效特征,过早停止会欠拟合。

batch-size:由显存决定。6GB 显存跑 img-size 640 的 yolov5s,batch-size 16 左右;12GB 显存可以开到 32。batch-size 太小会让梯度抖动剧烈,loss 曲线看起来像锯条,这时候不要怀疑代码,先把 batch 提到 16 以上再观察。

img-size:默认 640。满溢检测里垃圾溢出物的目标通常不大,如果现场摄像头视角较远,我会先用 1280 跑一版对比。1280 对显存的消耗接近 640 的四倍,如果机器带不动,优先考虑调整拍摄机位让垃圾区域占更多像素,而不是硬上大分辨率。

python train.py \ --data full_dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 80 \ --cache ram \ --workers 4

参数--cache ram是把图片预加载进内存,能明显减少每个 epoch 的磁盘读取时间;--workers 4是数据加载线程数,Windows 上如果报 DataLoader worker 相关错误,把它降到 0 或 2 即可。--weights yolov5s.pt会加载 COCO 预训练权重,对垃圾桶这种通用物体迁移效果很好,不建议从--weights ""开始。另外 YOLOV5 自带的数据增强默认开启马赛克和 HSV 扰动,采集素材里如果有大量夜间或强反光图,建议把 hyp 文件里 hsv_h、hsv_s 的幅度调小,桶的颜色本来就固定,过度色偏增强会让模型学到不真实的视觉外观。

4.3 训练日志判读:怎么看模型真的在收敛

训练启动后,终端里每一轮都会输出一行指标:box_loss、obj_loss、cls_loss、P、R、[email protected]、[email protected]:0.95。满溢检测的 3 类别任务里,我优先看两类指标。

第一看 val cls_loss 是否持续下降。cls_loss 是分类损失,normal/half/full 三类的区分度都体现在这里。如果 cls_loss 在 30 轮之后开始波动不降,说明类别特征不够明显,比如 half 和 full 的标注边界模糊,这时去查标注而不是加轮次。

第二看 [email protected] 的最终值。对于这个任务,[email protected] 在验证集上达到 0.85 以上算合格。注意这里的验证集是按桶 ID 分组划分的,模型见到的都是“没见过的桶”。如果这个数值能到 0.85,部署到新点位大概率不会崩。若验证 mAP 很高但现场频繁误报,问题通常不在训练而在后处理阈值,这部分在第 6 章展开。

还有一个重要习惯:训练完不要只看 last.pt,一定要用 best.pt。YOLOV5 在每轮结束后会根据验证 mAP 保存 best.pt,它对应的状态不一定是最后一轮但一定是验证集上表现最好的权重。导出和部署都以 best.pt 为准,last.pt 只用来断点续训。

5. 垃圾桶满溢检测避坑:小目标、标注边界与误报的 5 个翻车现场

5.1 满溢目标太小:resize 后被压成 8 像素

现象:训练曲线正常,验证集 mAP 不低,但一到现场测试,距离稍远的满溢桶全部漏检,尤其是溢出物只有一小团垃圾袋时。原因:摄像头视角较宽,垃圾高出桶口的那部分在 640 分辨率下只占十几个甚至几个像素,YOLOV5 的深层特征图下采样 32 倍,小目标特征基本被抹平。解决:先把机位调低或者拉近,让桶在画面中的高度占到 30% 以上;其次是训练时把 img-size 从 640 调到 1280,同时把 batch-size 减半。如果两者都做不到,考虑把检测区域裁成几个 ROI 分别送检,而不是指望模型在整幅画面里找小目标。

5.2 half 和 full 边界模糊:标注规则不统一

现象:full 的 recall 很高但 precision 很低,现场显示满溢报警的大部分桶其实只有半满。原因:标注员把“快满了”和“已经溢出来”都标成了 full,类别边界由人说了算,模型只能学到一半语义。解决:重新审核标注,确立硬性规则——full 必须以垃圾高出桶口沿为底线,且标注框包含溢出主体;half 以桶盖无法闭合为判断依据。审核方法是在训练集里采样 50 张 full 图片逐一检查框的位置和类别,必要时删掉边界模糊的样本。宁可少数据也不能让类别语义浑浊。

5.3 data.yaml 里写死绝对路径:换机器就报错

现象:本地训练结束后,把项目拷贝到服务器或另一台电脑,train.py 报No such file or directory或者AssertionError: train: No labels in ...。原因:data.yaml 里 path 或 train/val 写的是/home/yourname/...这种本机绝对路径,换了根目录后路径直接失效。解决:全部改成相对路径组织。yaml 里path: ./data,train: images/train,训练命令在项目根目录下执行。这一步看似不起眼,实际是多人协作项目里最频繁翻车的点。

5.4 直接随机划分数据集:验证集见过了训练集的桶

现象:训练过程中 val 指标一直很高,loss 看起来也漂亮,但新点位部署后明显不如验证集表现。原因:数据按图片随机划分,同一个桶不同角度的照片一部分进了训练集一部分进了验证集,验证过程等于是开卷考试。解决:按第 3 章划分脚本那样,按桶 ID 分组划分,确保一个桶的所有图片只出现在一侧。验证集 mAP 会因此下降一点,但那是真实的水平;如果下降幅度太大,说明原始数据量不足或同一个桶的不同状态图片太少,需要补采集。

5.5 类别不平衡:normal 占七成,full 学不出来

现象:训练结束看混淆矩阵,normal 的 recall 很高,full 的 recall 只有 50% 左右,现场对真正满溢的桶频繁漏报。原因:正常状态的桶占采集量的大部分,满溢状态因为“少见”而被模型忽略。解决:采集阶段就按比例控制,full 类不要低于总量的 25%。已经采完的话,用简单过采样:把 full 类样本复制两到三份再训练。YOLOV5 还可以通过--cls 1.0调高分类损失权重,默认 0.5 在类别不平衡时会偏向大头类别。注意过采样复制的是同一批图,YOLOV5 自带的马赛克和随机增强会生成不同变体,不会完全重复。

6. 从训练结果到现场报警:后处理、ONNX 导出与边缘端部署

6.1 置信度阈值与连续帧触发:报警的最后两道闸门

训练 mAP 达到 0.85 只是第一步,现场报警的关键在后处理。默认推理阈值 0.25 对满溢检测太激进,塑料反光、雨水影子都容易让模型在 0.3 附近抖动。我的做法是双阈值:检测阈值 0.35,只保留超过它的 full 检测框;报警阈值 0.5 且连续 3 帧都触发才推送工单。连续帧能避免人影遮挡造成的瞬时误报。score 取目标置信度乘以类别置信度,低于阈值的弱检测直接丢弃。阈值不要拍脑袋定,拿一段 30 分钟现场视频跑一遍,统计误报间隔再决定。满溢报警宁晚 5 秒,也不要一两小时一次的假工单,保洁对狼来了式的报警会彻底免疫。

6.2 ONNX 导出与树莓派 5 推理验证

python export.py \ --weights runs/train/exp/best.pt \ --include onnx \ --opset 12

opset 12 兼容性好,树莓派 5 和多数嵌入式设备上的 onnxruntime 都能直接加载。导出后用 onnxruntime 跑一遍样例图,对比 PyTorch 推理结果,两者的 mAP 差异应控制在 0.2 个百分点内。树莓派 5 的 CPU 跑 yolov5s 的 640 输入 ONNX 模型,单帧推理大约 80 到 150 毫秒,按 5 秒一帧抽检完全够用。部署时用官方 arm64 预编译包,注意散热,避免长时间推理触发降频。

这个项目让我印象最深的一次翻车,是图省事直接随机划分数据,验证 mAP 0.9,现场新点位掉到 0.7。改成按桶 ID 分组后验证 mAP 0.86,但新点位表现一致。数据集的类别定义、标注规则、划分方式这三件事做扎实了,从训练到部署会很顺。希望帮到你。

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

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

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

立即咨询