
简介面向目标检测入门与智能环卫场景的 YOLOV5 实战项目围绕垃圾桶满溢检测提供完整可运行方案覆盖满溢垃圾桶、未满溢垃圾桶和垃圾三类目标代码经测试可直接使用。数据集划分清晰训练集含2680张图片与对应txt标注验证集含669张图片与对应txt标注并附带已训练权重下载后即可完成推理演示。资源共2000个文件除大量txt标签文件外还包含Python训练/推理脚本、yaml模型配置、shell辅助脚本和Markdown说明文档整体压缩包约450MB目录结构清晰便于替换数据、迁移训练或二次开发。模型迭代100个epoch最优精度为mAP0.50.91、mAP0.5:0.950.73runs目录下保存混淆矩阵、PR曲线、F1曲线等评估图表runs/detect中留有批量推理结果检测效果直观。目前已有366人学习下载适合需要快速搭建YOLOV5检测环境、理解完整训练评估流程或开展智能垃圾桶项目的开发者。1. 垃圾桶满溢检测为什么值得用 YOLOV5 落地三个类别背后的真实场景城市环卫考评、物业保洁派单、园区垃圾清运调度都在等一个能自动回答“这只桶要不要马上清”的摄像头。垃圾桶满溢检测这个任务难点不在模型结构而在数据集满溢状态没有工业标准的明确定义同样是桶口堆出垃圾有的现场叫“满了”有的现场叫“还能塞”。所以做 YOLOV5 实战项目时先别急着调网络把“3 类别”这件事说清楚模型才有收敛的基准。这里的三类别通常指正常桶、满溢桶、桶周围散落垃圾覆盖了清运决策里最重要的两个状态和一个混淆来源。适合谁准备用 YOLOV5 训练自己数据集、又要面对真实摄像头视角和动态光照的开发者这篇按数据定义、标注、训练到部署的顺序讲落地路径。2. 三类别数据集怎么定义类别边界、采集角度与标注规范2.1 三个类别的划分正常垃圾桶、满溢垃圾桶、周边零散垃圾第一件事是把类别边界锁死。常见做法是定义normal_trash_can、overflow_trash_can、scattered_garbage三个类但不同标注员对“满溢”的理解可以在同一张图上差出半个桶。我的划分标准是垃圾最顶端超过桶口平面或者袋装垃圾明显高出桶口且视觉上无法再关盖归为overflow_trash_can只要有任何一个独立垃圾实体与桶没有直接接触、落在地面或桶边就单独标scattered_garbage。注意scattered_garbage不是“桶里的垃圾露出来”而是周围散落的垃圾这个边界在采样时最容易乱。为什么要单独拆出周边散落垃圾因为清运考核通常有两个指标桶满没满以及桶周围有没有落地垃圾。很多小区垃圾桶满溢的同时地面也脏两个目标发生在同一画面里如果不分出来模型会把地面垃圾当成桶体的一部分检测框要么巨大要么漏检。三分类把这些现象拆开后续做派单逻辑也方便满溢桶触发清运、散落垃圾触发清扫动作不同。还有一类“半满桶”是否要加第四类我建议不加。半满属于正常桶的视觉变化范畴YOLOV5 学习的是桶口形态和垃圾面积的组合特征训练数据里多放半满样本它自然会把这类归入正常。类别越多标注一致性和样本配比越难控制3 类别是性价比最高的方案。2.2 数据采集中影响模型的三个因素机位、光照、遮挡采集阶段直接决定模型上线后的生死这部分没有代码可写但比代码重要。机位方面垃圾桶检测摄像头的架设高度一般在 2.5 米到 4 米之间俯视角度 30 到 45 度最接近真实监控画面。采集时不要让镜头正对桶口桶口的圆形透视会严重变形导致正常桶看起来像满溢。批量采集时建议固定机位拍一段连续视频然后按帧抽图这样能得到同一桶在不同角度的自然变化。自收集不同场景。光照是垃圾桶场景最大的变数。白天阳光直射在蓝色或绿色桶身上会形成高光溢出垃圾的塑料袋反光更强夜间红外补光下黑色垃圾桶会吃光轮廓边缘几乎消失在背景里。采集时要刻意覆盖晴天上午、午后逆光、阴天、夜间开补光灯、夜间无补光几种条件。建议每个采集点位至少保留三个时段的图像夜间样本占比不低于总量 20%否则模型在夜间会频繁漏检事后补采很难受。遮挡方面要拍桶前停靠的车辆、行人经过的瞬间、绿化带枝叶挡掉桶体下半部分的画面。模型如果只在干净场景训练一到现场就会被遮挡干扰。我的经验是宁可画面里出现一半桶身的样本多一点也比全是完整桶身的样本好因为实际监控里完整桶身反而是少数。2.3 标注工具的选型与标注一致性检查标注工具我用 LabelImg 和 X-AnyLabeling 都跑过。LabelImg 是老牌工具单机小批量的操作成本最低X-AnyLabeling 集成了 SAM 分割辅助适合快速框出桶和散落垃圾的轮廓但对标注员的上手要求高一点。无论用哪个输出格式都要统一到 PASCAL VOC 的 XML 或直接选 YOLO 的 txt 格式这一步先想好后面转换脚本才不用返工。标注一致性检查是数据集质量最关键的环节。三个类别里normal_trash_can和overflow_trash_can的边界争议最大我的检查方法是每标注完一批图就抽取 20% 做二次标注比对同一张图的类别判定是否一致。kappa 系数过低就把分歧样本重新归类而不是留着让模型去“平均”两种标注方式。标注框的精细度也要定规矩桶体从桶口上沿标到桶底可见边缘桶盖打开时把盖子后面露出的垃圾开口包含进去不要把地面阴影标进框。散落垃圾的框贴着垃圾外轮廓如果一个区域有多个紧挨着的垃圾袋合并成一个框即可不要逐个拆开增加噪音。一批图标注完后用脚本统计每个类别的框数量、框面积分布、长宽比分布发现问题及时处理。3. 把图片整理成 YOLOV5 能训练的数据集目录结构、标签转换与超参数3.1 数据集目录与 label 文件格式YOLOV5 训练自己的数据集时目录结构要按它默认的读取逻辑来组织。常见的做法是在项目根目录下建一个datasets/trash_overflow目录里面分images和labels两大目录各自再分train和val。注意 YOLOV5 不强制要求 val 存在但不给你划分语料的话训练过程无法计算验证指标调参会像盲调。每一张图片对应的 label 文件是 txt文件名与图片名保持一致。每行代表一个目标格式为class_id x_center y_center width height五个值都是归一化到 0-1 之间的浮点数基于图片的宽高做除法。如果标注工具直接导出的是 VOC 的 XML 坐标就需要用脚本做换算这一步省不了。labels 目录里不允许出现空文件对应没有目标的图片。垃圾桶检测场景里画面中完全没有桶的空背景图不要在 val 里大量放置因为 YOLOV5 训练时会忽略没有标注的图片验证时也不会产生任何检测结果等于白占比例。我在第一次做数据集时犯过这个错后面才意识到空图导致的类别不平衡问题。3.2 一段可复用的脚本将 VOC XML 转成 YOLO txt 并自动拆分训练集下面这段代码是我在垃圾桶满溢检测数据集处理中反复用到的脚本负责把 XML 标注转成 YOLO 格式并按比例拆分训练集和验证集。import os import random import xml.etree.ElementTree as ET # 类别映射顺序与模型的 class names 必须一致 CLASS_MAP { normal_trash_can: 0, overflow_trash_can: 1, scattered_garbage: 2 } def xml_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(./size/width).text) img_h int(root.find(./size/height).text) with open(out_txt_path, w) as f: for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: print(f跳过未定义类别: {cls_name}) continue # 左上角坐标与宽高 xmin int(float(obj.find(bndbox/xmin).text)) ymin int(float(obj.find(bndbox/ymin).text)) xmax int(float(obj.find(bndbox/xmax).text)) ymax int(float(obj.find(bndbox/ymax).text)) # 转归一化中心点 cx, cy, w, h box_w xmax - xmin box_h ymax - ymin cx (xmin box_w / 2) / img_w cy (ymin box_h / 2) / img_h w box_w / img_w h box_h / img_h f.write(f{CLASS_MAP[cls_name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n) def split_dataset(images_dir, labels_dir, val_ratio0.2): images [f for f in os.listdir(images_dir) if f.endswith(.jpg)] random.seed(42) random.shuffle(images) val_count int(len(images) * val_ratio) val_images set(images[:val_count]) train_images set(images[val_count:]) for subset in [train, val]: os.makedirs(fimages/{subset}, exist_okTrue) os.makedirs(flabels/{subset}, exist_okTrue) for img in images: label_file os.path.splitext(img)[0] .txt src_label os.path.join(labels_dir, label_file) if img in val_images: os.replace(os.path.join(images_dir, img), fimages/val/{img}) os.replace(src_label, flabels/val/{label_file}) else: os.replace(os.path.join(images_dir, img), fimages/train/{img}) os.replace(src_label, flabels/train/{label_file}) # 按实际路径调整 xml_dir annotations img_dir raw_images label_dir raw_labels os.makedirs(label_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) txt_name os.path.splitext(xml_name)[0] .txt xml_to_yolo(xml_path, os.path.join(label_dir, txt_name)) split_dataset(img_dir, label_dir, val_ratio0.2)这段脚本做两件事第一解析每个 XML 里的目标框把左上角坐标转换成分数形式的中心点和宽高第二把所有图片随机打乱后按 8:2 拆到 train 和 val 目录同时把对应的 txt 标签移到相同子目录。参数说明CLASS_MAP的键必须和 XML 里的标注名称完全一致一个字符不对就会被跳过val_ratio取 0.2垃圾桶场景的样本量通常不大验证集太少会导致指标波动大random.seed(42)固定随机种子保证每次运行划分一致。3.3 训练入口命令与四个关键超参数数据集准备好后进入 YOLOV5 的训练阶段。训练前需要新建一个数据集配置 YAML 文件写明类别数和类别名称这一步容易漏。常见的做法是写一个trash_overflow.yaml内容指定训练、验证目录以及上面已经定义好的类别列表。训练命令的典型入口长这样。python train.py \ --data trash_overflow.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --cache参数说明--weights yolov5s.pt是指定用 COCO 预训练权重垃圾桶桶身和散落垃圾与 COCO 里的部分类别有语义重合迁移学习能明显加快收敛--img 640是输入分辨率如果摄像头画面里的桶比较小可以升到 768 或 896但显存消耗和推理时长都会增加--hyp指向数据增强超参数文件不要用默认hyp.scratch-high.yaml直接开高增强垃圾桶场景的强光照变化容易让增强过头后面细说。四个最影响结果的超参数hsv_h、hsv_s、hsv_v控制色调饱和度和明度的增强幅度现场画面亮度变化大时hsv_v保持默认值即可过度调整会让夜间样本失真flip_ud是上下翻转增强但很多摄像头是固定高度俯拍上下翻转会让桶的阴影方向颠倒建议设成 0scale控制目标缩放范围垃圾桶检测里框的大小分布差异大scale0.5覆盖率更好mosaic默认开启在满溢检测场景下可以保留但在小批量数据里马赛克增强会引入大量切割不完整的桶体建议训练到后半程关闭。可以用--epochs配合--patience早停当验证 mAP 不再提升时自动停止。3.4 训练中怎么判断模型真的在学训练过程输出的box_loss、obj_loss、cls_loss和mAP四个指标需要组合着看不能只看 mAP。box_loss和obj_loss在训练集和验证集同时下降才算正常如果box_loss下降但obj_loss原地不动通常是目标框的尺寸分布不正常回去检查标签的宽高值。我最常观察的节点是第 20 个 epoch 附近。如果此时mAP0.5还没有达到 0.5 以上大概率不是训练轮次不够而是标签有问题类别错标、框超出图像边界、归一无归一化错误。直接在val_batch0_pred.jpg里看每张预测图框是否贴合桶体边界比盯曲线更容易发现问题。4. 垃圾桶满溢检测训练避坑五条最影响 mAP 的踩坑记录4.1 满溢边界标注不统一导致类别错分现象overflow_trash_can的精确率很高但召回率低很多明显满溢的桶被模型判成normal_trash_can。从混淆矩阵看两个类别的错分几乎循环出现。原因标注时对“高出桶口多少算满溢”没有统一标准。A 标注员认为袋装垃圾露出桶口 10 厘米就算满溢B 标注员认为必须垃圾向外塌落才算。模型拿到的训练信号自相矛盾最终学到的边界偏向某一种。解决重新制定标注规范并二次复核。规定“垃圾顶部明显高于桶口上沿且视觉上盖不上桶盖”为满溢“垃圾虽多但仍低于桶口边缘”为正常。对有争议的样本单独存到一个待确认文件里用多数投票决定归类。复查后的类别间错分率明显下降。4.2 类别间数量差距大导致散落垃圾不收敛现象训练 50 轮后scattered_garbage的 mAP 仍低于 60%而另外两个类别已经到 85% 以上。原因垃圾桶满溢的数据采集大多集中在桶满状态正常桶样本充足散落垃圾往往只在部分场景出现。训练集里三类样本比例逼近 5:4:1小类别在损失函数中贡献不足梯度被大类占满。解决先按类别统计样本数量把散落垃圾的图片扩充到不低于正常桶的 70%。扩充手段不是简单复制而是把同一张图做左右翻转、小幅旋转、亮度扰动后作为新样本同时在损失函数中提高小类的边界框权重。YOLOV5 没有直接配置类别权重的入口但可以在数据增强的copy_paste中增加小类样本的复制概率。4.3 直接使用 COCO 预训练权重在夜间场景翻车现象白天测试 mAP 达到 0.82换成夜间红外录像测试时 mAP 掉到 0.3模型几乎把垃圾桶全部漏掉。原因COCO 预训练权重的底层特征是在自然光照图片上学习的红外图像的单通道纹理与可见光差异巨大。hsv_h等颜色增强对红外图像无效甚至会产生干扰。解决数据集里单独增加夜间样本比例并且训练时把hsv_v增强范围调大一点模拟夜间增益变化。更有效的做法是用夜间的预检结果做一次半自动标注扩充夜间样本到总量的 25% 以上否则任何数据增强都救不回来。4.4 桶盖反光导致频繁误检现象模型把部分地面上的反光区域识别成散落垃圾尤其是不锈钢桶和高光塑料桶。原因散落垃圾的纹理特征与桶身高光反射区域在灰度梯度上高度相似模型学到的是“高亮区域”而不是“垃圾轮廓”。解决标注时把桶身高光区域对应的图片单独抽出来给scattered_garbage类增加负样本——也就是包含反光但不含垃圾的图片。YOLOV5 训练时允许labels目录下的空文件存在这类负样本图片无需标注模型见到足够的反光背景后误检会明显收敛。4.5 混淆矩阵显示正常桶与满桶难分的调试方法现象normal_trash_can与overflow_trash_can的混淆率在 15% 左右两个类别的置信度分数都很接近 0.5。原因半满桶处于两个类别的视觉中间地带。垃圾超过桶口一部分但未大面积塌落时人眼都难以快速判断模型学到的特征在边界样本上自然模糊。解决把置信度阈值从默认的 0.25 上调到 0.4减少边界样本的误判输出。更重要是在标注阶段将“垃圾刚超过桶口”的样本独立筛选出来统一归到满溢类并增加这类过渡样本在训练集中的占比。模型见过足够多的过渡态后决策边界会更平滑而不是在两个类别间来回跳。5. 从训练完到能用的最后一步验证指标、测试视频与嵌入式部署5.1 用混淆矩阵和 PR 曲线判断能不能上线results.png里除了 mAP 还包含三类各自的 PR 曲线不要只看汇总指标。实际上线前我一般先看scattered_garbage的 PR 曲线如果曲线在置信度 0.6 到 0.8 区间出现明显拐点说明高置信度的检测结果可信可以放心接后台如果曲线一直平缓上升说明模型输出大量低置信度误检需要针对该类别再补样本。跑完验证集后抽样打印confusion_matrix.png重点看对角线以外的值是否集中在相邻类别之间。如果错分分散在多个类别中说明分类特征没有学好但分布集中在一个方向往往是标注问题。上线前固定一组真实监控视频用训练时保留的随机种子重复推理两次确认输出框没有抖动。5.2 导出 ONNX 做量化时的两个限制边缘设备部署时YOLOV5 官方export.py可以直接导出 ONNX。注意两个坑第一--dynamic参数会让 ONNX 输出形状可变但推理框架的预处理环节要额外处理动态 shape垃圾桶检测场景的输入尺寸固定为 640x640 即可不要开动态第二量化到 int8 时如果校准数据集里只有白天图片夜间红外图片在量化后会出现严重的精度下降必须把校准集做成白天夜间混合。另一个常见问题是 INT8 量化后overflow_trash_can和scattered_garbage这两个小类别掉点严重原因是小目标的特征图数值范围大量化均匀分段后信息丢失多。复现第一步可以先用--half半精度推理替代 int8很多场景下精度损失只有 1% 到 2%但推理速度的提升已经足够。5.3 一个检查部署效果的技巧用连续监控视频跑帧计数我习惯在部署后不做静态图片测试而是用环境里录好的一段 10 分钟连续监控视频做测试。方式也不复杂让模型逐帧推理输出框的同时按时间戳记录每个类的检测次数。对比人工清运记录看模型的检测数量和真实满溢事件的吻合程度。要注意的是如果视频帧率 25 帧、设备推理速度只能跑 10 帧就不能直接对比逐帧结果而要对检测结果做去重按相邻帧的 IoU 合并。一个容易忽略的细节是检测框的平滑。满溢桶被风吹动或行人短暂遮挡时单帧检测结果会出现快速的“满溢—正常—满溢”跳变后台如果直接用这个结果派单会被阈值抖动烦死。我通常给满溢状态加一个连续 N 帧保持的计数器连续超过 5 帧检测为满溢才触发告警中间出现短暂漏检计数器清零但不立刻取消告警避免视频文件帧率抖动造成重复通知。实测这类垃圾桶满溢检测项目的真实上线时间远不止训练模型这一个环节。数据定义和标注一致性占掉六成时间训练和微调反而很快。花一晚上把标注规范写好、把采集时段覆盖全后续训练会顺利很多这是我做过几轮项目后最想回头改的第一步希望帮到你。本文还有配套的精品资源点击获取