做智慧环保项目踩过的第一个坑,往往不是模型精度不够,而是“什么垃圾”“放在哪个位置”“用哪种方式识别”这些业务问题没有被定义清楚。我去年全程参与了一套YOLO垃圾分类系统的搭建,从模型选型、数据标注、训练调优,到最终在边缘盒子、树莓派和手机摄像头上跑通推理部署,前后折腾了小半年。这篇文章把整个过程中的模型演进对比和落地部署经验整理出来,给正在做垃圾分类识别、智慧环保监控,或者想把自己的检测模型真正部署到生产环境的同学一份可以直接参考的操作指南。
这套系统做的事情不复杂:用摄像头对准垃圾投放点或转运站,实时识别画面里出现的垃圾类别,一旦发现厨余垃圾被扔进可回收桶,或者有害垃圾混入其他垃圾,就触发报警并自动记录。听起来简单,真正跑起来之后才发现,从YOLO的版本选择到边缘设备适配,每一步都藏着很多细节。
1. 项目定位与整体设计思路
1.1 智慧环保场景下的真实需求
垃圾分类监控和普通的目标检测项目有个明显的区别:它不只追求“检出率”,更追求“行为判断”。现场最常见的场景是一个居民把一袋混装垃圾丢进桶里,系统需要判断袋子里面大致包含哪类垃圾,以及这次投放是否违反了分类规则。这意味着模型不光是识别“画面里有瓶子”,还要在投放瞬间从横向角度、光照阴影、人手遮挡中抓出核心特征,任务难度比静态图像识别高不少。
我最初接过需求时,第一反应是直接用分类网络MobileNet去解决。但很快就发现不行:摄像头画面里通常同时出现多个目标,一个垃圾桶前可能同时站着两个人,有人拿着纸箱,有人拎着厨余垃圾袋,分类网络只能给整张图打一个标签,根本没法做多目标定位。作为替代,目标检测模型是更有可行性的方向:框出每个目标的位置和类别,后台再根据坐标和时序信息判断投放行为是否符合规则。
具体场景里,还要考虑识别目标的粒度。常见做法是把垃圾分成可回收物、厨余垃圾、有害垃圾、其他垃圾四类,但模型不能只输出这四类,否则“可回收物”这个大类里的矿泉水瓶、易拉罐、纸箱、旧衣服全都混在一起,后续的回收统计和报警策略就没法细化。所以我在设计类别时采用“大类+细类”的松耦合方案:模型先识别细类(比如pet瓶、易拉罐、纸箱、菜叶、剩饭、电池、过期药品),后台再通过映射关系映射到四个大类。这样的好处是模型学的是具体物体,边界清晰,四类映射表可以随时调整,不需要重训模型。
1.2 为什么是YOLO:检测模型选型逻辑
检测模型的可选项不少,两阶段的Faster R-CNN,单阶段的YOLO、SSD、RetinaNet,还有近几年被工程圈频繁讨论的DETR、YOLO+Transformer组合。落到智慧环保边缘部署场景,我最终选了YOLO,理由其实很务实。
首先是速度。Faster R-CNN在同等精度下推理速度慢一个量级,垃圾分类监控常需要同时处理四到八路视频流,边缘盒子算力本来就有限,每帧几十毫秒的推理时间根本扛不住。YOLO作为一阶段检测器,天然在速度和精度之间做到了较好的均衡。
其次是生态。目前围绕YOLO的周边工具链非常完整,从预训练模型下载、数据集格式转换、训练参数配置,到ONNX导出、TensorRT/RKNN量化部署,社区都有人实践过。这意味着当我在RK3588上部署时踩到算子不兼容的坑,大概率能在社区找到对应的解决方案,这种“被市场需求反复验证过”的可靠性,对落地项目非常重要。
然后是模型迭代速度。近几年YOLO几乎每年都有新版本发布,每次更新都伴随检测头、损失函数或训练技巧的改进,比如Anchor-Free机制、DFL损失、C2f模块等。虽然新版本不一定完全适合每个硬件平台,但至少研究社区一直在解决检测任务的通用痛点,我们可以站在这个基础上做场景适配。
1.3 端到端系统架构
整套系统的运行链路是这样的:前端摄像头(或NVR)通过RTSP拉流 → 视频解码得到RGB帧 → 送入YOLO推理引擎 → 后端对检测框做业务逻辑判断(如类别映射、区域过滤、帧间平滑跟踪)→ 触发告警或写数据库 → 管理后台展示统计结果。
部署架构我做了两级拆分:现场边缘端优先算,服务器或云端做汇总。边缘端通常是一台RK3588盒子或树莓派,负责实时检测和初步过滤;只有判定为异常投放的目标,才把截图和检测结果上报到后端。这样设计的理由很直接:垃圾投放点的摄像头数量多、视频流长期在线,如果所有画面都传回中心服务器,带宽和存储成本会很快失控。边缘端把“大多数正常画面”直接丢弃,只保留“可疑行为”和“统计数据”,能大幅降低全链路资源消耗。
另外,我在架构里专门加了一层“业务规则引擎”模块。检测模型只输出类别和坐标,但最终要不要报警、误报率是否可接受,还需要业务规则来兜底。比如同一个易拉罐在画面里闪现了3帧就被检出,那大概率是误检;如果连续10帧都稳定出现在同一区域,才值得关注。这部分我在后面部署调优章节会展开说明。
2. YOLO模型演进对比:选型不是越新越好
2.1 从YOLOv3到YOLO11:检测头与损失函数的迭代
做方案选型时,团队内部讨论最多的就是“到底该用YOLOv5、YOLOv8还是最新的YOLO11”。为了搞清楚这几代模型到底差在哪,我把各版本的核心变化梳理了一遍。
YOLOv3是最早采用FPN多层特征融合的代表,通过三个不同尺度的特征图分别检测大、中、小目标,同时把分类损失从softmax改成多标签交叉熵,这对垃圾场景很有意义——一个塑料袋里可能同时装了菜叶和剩饭,模型可以给同一个框输出多个标签概率。YOLOv5则在工程上有很大贡献,包括自适应锚框计算、Mosaic数据增强、自动混合精度训练,虽然严格来说它并不是官方YOLO系列,但由于工程生态完整、资料充足,至今仍有很多生产项目在用。
YOLOv8是近两年部署项目最常用的选择之一,它的一个重要变化是彻底转向Anchor-Free检测头,不再需要预先指定锚框,简化了训练配置;同时主干网络引入C2f模块,增强了梯度传导;损失函数则在目标损失和回归损失基础上引入了DFL(Distribution Focal Loss),将边界框坐标建模为离散分布,对模糊边界的预测更友好。最新的一些版本在骨干和检测头上做了进一步轻量化,推理速度更快,但对特定硬件和特定任务来说,精度未必比v8有压倒性优势。
我们也讨论过YOLO和Transformer结合,甚至复现过一些Mamba-YOLO之类的实验性模型。这类模型在复杂场景下确实有提升精度的潜力,但部署难度明显增加:注意力模块在边缘NPU上的算子支持不友好,量化后精度掉得厉害,训练时间也拉长不少。我的结论是:学术研究和竞赛可以追新,生产落地要慎重。
2.2 垃圾分类场景下的实测对比
为了选型,我在同一个小规模厨余垃圾数据集上做了对比实验,数据集大概1800张图,覆盖菜叶、剩饭、蛋壳、骨头等5个细分类别,用同一套训练参数、相同的输入分辨率(640x640),训练100个epoch。结果如下表:
| 模型 | mAP@0.5 | 参数体量 | 推理耗时(RK3588 INT8) | 部署难度 |
|---|---|---|---|---|
| YOLOv5s | 0.921 | 7.2 MB | 约28 ms | 低 |
| YOLOv8s | 0.934 | 11.2 MB | 约26 ms | 低 |
| YOLO11s | 0.938 | 9.4 MB | 约25 ms | 中 |
| YOLOv5m | 0.931 | 21 MB | 约45 ms | 低 |
| YOLOv8m | 0.946 | 25.9 MB | 约52 ms | 中 |
从结果看,模型体量上去之后精度确实会涨,但推理耗时也几乎线性增加。在垃圾投放点这种动态场景里,我们更看重“误检率”和“漏检率”的平衡,而不是单纯追求更高的mAP。YOLOv8s在精度和速度之间表现最均衡,所以我最终选择了它作为主力模型,YOLO11s则作为后续升级的备选。
有一点必须强调:这个结果只反映我当前数据集上的相对趋势,不代表绝对水平。模型自己数据集上的表现受数据分布影响很大,如果类别数量、样本数量或拍摄角度完全不同,结论可能会反过来。你的场景如果光照太极端、遮挡严重,应该在同一条件下多跑几组对比再说。
2.3 选型建议:按部署端反推模型
我的实际建议是,不要先定模型再找部署方案,而是先想清楚目标硬件。如果预估只能在树莓派4B上跑,那YOLOv5s或YOLOv8n可能才是安全选项;如果已经锁定了RK3588这类带NPU的边缘盒子,YOLOv8s的INT8量化也完全能接受;如果是在服务器上用GPU跑离线批量分析,才有足够余量去上YOLOv8m甚至结合注意力机制的高精度变体。
另一个关键指标是算子兼容性。我曾经在一款边缘设备上发现,模型用到的某个自定义模块(比如某些Bottleneck变体)在转成NPU格式时不被支持,只能放弃该模块重新训练。所以在选型阶段,可以先导出一次ONNX,再把ONNX放入目标推理引擎做一次“算子核查”,这一步最好在标注数据之前就完成,否则花了大量精力准备数据,最后模型根本部署不下去,整个项目就卡死了。
3. 数据集与标注:决定模型性能上限的基础
3.1 公开数据集与自建样本:别只靠复制粘贴
很多新手做垃圾分类项目喜欢直接网上找图片来跑,但部署到现场往往会发现效果很差。原因很简单:网图大多是“摆放整齐、光线充足、背景干净”的,而真实投放点是“随意堆叠、背光阴影、镜头被弄脏”的。所以我给自己的硬性要求是:公开数据集最多占20%,剩余80%都要尽量围绕实际环境采集。
数据采集我用了两种方式穿插进行。一种是固定摄像头定时抓拍,每次间隔5秒到10秒,连续录制多个时段,覆盖早中晚不同光照条件。另一种是手持手机模拟不同投放姿势拍摄,补充大量俯拍、斜拍、近距离和远距离的样本。采集完成后按统一规则重命名:类别编号-拍摄日期-序号,比如“03-20250312-0001.jpg”表示厨余垃圾类别在第3号类别下的第1张图片。
标注格式我统一转成YOLO格式的txt文件,每行是“class x_center y_center width height”,坐标是归一化后的0到1小数。格式转换这件事看似简单,但经常有人踩坑:XML转TXT时小数点精度保留不够,坐标会发生少量偏移;图像尺寸不匹配导致标注框全部错位。我一般会写一个校验脚本,随机抽取标注框和原图叠加显示,人工确认坐标没有漂移后再去训练,这个步骤能避免很多低级问题。
3.2 中餐厨余垃圾的标注细节
垃圾分类场景里最典型、也最考验人的就是中餐厨余垃圾。菜叶是软趴趴的,剩饭是一坨一坨的,骨头大小不一,塑料袋半透明还会混色,同类物体之间的形态差异非常大。这些目标在标注时容易出现“边界不清晰、框不应该一个”的情况。
我的经验是,标注厨余目标时按“可感知的物理边界”来画框,不要强行把多个物体并成一个框,也不要把一个巨大的团子拆成十几个碎块。比如一盘完整的剩菜倒在垃圾桶旁边,单个框把整体包住是合理的;如果菜叶散落成好几块,那就分别标几个框。框与框之间不建议大面积重叠,否则会干扰NMS后处理,让训练样本的回归目标不一致。
还有很多标注工具默认输出时会丢失类别字段,或者在保存时把类别索引自动改了。我建议在训练前做一次类别分布统计,看每个类别的样本数和标注框数是否悬殊。如果某类只有另一个类别的十分之一,就需要补充数据或采用重采样策略,否则模型会对少数类学习不足,出现过低的召回率。
3.3 数据增强与难例挖掘
数据增强我主要是基于Mosaic和MixUp再叠加光度畸变。Mosaic把四张图拼成一张,能显著缓解小目标样本不足的问题;光度畸变包括随机调整亮度、对比度、饱和度,这等于模拟一天不同时段的自然光变化。还有一个容易被忽视的点是“翻转增强”,垃圾投放现场是双向通道,目标可能从左边进来也可能从右边进来,水平翻转一定要开。
难例挖掘是我觉得比增强更能提升实效的技巧。方法很简单:用小规模训练完的模型去跑未标注的视频帧,把那些模型“既不像正类也不像背景”的置信度中低区间样本挑出来,人工判断后补充到训练集。比如模型对一张被塑料袋遮挡的矿泉水瓶图片输出了0.55的置信度,这种图就值得重点补标。这样做两轮之后,我在现场场景的误检率至少下降了三分之一。
另外要特别提醒:增强策略不能盲目堆叠。厨余垃圾类别之间的外观差异本身就小,如果增强过猛,比如大量随机擦除或重度模糊,模型会误以为类别特征本来就残缺,训练起来反而更难收敛。我通常把增强强度控制在让新样本和原样本“看起来还是同一个类别”的范围内。
4. 训练环境、损失函数与参数调优
4.1 环境配置与预训练模型下载
训练环境我用的是Ubuntu + Conda + PyTorch的组合,把Conda虚拟环境独立出来,避免和系统Python环境冲突。如果只是跑YOLO系列,一般不需要从源码编译,直接安装ultralytics这种统一封装包即可。要注意的是显卡驱动和CUDA版本必须和PyTorch匹配,版本不一致会出现cuda runtime error,这个坑几乎每个新手都会踩一次。
预训练模型我建议优先从YOLO官方仓库下载COCO预训练权重。下载时要注意权重文件和代码版本的匹配,比如拿YOLOv5v7.0的权重硬套到最新代码上,会直接报错键值不匹配。我的做法是先确定要用哪个版本,锁定对应release的权重文件,然后再开始写训练脚本,后续不再随意升级代码库。
对于时间紧、算力一般的同学,我建议先把数据集按“train / val / test”以7:2:1的比例划分好,划分时保证同一类别的图片不会全部挤在训练集或验证集里。可以用一个简单的随机脚本加seed参数固定划分结果,方便在不同实验之间复现对比。
4.2 损失函数与超参设置逻辑
YOLO系列从v5到v8,损失函数的核心往往包含三部分:分类损失、边界框回归损失和置信度损失。v8的回归损失用DFL实现了对边界框坐标分布的学习,能让模型对“边界不太清晰”的目标预测更稳定,这也正好适合垃圾袋边缘模糊的情况。理解这一点之后,你会明白为什么不能随便把回归损失换成传统的L1 Loss,它很可能导致框回归精度下降。
超参设置上,我习惯先用默认参数做一轮实验,拿到baseline后再做针对性调整。比较重要的几个参数包括:
- imgsz:输入分辨率。640是最常见的平衡点,如果摄像头画面里目标偏小,可以考虑调到768或960,但推理速度会下降,需要测试后确认。
- batch size:尽量在不爆显存的前提下给大一些,batch太小会导致BN统计不稳定。
- lr:初始学习率0.01左右通常比较稳,遇到训练震荡就先降一半再试。
- epochs:垃圾分类数据量不大,一般100到200个epoch足够,过多反而过拟合。
还有一个容易忽视的参数是anchors。如果用的是Anchor-Free模型,比如YOLOv8,那不需要手动设置锚框;但如果你在YOLOv5那种Anchor-Based框架下工作,建议启用autoanchor功能,让模型根据自己数据集的框尺寸自动计算锚框,比手填合理得多。
4.3 训练过程监控与瓶颈排查
训练时我几乎不看每张iter的loss数值,而是看两类曲线:一类是mAP曲线,另一类是分类和回归损失曲线。如果mAP一直在涨而回归损失抖动剧烈,说明学习率偏高或者边界框标注本身存在噪声;如果损失降到某个平台就不再下降,可以考虑调整特征图融合层或增加训练时长。
我在训练中还会同步做一次“可视化验证”:训练结束后抽出十几张验证集图片,跑一次推理,把预测框画在原图上,和真实标注框对比。这一步比看任何指标都直观。模型输出高置信度但框明显偏移,或者两个重叠目标只输出一个框,这些都是可视化后立刻能发现的问题。
训练阶段最容易翻车的是“数据泄露”,也就是验证集的图片不小心混入了训练集。这种情况会让指标虚高,部署到现场后大打折扣。我建议训练前用文件名哈希或图片哈希做一次查重,确保训练集和验证集完全不相交。
5. 推理引擎与边缘侧部署
5.1 目标硬件平台评估
垃圾分类监控的部署硬件,我实际测试过三类:
| 平台 | 算力特点 | 可支撑模型 | 帧率参考 | 适合场景 |
|---|---|---|---|---|
| RK3588 | 集成NPU,算子覆盖较好 | YOLOv8s INT8 | 20-30 FPS | 垃圾投放点边缘盒 |
| 树莓派5 | CPU推理,无专用NPU | YOLOv8n / v5s | 5-10 FPS | 教学验证、低成本原型 |
| 手机摄像头 | GPU/NPU,框架限制多 | YOLOv8n量化 | 15-25 FPS | 单点临时巡检 |
从成本角度,RK3588盒子是当前性价比很高的选择,支持多路视频流解码,NPU算力对YOLOv8s这类模型也足够覆盖。树莓派则更适合“先跑通流程”的阶段,性能有限但灵活,作为算法验证平台很合适。手机摄像头的部署则面对更复杂的碎片化问题,需要考虑NCNN或TNN这类移动端推理框架,模型选择也要更轻量。
5.2 模型转换与量化部署
从PyTorch权重到边缘芯片可执行文件,中间的流程一般是:导出ONNX → 算子核查 → 精度验证 → 转成目标平台格式(TensorRT的engine、RK3588的rknn)→ 量化校准 → 最终部署。
以RK3588为例,我会先用ultralytics把pt导出成ONNX:
yolo export model=best.pt format=onnx opset=12 dynamic=False然后是关键的量化环节。RKNN Toolkit进行INT8量化时,需要准备一个校准数据集,一般选200到500张有代表性的图片即可,这些图片不需要标注,但要尽量覆盖现场真实画面分布。量化后精度掉点如果超过2个百分点,就要检查两件事:一是校准集是否太单一,二是网络里是否有对量化敏感的层,比如某些激活函数或大数值的split操作。
部署时我一般先跑FP16版本验证整体流程正常,再切INT8去压性能。FP16和INT8在某个特定芯片上的latency差距很大,不能只看理论算力冗余。实操前期就把插件和依赖库版本锁定,避免部署现场反复debug环境问题。
5.3 边缘部署监控误检率高的调优手段
很多同学反馈“边缘部署后误检率比训练时高很多”,我分析下来最常见的原因有四个:输入图像缩放方式不对、置信度阈值太低、没有做时序平滑、缺少区域过滤。
输入缩放是容易被忽略的问题。摄像头原图通常不是正方形,YOLO推理需要resize到640x640。直接用opencv的resize会把物体拉变形,最好使用letterbox方式,等比缩放并用灰色填充边缘,同时记录缩放比例和填充偏移,方便把检测框映射回原图坐标。
置信度阈值我一般设置在0.4到0.5之间,不要为了追求检出率一路降到0.2。垃圾投放场景里误报警会让人逐渐失去对系统的信任,适度调高阈值,宁可漏掉少量模糊目标,也不要被大量假阳性淹没。
时序平滑用的是“跟踪加延迟确认”策略:每一帧可能都输出同一个目标的检测框,但单帧目标可能闪烁。我在实际部署中增加了一个简单跟踪模块,每个目标框在连续多帧中如果都能匹配上(可以用IoU或坐标距离做匹配),才最终确认为有效目标;如果只出现了一两帧就消失,直接丢弃。这个办法能显著降低垃圾袋抖动和飞虫之类的干扰。
区域过滤也很好用。在监控画面里用多边形划定投放区域,检测框中心点落在区域外就忽略,这样模型偶尔误检到的建筑角落、树枝摆动,都不会触发报警。
触摸设备与手机端部署,我自己更倾向于先用NCNN在手机CPU上做推理。模型导出后要做NCNN的FP16推理测试,如果效果不理想再考虑INT8量化。现在主流手机都有不错的NPU支持,但各家不同芯片的加速框架差异很大,统一用NCNN至少能保证兼容性和普适性。
6. 常见问题与排查技巧实录
6.1 训练中BN崩溃
BN崩溃是我在训练过程中遇到过的“最玄学”之一,表现是loss突然变成NaN,或者BN层的running_mean和running_var出现极端值。抛掉底层原理,最可能的触发条件是batch size太小、学习率过大、输入数据中存在异常值,或者模型在FP16混合精度下出现梯度爆炸。
我推荐的做法是:先把batch size调到32以上看问题是否消失;如果显存不够,就降低batch但同时调低学习率,并同步调整BN的momentum参数;另外,输图片前做一次归一化和异常值检查,避免个别全黑或过度曝光的图片主导BN统计数据。如果问题仍然存在,可以把混合精度改为FP32训练,虽然速度慢一点,但稳定性好很多。
6.2 混淆矩阵总和为什么不是100%
训练结束后看混淆矩阵,很多人会惊讶于“横向或纵向加起来不是100%”。正常情况下,每一行的召回率加和并不是必然等于100%,因为同一个目标可能被模型预测到多个类别,也可能某些目标完全没被检出。更常见的误解是把混淆矩阵的列当成了“分类结果的百分占比”,其实列代表真实类别,行代表预测类别。
我在做混淆矩阵分析时,常用的排查方法是看“对角线上的值是否明显高于非对角线”,以及“哪一类的样本大量被预测为背景(或某个大类)”。如果某类被大量预测为另一个视觉相似类别,优先去检查标注质量,而不是马上改模型结构。如果出现“预测类别集中在某一列”,说明训练集可能存在类别不平衡,需要用重采样或加权重损失来扭转。
6.3 边缘部署误检率高但训练指标很好
这类问题往往在训练阶段不明显,直到部署才暴露。除了我在上一章提到的输入处理和时序平滑外,还有两个排查方向值得注意:第一,检查推理用的预处理是否和训练时完全一致,比如训练用letterbox,推理用torchvision的transform,像素归一化范围可能就不一致;第二,检查摄像头安装角度,如果摄像头俯视和训练数据横拍差异太大,会直接导致现场数据分布偏移,这种情况优先搜集现场样本来微调模型,单纯调阈值作用有限。
6.4 环境配置冲突速查表
| 典型报错 | 常见原因 | 解决办法 |
|---|---|---|
| CUDA out of memory | batch过大或显存被其他进程占用 | 降低batch,释放显存,/减少workers |
| RuntimeError: device-side assert triggered | 标签索引超出类别总数 | 检查类别索引是否从0开始、txt文件解析是否正确 |
| No module named 'torch' | 虚拟环境未激活或依赖未装 | 检查conda环境与终端路径的匹配 |
| ONNX export fails | 自定义算子不被支持 | 去掉自定义层,或改用torch脚本化导出 |
这些环境问题单独看都不难解决,但它们会消耗大量项目时间,尤其是快要交付的时候。所以我更推荐在项目早期就把环境依赖、模型版本、数据集划分全部固定下来,用requirements.txt或Docker镜像保存初始状态,后面怎么迭代调参都不会“环境崩得莫名其妙”。
写在最后:从技术验证到长期可用的一个提醒
刚把垃圾分类系统部署到现场的那几天,我每天盯着实时检测画面,总希望模型在所有场景下都完美。但跑了一段时间后,我反而更关注“系统是否稳定、误报是否让用户厌烦、数据统计是否准确”这些工程指标。模型精度只是整个系统的一面,真正的价值来自于模型和业务规则的配合,以及人对系统的信任积累。
如果你准备在类似项目上复刻这套流程,我的建议是先选好部署硬件,再决定YOLO版本和数据集的规模;数据采集时多模拟真实投放动作,不要把精力浪费在网络图片上;训练阶段盯住混淆矩阵和现场可视化结果,而不是只盯mAP;部署阶段优先做letterbox、时序平滑和区域过滤这三个小改动,往往比换个更大模型收益更明显。
这套方法不止能用在垃圾分类上。只要把类别定义和场景数据替换掉,同样的流程还可以迁移到试卷题目自动切割、火灾实时监控、工业缺陷检测等许多视觉任务中。后续我还在测试将CLIP这类图文模型作为辅助校验层,用来过滤YOLO在中文长尾类别上的误检。等数据跑得再充分一些,再拿出来分享。