简介面向目标检测学习者的YOLOv5实战资源提供垃圾桶满溢检测三类别数据集及完整训练方案适用于智慧环卫、城市精细化治理等场景的算法验证与项目实训。压缩包共2000个文件主要包含1921个txt标注文件、40个Python脚本、23个yaml配置及9个sh脚本另有少量说明文档和json配置整体约450MB代码与数据经测试可直接运行。已有366人学习项目迭代100轮最优精度mAP0.5为0.91、mAP0.5:0.95为0.73并保留验证集混淆矩阵、PR曲线、F1曲线及推理结果等训练产物。资源内含2680张训练图与669张验证图均配套YOLO格式标签并提供训练好的权重参数可直接用于检测推理、迁移学习也适合在此基础上继续改进YOLOv5结构。1. 垃圾桶满溢检测是什么一张监控截图背后的 3 分类问题凌晨四点环卫调度中心的大屏上弹出一条告警某街道中转站 3 号工位垃圾桶满溢系统自动派单给最近的清运车。这套流程听起来不复杂但背后那个能分清「这个桶到底满没满」的模型就是标题里的 YOLOv5 实战项目要解决的事。它用一份 3 类别的垃圾桶状态数据集训练一个基于 YOLOv5 的目标检测网络让摄像头能实时判断每一个垃圾桶的满溢程度。这个任务最反直觉的地方在于它不是一个简单的二分类问题不同角度、不同光照下「满」的定义完全不同所以真正决定模型上限的不是网络结构而是数据集怎么标、怎么划分、怎么喂给训练脚本。这篇文章就是把你从「下载了个数据集」带到「能独立训出可用模型、知道参数为什么这么设、部署时哪里会翻车」的状态。适合正在做环卫智能化、边缘设备部署或者第一次用 YOLOv5 训练自己的数据集、想避开入门暗坑的开发者。2. 把原始图片变成 YOLO 能吃的标注3 类别定义、目录结构与校验脚本拿到一批垃圾桶照片后第一件事不是急着训练而是把数据整理成 YOLOv5 要求的格式。很多人在这里栽跟头直接用现成数据集目录开训报错一个接一个其实 80% 的问题都出在标注文件格式、类别 ID 对不上、标签和图片文件名不匹配这三件事上。这一章我们把每一步拆开做完之后你的数据目录会是干干净净、可以反复使用的状态。2.1 满溢检测的 3 个类别怎么定才不会被模型「带偏」标题里写明是 3 类别最常见的落地分法是按垃圾桶状态来分normal正常、half半满、overflow满溢。为什么不用「有垃圾 / 没垃圾」这种二分类因为清运调度需要的不是「有没有」而是「该不该去」——只有 overflow 状态的桶才需要马上派车。但如果你把 half 和 overflow 之外的都归为 normal模型很快就会被带偏半满的桶在各种角度下和满溢长得太像边界一模糊误检率直接拉高。我一般会再给标注定三条硬性规则写下来贴给标注员看提示宁可漏标不要错标。对边界拿不准的图片直接删掉不放进数据集。桶内垃圾超过桶口平面视为 overflow。垃圾高度在桶体 1/3 到 2/3 之间视为 half。桶内基本为空或只有少量底部垃圾且桶盖能正常盖上视为 normal。另一个常见做法是按垃圾材质分 3 类——厨余、可回收、其他。这在分类任务里没问题但用在满溢检测上就会跑偏同样是厨余桶有的满溢有的半满模型学的是「桶的种类」而不是「桶的状态」部署时看到个干净的新桶反而懵了。所以凡标题强调「满溢检测」类别必须先选状态维度再考虑材质。标注工具我常用 labelImg 或者开源的 X-AnyLabeling导出成 YOLO 格式后每张图片对应一个同名 txt 文件格式是class_id x_center y_center width height注意这里 x_center、y_center、width、height 全部是归一化到 0~1 的小数。很多第一次上手的人会误填成像素坐标模型训练直接崩loss 直接 NaN。2.2 标注格式转换与标签校验脚本半小时排查 500 张图的毛病如果你拿到的标注是 COCO 格式一个 JSON 文件存全部标注需要先转成 YOLO 的每图一个 txt。这里给一个我常用的转换脚本骨架用 Python 跑能顺带把越界框和类别 ID 错误一次查出来import json import os import cv2 def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) os.makedirs(out_dir, exist_okTrue) img_info_map {img[id]: img for img in data[images]} cat_map {cat[id]: idx for idx, cat in enumerate(data[categories])} for ann in data[annotations]: img_info img_info_map[ann[image_id]] img_path os.path.join(img_dir, img_info[file_name]) img cv2.imread(img_path) if img is None: print(f图片无法读取: {img_path}) continue h, w img.shape[:2] x, y, bw, bh ann[bbox] # COCO bbox 是左上角 宽高 x_center (x bw / 2) / w y_center (y bh / 2) / h bw_norm bw / w bh_norm bh / h cat_id cat_map[ann[category_id]] txt_name os.path.splitext(img_info[file_name])[0] .txt with open(os.path.join(out_dir, txt_name), a) as tf: tf.write(f{cat_id} {x_center:.6f} {y_center:.6f} {bw_norm:.6f} {bh_norm:.6f}\n) if __name__ __main__: # 用法: python coco_to_yolo.py 标注json 图片目录 输出目录 import sys coco_to_yolo(sys.argv[1], sys.argv[2], sys.argv[3])这段代码做的事情是把 COCO 的 bbox 换算成 YOLO 的归一化中心点格式。关键点有两个一是cv2.imread读不到图时立即打印防止图片缺失导致训练时 txt 和 jpg 对不上二是类别 ID 要重新映射因为 COCO 的 category_id 往往不是从 0 开始连续排列而 YOLOv5 要求类别 ID 必须从 0 开始且不能跳号否则训练日志里类别数和实际标签对不上。如果你手里的数据本身就是 YOLO 格式那也要做一次校验。我见过最多的坑是某个 txt 里写着5 0.5 0.5 0.2 0.2但数据集只定义了 3 个类类别 ID 5 直接让训练脚本报错。校验脚本也很短# 查出哪些标注文件的类别 ID 超范围 for f in labels/*.txt; do awk { if ($1 0 || $1 2) print FILENAME: $0 } $f done这一行 awk 能把你数据集里所有非法类别标出来。跑完发现有几个文件有问题不要手改——直接回到标注工具里修正对应图片再重新导出。手工改 txt 的数字很容易把框的中心点坐标一起改坏这是血泪经验。2.3 目录划分train、val、test 怎么分才不「串味」YOLOv5 对数据目录的默认要求很简单但很多人会在划分逻辑上犯错。正确结构是这样的dataset/ ├── images/ │ ├── train/ # 训练图片jpg/png 均可 │ ├── val/ # 验证图片 │ └── test/ # 测试图片可选但建议留 ├── labels/ │ ├── train/ # 每张图片同名的 txt │ ├── val/ │ └── test/注意 images 和 labels 下的子目录名称必须完全一致YOLOv5 会在训练时按文件名自动去找对应标注。如果你把 val 的图片放在images/val但标注放在labels/val2就会看到 Loss 正常但 mAP 一直是 0 的诡异现象最后查半天发现是路径没对上。划分比例我一般用 8:1:1。关键是划分时要以「场景」为单位而不是以「单张图」为单位。什么意思同一个垃圾桶在不同时间拍的连续帧如果一部分进了 train、一部分进了 valval 的精度会虚高因为模型在 train 里已经见过同一个桶了。部署后换个视角的桶精度立刻掉一大截。所以先按摄像头点位或者桶的编号分组再整组划分。一个按文件名前缀分组的 Python 脚本片段import os import random import shutil from collections import defaultdict src_img images_all src_lbl labels_all groups defaultdict(list) for f in os.listdir(src_img): prefix f.split(_)[0] # 假设文件名是 cam01_0001.jpg 这样的格式 groups[prefix].append(f) all_files list(groups.keys()) random.seed(42) random.shuffle(all_files) n len(all_files) train_groups set(all_files[:int(n * 0.8)]) val_groups set(all_files[int(n * 0.8):int(n * 0.9)]) for f in os.listdir(src_img): prefix f.split(_)[0] ext os.path.splitext(f)[0] lbl ext .txt if prefix in train_groups: shutil.copy(os.path.join(src_img, f), dataset/images/train/) shutil.copy(os.path.join(src_lbl, lbl), dataset/labels/train/) elif prefix in val_groups: shutil.copy(os.path.join(src_img, f), dataset/images/val/) shutil.copy(os.path.join(src_lbl, lbl), dataset/labels/val/)这里random.seed(42)保证可复现——同一个数据集每次划分的结果一样你和同事讨论精度时基准才统一。如果哪天调参后效果变好你能确认是参数起的作用而不是数据划分变了。这是很多人忽略的玄学点。3. 用 YOLOv5 训练自己的满溢数据集yaml 配置、训练命令与三个关键超参数数据准备好了接下来就是训练。这一章直接给可抄的配置和命令。YOLOv5 的训练入口是仓库根目录下的train.py但所有行为都由一个数据集 yaml 文件和一个训练命令决定。理解这两个文件里的每一项比你盲目调十次参都有用。3.1 数据集 yaml 配置文件路径、类别名与一个容易忽略的坑在 YOLOv5 仓库的data目录下新建一个trash_overflow.yaml内容如下# 垃圾桶满溢检测数据集配置 path: /home/user/dataset # 数据集根目录的绝对路径 train: images/train # 相对 path 的训练图片目录 val: images/val # 相对 path 的验证图片目录 test: images/test # 相对 path 的测试图片目录 nc: 3 # 类别数量必须和标注里的 class_id 最大值 1 相等 names: [normal, half, overflow] # 类别名顺序就是 class_id 的顺序这里有个非常容易翻车的点path写相对路径还是绝对路径。YOLOv5 官方代码里支持相对路径但它会相对于你执行train.py时所在的目录解析而不是相对于 yaml 文件所在目录。很多人把 yaml 放到data/下然后path: dataset/images/train结果训练报错说找不到图片。我一般直接写绝对路径一劳永逸。第二个坑是names列表的顺序必须和标注 txt 里的 class_id 一一对应。比如你的 txt 里0代表的是 overflow但 yaml 里names[0]写的是 normal那训练不会报错但混淆矩阵和后续部署输出全乱套模型推理出来标着 normal 的框其实是满溢桶。3.2 最小训练命令与每个参数的含义配置好 yaml 后在 YOLOv5 仓库根目录执行python train.py \ --data trash_overflow.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 4 \ --device 0这是一个适合单张消费级显卡的最小命令比如 RTX 3060 或 4060 级别。逐项说明--weights yolov5s.pt用 COCO 预训练权重做迁移学习。s 是 small速度最快内存占用最小。如果你的场景里有大量小目标比如远处的垃圾桶可以换yolov5m.pt或yolov5l.pt但第一次跑建议先用 s 打通流程。--img 640输入图像缩放尺寸。640 是速度和精度的常用平衡点。如果你的监控画面分辨率是 1920x1080训练时 YOLOv5 会把图缩到 640这没问题但如果桶在画面里占的像素极少建议用 896 或 1280代价是训练时间和显存翻倍。--batch 16每次迭代输入 16 张图。batch 越大梯度越稳定但显存占用越高。显存不够时先减 batch不要先减 img因为图像尺寸对精度影响更大。--epochs 100训练轮数。满溢检测这类背景相对固定的任务一般 60~100 轮就能收敛。如果你发现 50 轮后 loss 还在明显下降就继续加。--workers 4数据加载线程数。Windows 上有时会报错调成 0 是最省事的办法Linux 上设成 CPU 核心数的一半左右。训练启动后不要干等着。盯住终端输出的几行指标初始 loss 应该在 0.1 以下因为有预训练权重如果第一轮就是 NaN说明标注里有空 txt 或者类别 ID 越界立刻停下回去查数据。3.3 三个直接影响收敛的超参数学习率、图像尺寸与锚框很多时候模型训出来 mAP 不低但实际效果差问题不在网络结构而在超参数。YOLOv5 默认的--lr0 0.01适合大多数情况但满溢检测任务有个特点类别高度不平衡。如果你的数据里 overflow 只占 5%half 占 15%normal 占 80%模型会倾向于把所有框都预测成 normalmAP 看着还行真正满溢的桶一个都检不出来。这种时候我一般做两件事。第一给训练命令加类别权重文件让模型对少数类别更敏感。方法是在 yaml 文件同目录下写一个类别权重列表传给--class-weights但 YOLOv5 对这块支持不如 YOLOv8 完善更实用的做法是直接在数据层面过采样。简单说就是把 overflow 图片复制几份放进 train 目录让三类数量别差得太悬殊。经验值是最少类样本数不少于最多类的 30%否则很难训好。第二个参数是--multi-scale。这个开关会在每次迭代时随机把输入图像缩放在img * 0.5到img * 1.5之间。满溢检测里桶的大小变化极大——近处的桶占了半屏远处的桶只有几十个像素加上这个参数可以有效提升小目标的召回率。代价是训练时间增加约 20%但值得。第三个是--save-period。默认每轮都保存权重但浪费磁盘。我习惯设置为 10每 10 轮存一个 pt 文件方便回滚。训练到一半断电或 loss 发散你可以从最近的一个 checkpoint 继续不需要从头再来这个习惯给我省过不少时间。4. 训练结果分析mAP、混淆矩阵与五个高频踩坑点训练结束不是看results.jpg里 mAP 0.9 就能欢呼的。满溢检测的失败模式很隐蔽mAP 高但现场误报多、白天准晚上崩、近处准远处漏。这一章先讲怎么看结果文件再讲五条我用真金白银换来的排错经验。4.1 从 runs 目录里读出模型真实水平confusion_matrix.png 是最诚实的文件训练完成后YOLOv5 会在runs/train/exp/目录下生成一堆文件。大部分人只看results.png里的曲线但最有价值的是confusion_matrix.png和F1_curve.png。混淆矩阵里对角线越亮越好但如果看到溢出类别的行被大量预测成 normal说明类别不平衡的老毛病还在。这种情况下你再调 mAP 都没有意义回到数据集做过采样或者重新清洗误标样本。F1 曲线能告诉你置信度阈值该设多少。满溢检测部署时你不能直接用默认的 0.25 置信度——环卫场景里漏检一次满溢桶的投诉成本远高于多跑一次空车。所以我一般会把阈值调到能接受的最大误检率下的最高置信度。看 F1_curve 上哪个置信度位置曲线的值最高就在那个附近选。另一个常见操作是拿一批没进过训练集的现场图片做批量推理输出每张图的检测结果和置信度。用自带的detect.pypython detect.py \ --weights runs/train/exp/weights/best.pt \ --source test_images/ \ --conf 0.4 \ --save-txt \ --project runs/detect/--save-txt会把每张图的检测结果写到 txt 里方便脚本统计误检率。我会写一个简单的 Python 脚本统计这批现场图里overflow 类别的检出率召回率和把 normal 错检成 overflow 的数量误检率。4.2 避开这些坑类别不平衡、过拟合、部署环境差异、空标签文件、置信度阈值第一条现象是训练 loss 下降但 val 的 mAP 波动大最后 best.pt 反而出现在前几轮。原因是验证集里有大量和训练集几乎一样的连续帧模型在训练集见过的场景太多val 失去代表性。解决方法是按 2.3 节按场景分组重新划分不要让同一个桶的连续帧同时出现在两个集合里。第二条类别不平衡导致 overflow 的召回率极低。现象是混淆矩阵里 overflow 行大部分落在 normal 列。解决方法是过采样并检查是不是标注员把大量 overflow 标成了 half——因为「满到什么程度算溢」的主观判断差异比想象中大得多。第三条Windows 下训练时报BrokenPipeError。原因是--workers大于 0 时 Windows 的数据加载器和主进程之间有兼容性问题。解决方法是--workers 0损失一点加载速度但能稳定跑完。第四条训练一切正常导出 ONNX 后在树莓派或者 RK3568 这样的边缘设备上部署精度掉了 5~8 个点。原因是训练时图片经过的归一化和推理时的预处理不一致。YOLOv5 官方仓库导出 ONNX 时会内置预处理信息但你自己写推理脚本时忘了做letterbox等比例缩放加灰边图直接 resize 导致几何变形。解决方法是导出时用python export.py --weights best.pt --include onnx --img 640然后推理时严格按导出的预处理来。第五条val 里一个垃圾桶都检不出来但训练 loss 正常。原因 90% 是 labels 目录下对应的 txt 文件是空的或者文件名没对上。空的 txt 文件不会让训练报错但会让这一张图的 loss 计算异常。排查方法很简单# 找出所有没有对应标签的图片 find images/val -name *.jpg | while read f; do name$(basename $f .jpg) if [ ! -f labels/val/$name.txt ]; then echo 缺少标签: $f fi done这三条排查思路能覆盖大多数「训练完了但效果不对」的怪问题。记住一个原则先怀疑数据再怀疑参数最后才怀疑网络结构。YOLOv5 本身非常稳定绝大多数翻车都发生在数据链路里。5. 让满溢检测更贴近现场三个改进方向与我的例行验证步骤模型已经能跑但离「让人放心部署」还差几步。这一章不展开讲新框架只讲我在垃圾桶满溢检测场景里反复验证过有效的三个方向以及每次改动后我固定要做的一套验证流程。第一个方向是针对性数据增强。满溢桶的核心视觉特征是「桶盖上鼓起来、边缘变形」但现场照片里这些特征很容易被阴影和反光干扰。我自己的做法是给训练命令加--hyp指向一个调过的超参文件把hsv_h、hsv_s等颜色增强参数稍微调低同时把translate和scale调高因为摄像头安装角度变化比颜色变化更常见。具体数值我一般设置为hsv_h: 0.01、hsv_s: 0.5、translate: 0.2、scale: 0.8。这组参数对室内外混合的垃圾桶场景比默认值更稳。第二个方向是时间维度的复用。单个摄像头单帧偶尔会误检但同一个桶连续 10 帧里有 8 帧都显示 overflow那基本就是真溢了。这个用部署侧的简单时序滤波就能解决不需要改模型。我在边缘盒子上用 OpenCV 读帧维护一个简单计数器检测到 overflow 连续超过 N 帧才触发告警N 一般取 5~8 帧。这样能把偶发的误检压到几乎为零成本几乎可以忽略。第三个方向是小目标优化。如果你发现远处小尺寸的桶经常漏检除了调大--img还可以尝试在训练时启用--rect参数。这个参数会让 YOLOv5 按图片宽高比分 batch相同长宽比的图放在一起缩放减少拉伸变形对竖屏监控画面尤其友好。代价是每个 batch 的 shape 不一致需要显存稍微大一点。每次调整完数据或参数我例行跑三条验证# 第一条在未见过的现场图片上批量推理统计每个类别的置信度分布 python detect.py --weights best.pt --source val_new/ --conf 0.25 --save-txt # 第二条导出 ONNX 并用 onnxruntime 推理同一张图对比和 PyTorch 原版的结果 python export.py --weights best.pt --include onnx --img 640 python onnx_infer.py --weights best.onnx --image test.jpg # 第三条用 video 模拟连续帧验证时序滤波逻辑 python detect.py --weights best.pt --source test_video.mp4 --conf 0.4第二条尤其重要。我遇到过 PyTorch 里检测正常、导出 ONNX 后同一个框置信度突变的案例最后查到是 ONNX 的 NMS 参数和原版不一致。所以我的习惯是任何模型改动之后不只在 PyTorch 里自嗨一定把导出 onnxruntime 推理 和原版对比这一步走完再交给部署。这套组合拳打完你的满溢检测模型才算真正从「训练集里 mAP 高」变成「现场能用」。我也曾在第三类上偷懒跳过 ONNX 对比结果在 RK3568 上白排了一天错最后还是回到这步流程。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?