城市场景下用 Faster R-CNN 识别打包垃圾从数据集到部署的一次完整实践最近终于把面向“打包垃圾”检测的这套 Faster R-CNN 项目跑通了。说“终于”是因为这类任务远比想象中麻烦——垃圾不是一个规整的物体它的外观、颜色、遮挡情况千变万化同一袋垃圾在早上和傍晚的光线下看起来完全是两个东西。这篇文章把我从数据收集、标注、训练到部署的全过程梳理一遍包括踩过的坑和最终采用的参数方案给正在做类似城市视觉项目的朋友一个参考。这套项目基于 Python 实现核心算法采用 Faster R-CNN目标是识别城市道路、小区、商业街区等场景中“打包放置”的垃圾垃圾袋、成捆废弃物、堆放的杂物包等输出检测框和置信度。它解决的痛点是城市管理巡查中人工巡检效率低、盲区多需要用摄像头自动发现乱堆乱放的垃圾包及时上报处置。适合做智慧城市、环卫监管、社区治理这类方向的同学参考也适合刚入门目标检测、想找一个非标准数据集练手的人阅读。1. 项目背景城市里“看不完”的垃圾1.1 这个项目到底要解决什么问题先说清楚“打包垃圾”和普通垃圾的区别。街面上的垃圾主要分两类一类是散落的零碎垃圾比如纸屑、烟头、落叶这类目标太小太碎检测难度极大实战价值也低另一类就是“打包垃圾”——居民用塑料袋装好的生活垃圾、商铺门口堆放的一捆纸箱、装修遗弃的编织袋、绿化带里成堆的落叶袋。这类垃圾通常体积不小、边界相对清晰但问题是形状极其不固定垃圾袋是软的放在地上会坍成不规则形状纸箱捆是硬的形态相对稳定编织袋可能是鼓着的也可能是瘪的。而且它们出现在城市各种角落墙角、垃圾桶旁边、绿化带里、人行道上背景杂乱光线变化大。这套项目选定的目标就是后者。识别出这些“成包的垃圾”后系统可以给城市管理部门生成工单提示某个点位出现了垃圾堆放需要调度环卫人员处理。相比逐帧靠人眼看监控这种自动检测能覆盖更多点位、全天候工作而且积累了数据之后还能做趋势分析——比如哪个区域在什么时间段垃圾高发倒推管理上的根源。1.2 为什么是目标检测而不是图像分类一开始有人建议用图像分类来做把摄像头拍到的画面直接分“有没有垃圾”两类。这个方案在实验室看着可行落地就崩。原因在于摄像头是固定视角的大场景画面一帧 1920x1080 的图里垃圾往往只占很小一块区域整图分类根本定位不了垃圾在哪后续也没法让环卫人员快速找到点位。目标检测输出的是一个矩形框加上类别和置信度恰好满足“定位”这个核心需求。有了检测框就可以算出垃圾在画面中的像素坐标再结合摄像头的安装角度做简单的映射大致判断垃圾在现实世界中的方位。这就是选检测框架而不是分类网络的根本原因——业务要的不只是“有”或“没有”而是“在哪”。2. 数据与标注决定模型上限的前置环节2.1 数据采集策略做检测项目数据是最容易被低估的环节。我一开始天真地以为网上能找到现成的“垃圾检测数据集”结果翻了半天要么是垃圾分类数据集区分可回收、厨余、有害那种要么是无人机视角的河道漂浮物跟“城市地面打包垃圾”完全不是一回事。最后只能自己采集和整理。采集渠道主要用了三类一是从某试点街道的监控摄像头导出了一批历史录像覆盖晴天、阴天、雨天和不同时段二是团队几个人拿着手机在城市里走街串巷专门拍街角的垃圾堆放点三是从公开的街景数据里筛了一部分存在垃圾堆放的截图。三种来源加起来一共攒了大约 4200 张有效图片其中约 3000 张用于训练600 张用于验证600 张用于测试。这里有个经验教训监控录像导出的帧画面质量差异极大。有的摄像头是老式模拟信号转数字分辨率只有 704x576垃圾在远处只有十几个像素有的新摄像头是 400 万像素枪机细节不错但视野太宽垃圾目标依然偏小。我最后在训练集里故意混入了不同分辨率的图片而不是统一 resize 到同样的清晰度让模型尽量适应真实场景的多样性。如果你做的项目也依赖现场摄像头一定不要只用高清手机拍的照片训练否则上线后会被老旧摄像头的画面直接击穿。2.2 打包垃圾的标注规范标注是这套项目里最需要较真的地方。垃圾不是人、不是车标注得很随意的话模型学出来也很飘。我参考了多位做过环卫检测项目的同行的经验定了几条硬性规则垃圾袋、堆放物出现完整的袋体轮廓时按外接矩形框住框要贴住目标边缘不留太多背景。多个垃圾袋紧挨在一起时如果彼此重叠严重框成一个大目标如果之间有明显间隔分开标。垃圾被车辆、行人、栏杆部分遮挡时仍按可见部分的最大范围标注不要凭想象补齐遮挡部分。垃圾桶本身不标注但垃圾桶旁边散落的垃圾袋要标注。不清晰的远距离目标小于 40 像素不标注这些目标在训练中只会引入噪声。标注工具用的是常见的开源标注软件导出为 VOC 格式的 XML再转换成 COCO 格式的 JSON方便后续接入训练框架。前前后后标了大概两周中间有一轮返工——第一批标注的框偏大把不少地面背景圈进去了导致模型训练时误把一些砖缝纹理当成垃圾特征。后面重新统一标准后效果明显改善。标注这件事没有捷径如果自己一个人做多检查多复盘比闷头标完再回头改要高效得多。2.3 数据增强与样本扩充4200 张图不算多对 Faster R-CNN 这种较重的模型来说不增强一下很容易过拟合。我主要用了以下几类增强手段随机水平翻转概率 0.5这是最基础也是最有效的增强不改变目标语义。随机亮度、对比度调整模拟一天中不同时段的光照。垃圾检测对光照很敏感这个增强实测提升明显。随机裁剪和缩放让模型看到不同尺度的垃圾。对少样本类别做过采样比如“成捆纸箱”这类样本在数据里偏少就在训练时把包含这类目标的图片多喂几遍。有一点要注意像马赛克增强、混合增强这类手段用在通用检测数据集上效果不错但在垃圾检测这种目标不规整、背景又高度相似的数据上未必是加成。我试过把两张图拼接做增强模型反而把“画面边缘出现半截物体”的频率学高了导致推理时对画面边缘的目标误检率上升。最终只保留了翻转、颜色扰动、随机裁剪这三类保守增强。3. Faster R-CNN 选型分析与原理拆解3.1 为什么不用 YOLO 系列当前目标检测领域YOLO 系列是热度最高的方案之一速度快、工程生态完善很多人一上来就默认选 YOLO。但我在这套项目里最终选了 Faster R-CNN不是因为它更先进而是因为这个任务的特点决定了它更合适。第一精度优先。城市管理场景对漏检的容忍度极低——漏掉一袋垃圾工单系统就不会生成问题就会一直积压。YOLO 走的是单阶段路线速度快但小目标检测精度通常不如两阶段方法。垃圾袋在监控画面里经常属于中小目标Faster R-CNN 的特征提取和区域建议机制对这种目标更友好。第二训练数据量有限。Faster R-CNN 的候选区域机制本质上是在做“先粗筛再细判”它对标注噪声的鲁棒性比端到端回归的 YOLO 好一些在小数据集上不容易跑偏。第三推理速度并非瓶颈。当前部署场景不需要实时处理每一帧摄像头每 10 秒截一帧做检测单帧推理 200 毫秒左右完全够用。如果项目要求在嵌入式设备上做到 30 FPS那肯定要选 YOLO 或更轻量的模型但我们的场景是云端或边缘服务器速度要求远低于精度要求。当然不是说 YOLO 不行。如果数据量能到上万张且对帧率有硬性要求YOLO 系列可能是更稳的选择。选型这件事没有绝对的好坏只有匹配不匹配。3.2 网络结构与关键组件Faster R-CNN 的结构分四块骨干网络Backbone、区域建议网络RPN、ROI 池化ROI Pooling/Align、分类与回归头。简单理解就是骨干网络负责从图中提取特征RPN 负责“猜测哪些位置可能有目标”ROI 池化把候选区域的特征统一成固定尺寸最后分类头判断“是什么”和“框准不准”。骨干网络选的是 ResNet-50。有人会问为什么不用 ResNet-101后者理论上特征表达能力更强但在这个数据集上实测 mAP 提升不到 1 个百分点训练时间却增加了将近一半。ResNet-50 在精度和效率之间是最平衡的选择。RPN 部分需要重点调的是 Anchor 的设置。早期我在默认配置下训练Anchor 是三种尺度128、256、512和三种宽高比1:1、1:2、2:1结果对“长条形的纸箱捆”检测很差。后来统计了标注框的尺寸分布发现大量目标的宽高比集中在 1:1 到 1:1.5 之间尺寸集中在 80 到 300 像素区间就把 Anchor 调整为 (64、128、256、512) 配合 (1:1、1:1.5、1:2) 的组合检测效果立刻好了不少。ROI 池化这里我直接用了 ROI Align。相比早期的 ROI PoolingAlign 用双线性插值解决了两次量化造成的像素偏移问题对小目标坐标回归精度有明显帮助。在垃圾检测这种边界模糊的任务上坐标精确多几个像素后续的定位映射就能准确不少。3.3 预训练与迁移学习这部分是少走弯路的关键。我从头训练过一版loss 降到一定程度就再也不动了mAP 卡在 0.5 附近效果很差。后来改用 ImageNet 上预训练好的 ResNet-50 权重作为初始化冻结骨干网络前两个阶段只训练后半部分和 RPN、检测头效果立竿见影。迁移学习的本质是ImageNet 上学到的边缘、纹理、形状等底层特征对任何视觉任务都是通用的垃圾虽然在类别上不属于 ImageNet 的一类但“袋子的褶皱”“纸箱的棱边”这些视觉模式底层特征都是相通的。这里有个细节加载预训练权重后需要确认参数名称是否对齐。不同框架导出预训练权重的命名规则不完全一致加载时经常出现“某层参数随机初始化”的警告。我当时就因为这个原因骨干网络后半段实际上是随机初始化训练的白白浪费了两天时间。检查方法是在加载后打印每一层的参数来源或者对比加载前后同名层的权重数值是否变化。4. 训练实操与参数调优4.1 环境配置与依赖流程环境方面我比较推荐直接用现成的开源检测框架比如 MMDetection这比手写 Faster R-CNN 全部代码靠谱得多。MMDetection 把骨干网络、RPN、检测头做成了标准组件改配置即可跑通绝大多数训练流程而且自带 COCO、VOC 数据集的加载器省去大量数据预处理代码。硬件方面单卡 24GB 显存RTX 3090 级别即可比较舒服地训练批大小可以开到 8输入尺度设成 1333x800短边 800、长边不超过 1333。如果显存只有 8GB批大小降到 4 或 2其余配置保持不变也能训练就是时间长一些。Python 环境建议用 3.8 以上PyTorch 1.13 或 2.x 均可CUDA 11.7 以上。这里提醒一下MMDetection 不同版本对 PyTorch 版本有硬性要求安装前先查好对应关系不要直接 pip install 最新版然后期待它能跑。4.2 训练参数设置我最终采用的参数如下可以直接参考# 训练周期与学习率 runner dict(typeEpochBasedRunner, max_epochs24) optimizer dict(typeSGD, lr0.005, momentum0.9, weight_decay0.0001) lr_config dict( policystep, warmuplinear, warmup_iters500, warmup_ratio0.1, step[16, 22] ) # 批大小 data dict( samples_per_gpu8, workers_per_gpu4 )学习率用的是 0.005 而非默认的 0.02原因是垃圾检测数据集偏小且目标任务与通用目标不一致过大的学习率极易震荡。SGD 搭配动量 0.9、权重衰减 0.0001 是检测任务的经典组合Adam 在检测上并不总能胜过 SGD我实测用 SGD 收敛更稳。step 策略在 16 轮和 22 轮分别将学习率降到原来的 1/10这比余弦退火在这个数据集上效果好一点后者在小数据集上容易提前收敛到平台期。warmup 也不能省。前 500 次迭代用 1/10 的学习率预热让 RPN 网络在早期不因为初始化的随意性而剧烈震荡。很多入门的朋友忽略这个参数训练一开始 loss 就飞了还以为是代码写错了。4.3 训练过程监控与收敛判断训练过程中我主要盯着三个曲线总的 loss、RPN 的 loss、以及验证集上的 mAP。loss 曲线只用来判断训练是否稳定真正的收敛标志是验证集 mAP 在连续若干 epoch 内不再上升甚至开始下降——后者是过拟合信号应该提前停止。这里分享一个判断 mAP 是否可信的技巧我不仅看平均 mAP还单独统计了卡车遮挡、夜间、雨天这三类困难场景的子集 mAP。全局 mAP 可能看起来不错比如 0.72但夜间子集可能只有 0.4。如果只看平均值上线后被夜间场景拖垮都不知道原因。按场景拆开统计能精准定位模型的短板再针对性地补充数据和调整策略。最终测试集上的平均 mAP 约为 0.73其中晴天场景约 0.82雨天约 0.63夜间约 0.55——这个结果不算惊艳但对于这类形态高度不稳定的目标已经是能落地的水平了。5. 推理部署与性能优化5.1 推理流程与置信度阈值选择训练完的模型在部署时需要把推理代码从训练框架里抽出来。MMDetection 提供了现成的推理接口但在正式环境我不会直接用原始接口而是先做一轮整理输入图片先做预处理resize 到 1333x800归一化前向推理得到检测框、类别和分数再做 NMS 去重最后过滤掉置信度低于阈值的检测框。置信度阈值是部署前必须反复调的参数。阈值调低比如 0.3召回率提高但误检增多阈值调高比如 0.7误检减少但漏检增加。我的做法是结合业务成本来定一个误检工单让环卫工人白跑一趟成本是浪费人力一个漏检让垃圾继续堆放成本是城市管理考核扣分。两者权衡后我选择了 0.45 作为默认阈值又给夜间模式单独设了 0.35 的较低阈值因为夜间光线差模型整体置信度会偏低。NMS 的 IoU 阈值用的是 0.5这个值比较常规。有一个容易忽略的问题NMS 只对同一类别内的框做抑制如果你后续想同时检测“打包垃圾”和“散落垃圾”两个细分类别且它们位置高度重叠时跨类别的框不会被抑制可能出现同一个垃圾袋被输出两个框的情况。目前的方案是只设了一个“打包垃圾”类别绕过了这个问题但后续如果扩类别需要留意。5.2 推理加速方案虽然不需要实时处理但推理速度依然影响成本——摄像头数量多了以后单帧推理时间直接决定需要多少台服务器。我尝试了几种加速手段整理成下面的对比方案推理耗时ms/帧, 1333x800精度损失实施难度原始 PyTorch 推理450无最低FP16 半精度推理220约 0.5% mAP 下降低ONNX ONNXRuntime180无中TensorRT FP16110约 1% mAP 下降较高最终生产环境用的是 ONNX ONNXRuntime 方案。TensorRT 更快但部署环境对 TensorRT 版本的依赖比较苛刻而且导出过程中遇到过算子兼容问题调试成本高。ONNXRuntime 的精度几乎没有损失提速效果也够用。导出时需要注意输入张量的动态维度设置因为真实图片尺寸不固定ONNX 导出时要把输入维度设为动态否则遇到不同尺寸的图片会直接报错。快 2 倍多里有一个关键是把 GPU 上的预处理也做了。很多人只在 GPU 上跑网络图片读取、resize、归一化都放在 CPU 上CPU 和 GPU 的轮转会吃掉大量时间。我的做法是提前写好一个数据流水线用 GPU 上的张量操作把整个预处理链都放到推理设备上实测省掉了每帧约 80 到 100 毫秒的等待。5.3 部署到实际场景的注意点部署阶段有一个坑容易被忽视摄像头画面和训练集画面的分布差异。训练集里大多是白天光线正常的图片但实际监控画面里有早晚太阳直射造成的过曝、路灯下的偏色、甚至摄像头镜头上有水渍导致的模糊。上线第一周误检率比测试时高出不少后来分析发现很多误检是把地面上的深色水渍当成了垃圾袋。解决办法有两个一是从现场反复抓拍持续补充新场景数据做循环训练二是在推理层加一道简单的后处理规则——如果检测框里面的区域纹理特征过于平滑比如水面、阴影则降低该框的置信度。另外部署的输入帧率也不要太高。摄像头 25 帧每秒如果每一帧都做检测服务器压力大且没必要。我按场景配置了两种采样策略普通路段每 10 秒抽一帧重点点位垃圾高发区每 3 秒抽一帧。这样既保证了发现时效又控制了计算成本。后续还可以做基于运动检测的触发式采集——画面区域发生变化时才做识别但这是后话了。6. 踩坑记录与问题排查技巧6.1 训练过程与结果中的典型问题速查表做这个项目过程中反复遇到的问题不少挑几个高频且有共性的整理成表格方便大家直接对照排查现象根本原因解决方案训练 loss 不下降学习率过大或 backbone 未加载预训练权重降低学习率至 0.005检查权重加载日志验证 mAP 很高但实际场景漏检严重训练集过于“干净”缺少真实监控图的噪声混入低分辨率、雨天、逆光等真实图像对画面边缘的垃圾几乎不检测Anchor 尺寸覆盖不够或训练时数据增强裁剪过度统计标注框尺寸分布调整 Anchor减少大尺度随机裁剪同一袋垃圾输出多个重叠框NMS 阈值偏低或不同类别的框互相不抑制调整 NMS 的 IoU 阈值检查输出类别设置夜间误检率显著升高夜间样本太少或光照增强不足单独补充夜间数据对夜间样本做亮度、对比度增强推理速度远低于预期CPU 预处理与 GPU 推理没有流水线化预处理移到 GPU 上执行或增大 batch 推理6.2 印象最深的两个 Bug第一个 Bug 是加载预训练权重时的“静默失败”。MMDetection 加载权重后默认只打印总加载数量不报错。我一个阶段训练完发现骨干网络 B 部分的权重全是初始值细查才发现配置里的 load_from 路径因为版本不匹配被判定为“只加载兼容层”导致后半段骨干网络没有加载成功。排查方式很简单——在加载权重的代码里加几行把模型加载前后某个中间层的权重数值打印出来做一次数值对比确认真的加载了再继续跑。第二个 Bug 是验证集的“假阳性”误导。训练过程中验证集 mAP 一度到了 0.8让我以为模型已经很强了。结果可视化推理时发现验证集里有一批图片本身比较暗垃圾袋和地面的对比度低模型输出的是置信度极低的框但因为验证集里这类图片占比不多平均 mAP 还是被拉高了。后来按场景分层评估才暴露了夜间样本薄弱的真相。这件事提醒我指标一定要拆开看平均数是会骗人的。6.3 后续可以扩展的方向最后聊聊这套项目的后续扩展。当前只做单类别检测实际业务中完全可以细分可回收垃圾包、厨余垃圾包、装修废弃物包——对应的处置流程不同分类输出能让工单系统直接分派到不同的处理渠道。不过细分类需要更精细的标注和更多的数据尤其是厨余垃圾袋和可回收垃圾袋在外观上看不出明显差别时需要结合其他信息比如堆放位置、周围环境上下文辅助判断。另外一个方向是结合目标跟踪算法对同一袋垃圾做持续跟踪。摄像头检测到垃圾后不马上生成工单而是连续观察一段时间确认这袋垃圾是否被清理。如果跟踪到环卫人员把垃圾清走就不产生重复工单如果垃圾在原地停留超过预设时间比如 2 小时再触发告警。这样能大幅降低误报率也符合城市管理的实际节奏。从个人体会来说这类“非标准物体检测”项目难点从来不在模型结构有多新而在于对数据和场景的理解。Faster R-CNN 这套方案在网络结构上不算前沿甚至是五六年前的架构了但它在精度和稳定性上的表现配合上对城市垃圾场景的针对性调整依然能做出可落地的效果。做类似的视觉项目不妨先把数据和评估体系打磨扎实再回头挑剔模型架构顺序反了往往事倍功半。
阅读完成 · 觉得有帮助?