垃圾分类这件事,说起来简单,做起来是真磨人。我在社区做过一段时间的智能回收箱项目,最开始的想法很朴素——不就是把瓶子、纸板、厨余分开嘛,装个摄像头跑个分类模型不就完了?结果真上手才发现,光照变化、遮挡堆叠、类别极度不均衡、小目标密集,这些坑一个接一个。后来把方案从传统图像分类换成基于YOLOv8的目标检测,再针对生活垃圾场景做了一轮改进,才算把准确率和推理速度都拉到能用的水平。这篇就围绕"基于深度学习的垃圾分类识别目标检测系统"这个题目,把我在YOLOv8改进、数据集构建、训练调参、部署落地这条链路上的完整思路和踩坑经验摊开讲一遍。不管你是刚接触深度学习图像识别的新手,还是已经跑过YOLOv5想升级到v8的老手,应该都能从里面找到能直接抄作业的部分。
1. 为什么垃圾分类非得用目标检测而不是图像分类
1.1 图像分类在真实垃圾场景下的三个硬伤
很多人第一反应是:垃圾分类不就是分类任务吗,ResNet、MobileNet随便上一个不就行了。我一开始也是这么想的,直到把分类模型放到真实场景里跑,才发现问题大了。
第一个硬伤是多目标共存。真实的一袋垃圾里往往同时有塑料瓶、纸盒、果皮,图像分类模型只能给整张图打一个标签,它没法告诉你"这张图里有一个塑料瓶和两个纸盒"。而目标检测能同时输出每个物体的类别和位置框,这才是分拣机械臂真正需要的信息。
第二个硬伤是背景干扰。分类模型对整张图做全局池化,背景里的桌面、地面、人手都会被算进特征里。我实测过一个在干净数据集上准确率98%的分类模型,换到实际投放口场景,准确率直接掉到70%出头,因为背景分布完全变了。
第三个硬伤是无法定位。就算分类对了,你也只知道"这袋垃圾里有塑料",但塑料在哪、有多大、要不要抓,一概不知。分拣环节需要的是坐标,不是标签。
1.2 目标检测给出的答案:类别加坐标
目标检测的本质是同时解决"是什么"和"在哪里"两个问题。以YOLO系列为代表的单阶段检测器,把整张图划分成网格,每个网格直接回归边界框和类别概率,一次前向传播就能出结果。这个特性对垃圾分类特别友好——垃圾投放是实时的,用户扔进去你得马上判断,延迟超过几百毫秒体验就崩了。
YOLOv8相比前代,在骨干网络、特征融合、检测头和解码策略上都做了调整,尤其是它把Anchor-Based换成了Anchor-Free的解耦头,对小目标和密集目标的检测更稳。生活垃圾里瓶盖、烟头、电池这类小目标特别多,这一点提升很关键。
提示:如果你的场景里垃圾是单件、摆拍、背景干净,图像分类确实够用且更轻量。但只要涉及真实投放、多物体堆叠,直接上目标检测,别在分类上浪费时间。
1.3 垃圾分类检测和通用检测的任务差异
通用目标检测(比如COCO)追求的是"广",80个类别什么都能认一点。垃圾分类检测追求的是"专"和"细",类别通常按地方标准划分,比如可回收物、有害垃圾、厨余垃圾、其他垃圾四大类,往下还能细分到塑料瓶、易拉罐、纸箱、电池、灯管等几十个小类。
这个差异直接决定了三件事:一是数据集必须自己建,公开数据集很难完全匹配你的分类标准;二是类别不均衡会非常严重,电池、灯管这种有害垃圾样本天然就少;三是模型不需要认识猫狗,但必须把塑料瓶和玻璃瓶分清楚,类间差异小、类内差异大。理解了这三点,后面的改进方向就清晰了。
2. 生活垃圾数据集的构建:比调模型更决定成败的一步
2.1 类别体系怎么定才不返工
我见过太多项目,模型都训练完了才发现类别定义有问题,返工重标,血亏。定类别体系时我建议遵循两个原则。
第一,对齐下游分拣能力。你的机械臂或者人工分拣线能区分到什么粒度,类别就定到什么粒度。如果后端根本没法把塑料瓶和塑料盒分开处理,那检测模型分那么细就是浪费标注成本。
第二,预留"其他"兜底类。真实场景永远有你没见过的垃圾,硬塞进现有类别会拉低整体精度。我一般会留一个"其他垃圾"类,把长尾样本都归进去,保证主类别的纯净度。
下面是我在一个社区项目里用的类别划分,供参考:
| 大类 | 细分类别 | 典型样本 | 标注难度 |
|---|---|---|---|
| 可回收物 | 塑料瓶、易拉罐、纸箱、玻璃瓶 | 饮料瓶、快递盒 | 中,易拉罐和玻璃瓶易混 |
| 有害垃圾 | 电池、灯管、药品包装 | 5号电池、节能灯 | 高,样本少且形态多样 |
| 厨余垃圾 | 果皮、菜叶、剩饭 | 香蕉皮、菜梗 | 低,但易腐烂变形 |
| 其他垃圾 | 烟头、纸巾、陶瓷碎片 | 用过的纸巾 | 高,类内差异极大 |
2.2 数据采集:别只在一个场景拍
数据采集最容易犯的错,就是在一个固定机位、固定光照下拍几千张,然后模型一换环境就废。我的做法是刻意制造多样性:
- 光照:白天自然光、夜间补光、逆光、阴影下各拍一批
- 角度:俯拍、侧拍、斜45度都要有,模拟不同投放口
- 堆叠:单件、两三件挨着、一堆混在一起,覆盖遮挡情况
- 背景:不同颜色的垃圾桶内壁、不同材质的地面
数量上,我的经验是每个细分类别至少500张有效标注图,主类别(塑料瓶、纸箱)建议1500张以上。总量控制在1万到3万张之间比较合理,再多边际收益就低了,除非你要做工业级部署。
2.3 标注规范:框要贴边,类别要唯一
标注质量直接决定模型上限。我用LabelImg或者Labelme做标注,几条硬规矩:
- 边界框紧贴目标外轮廓,不要留太多空白,也不要切掉目标边缘。框太松会让模型学到背景特征,框太紧会丢失目标信息。
- 遮挡目标按可见部分标注,如果遮挡超过70%,我一般直接标成困难样本或者丢弃,避免污染训练集。
- 一个目标一个框,类别唯一。塑料瓶就是塑料瓶,不要一会儿标"瓶子"一会儿标"塑料"。
- 小目标单独检查。烟头、瓶盖这种小于32×32像素的目标,标注时放大到200%再画框,否则很容易画歪。
标注完成后一定要做一轮交叉复核,让另一个人随机抽10%的图检查,错误率超过5%就整体返工。这一步偷懒,后面训练时loss不下降你都不知道是模型问题还是标注问题。
2.4 数据增强:针对垃圾场景的定制策略
通用增强(翻转、缩放、色彩抖动)当然要用,但垃圾场景有几个特殊需求:
- Mosaic增强:YOLOv8默认开启,把4张图拼成1张,能显著提升小目标和密集目标检测。但要注意,如果你的垃圾都是单件大目标,Mosaic比例别开太高,否则会引入不真实的拼接边界。
- 随机遮挡:模拟垃圾互相遮挡,用灰色或黑色矩形随机盖住目标的一部分,提升模型对遮挡的鲁棒性。
- 亮度对比度扰动:模拟不同光照,这个对投放口场景特别重要。
- 旋转:垃圾没有固定朝向,随机旋转±30度是合理的。
我一般把增强后的数据集和原始数据集按1:1混合训练,避免增强过度导致模型学到不真实的分布。
3. YOLOv8改进:针对生活垃圾检测的几个有效方向
3.1 先跑通基线,再谈改进
我特别反对一上来就魔改网络结构。正确顺序是:先用YOLOv8官方预训练权重跑一个基线,记录mAP、召回率、推理速度,然后针对baseline的短板做定向改进。没有基线,你根本不知道改进有没有用。
基线训练命令大概是这样:
yolo detect train data=garbage.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0跑完看验证集结果,重点关注三类指标:整体mAP@0.5、小目标的mAP、以及各类别的召回率。我遇到的典型问题是:塑料瓶和玻璃瓶混淆严重,电池类召回率极低。这两个问题分别对应不同的改进方向。
3.2 注意力机制:让模型聚焦垃圾主体
生活垃圾图像里背景占比往往很大,模型容易把注意力分散到垃圾桶边缘、地面纹理上。加入注意力机制能有效缓解这个问题。
我试过在YOLOv8的骨干网络C2f模块后面插入CBAM(卷积块注意力模块),它同时做通道注意力和空间注意力,计算量增加很小。实测下来,mAP@0.5提升了约2.1个百分点,尤其是背景复杂的样本提升明显。
另一个选择是SE注意力,更轻量,适合部署在算力受限的设备上。如果你用的是RK3588这类边缘芯片,我建议优先考虑SE,CBAM的额外开销在边缘端会放大。
注意:注意力模块不是加得越多越好。我在每个C2f后面都加了CBAM,结果参数量涨了15%,mAP只多了0.3个点,得不偿失。一般加在骨干网络的后两个stage就够了。
3.3 检测头改进:小目标检测的关键
生活垃圾里小目标特别多,瓶盖、烟头、电池,这些在640分辨率下可能只有十几个像素。YOLOv8原生的检测头对小目标已经比v5好,但还能再优化。
我做过两个有效改进:
第一,增加一个更浅层的检测头。YOLOv8默认用P3、P4、P5三个尺度的特征图做检测,P3对应80×80的网格,已经能覆盖中等目标。我在P2(160×160)上加了一个检测头,专门负责小目标。代价是计算量增加,推理速度下降约15%,但小目标mAP提升了近5个点。如果你的场景小目标占比高,这个改进值得。
第二,用解耦头替换耦合头。YOLOv8本身已经是解耦头了,但分类和回归分支的通道数可以调整。我把分类分支通道数适当增加,因为垃圾分类的类间差异小,需要更强的分类能力。
3.4 损失函数调优:解决类别不均衡
有害垃圾样本少,模型会倾向于预测成多数类。除了在数据层面做重采样,损失函数层面也能做文章。
YOLOv8默认用BCE做分类损失、CIoU做回归损失。我试过把分类损失换成Focal Loss,让模型更关注难分类样本。参数上,gamma取1.5到2之间比较合适,alpha按类别频率的倒数设置。实测电池类的召回率从0.62提升到了0.78,效果明显。
回归损失方面,如果小目标多,可以试试WIoU或者EIoU,对边界框回归的收敛更友好。不过这些改动需要改源码,不是改配置文件就能搞定的,动手前先备份。
3.5 改进效果对比:用数据说话
下面是我在一个约2万张图的数据集上做的消融实验,硬件是单卡RTX 3090,输入640×640,训练100轮:
| 方案 | mAP@0.5 | 小目标mAP | 推理速度(FPS) | 参数量(M) |
|---|---|---|---|---|
| YOLOv8s基线 | 0.842 | 0.613 | 142 | 11.2 |
| +CBAM注意力 | 0.863 | 0.638 | 131 | 11.8 |
| +P2检测头 | 0.871 | 0.682 | 118 | 13.5 |
| +Focal Loss | 0.879 | 0.691 | 118 | 13.5 |
| 全部改进 | 0.891 | 0.714 | 112 | 14.1 |
可以看到,改进带来的精度提升是实打实的,但速度也在下降。实际部署时要根据硬件做取舍,不是精度越高越好。
4. 训练调参:那些文档里不会写的细节
4.1 超参数怎么设才不玄学
YOLOv8的默认超参其实调得不错,但垃圾分类场景有几个参数我建议手动调整:
- 学习率:默认0.01,如果batch size比较小(比如8或16),建议降到0.005到0.008,否则前期loss震荡厉害。用余弦退火调度比固定学习率收敛更稳。
- warmup:默认3轮,我一般加到5轮,让模型在前期慢慢适应数据分布,尤其是自定义数据集和预训练数据差异大时。
- 权重衰减:默认0.0005,如果发现过拟合,可以加到0.001。
- 类别权重:YOLOv8支持在data.yaml里设置类别权重,稀有类别可以适当加权,但别超过3倍,否则会过拟合。
4.2 训练过程监控:看什么指标
训练时别只盯着loss。我一般同时看四个东西:
- 训练loss和验证loss的走势。如果训练loss一直降、验证loss先降后升,说明过拟合了,该加正则或者早停。
- mAP@0.5和mAP@0.5:0.95。前者看整体检测能力,后者看框的精准度。如果前者高后者低,说明框回归不够准。
- 各类别的PR曲线。重点看稀有类别的曲线,如果某个类别AP特别低,回去查数据。
- 混淆矩阵。这个最直观,能看出哪些类别互相混淆。我之前就是通过混淆矩阵发现塑料瓶和玻璃瓶大量互错,后来针对性补了这两类的对比样本。
画损失函数曲线可以用YOLOv8自带的results.csv,用pandas读出来画就行:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/detect/train/results.csv') df.columns = df.columns.str.strip() plt.plot(df['epoch'], df['train/box_loss'], label='train_box_loss') plt.plot(df['epoch'], df['val/box_loss'], label='val_box_loss') plt.legend() plt.savefig('loss_curve.png')4.3 过拟合和欠拟合的实战判断
欠拟合的表现是训练loss都降不下去,mAP上不去。原因通常是模型太小、学习率太低、或者数据标注质量差。我遇到过一次,查了半天发现是标注文件里类别索引从1开始,而YOLO要求从0开始,导致所有标签错位,模型根本学不到东西。
过拟合的表现是训练集mAP很高,验证集低。解决办法按优先级:加数据 > 加增强 > 加正则 > 减模型。加数据永远是最有效的,实在加不了再用后面几招。
4.4 迁移学习:预训练权重怎么用
YOLOv8官方提供了在COCO上预训练的权重,一定要用。哪怕你的类别和COCO完全不重叠,骨干网络学到的底层特征(边缘、纹理、颜色)也是通用的。我实测过从零训练和用预训练权重的差距,同样100轮,预训练版本mAP高出近10个点,收敛速度快一倍。
加载方式很简单,训练时指定model=yolov8s.pt就行,YOLOv8会自动加载预训练权重。如果你想冻结骨干网络只训练检测头,可以在配置文件里设置freeze参数,适合数据量特别小的情况。
5. 从训练到部署:让模型真正跑起来
5.1 模型导出与格式选择
训练完的.pt文件不能直接上生产,需要导出成推理格式。常见选择:
| 格式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| ONNX | 通用部署 | 跨平台,生态好 | 需要推理引擎 |
| TensorRT | NVIDIA GPU | 速度最快 | 绑定N卡 |
| OpenVINO | Intel CPU/核显 | CPU上快 | 绑定Intel |
| RKNN | 瑞芯微芯片 | 边缘端低功耗 | 需要专用工具链 |
导出ONNX的命令:
yolo export model=best.pt format=onnx opset=12 simplify=True如果你的部署设备是RK3588这类边缘芯片,需要先导出ONNX再转RKNN,转换时注意量化方式,INT8量化能提速但会掉点,建议先用FP16跑通再考虑量化。
5.2 推理速度优化:别让模型等垃圾
实时垃圾分类对延迟敏感。我做过统计,从摄像头捕获到输出结果,整个链路要控制在300毫秒以内,用户才感觉不到卡顿。优化手段有几个:
- 降低输入分辨率:640降到416,速度提升约40%,mAP掉2到3个点,看你能不能接受。
- 半精度推理:FP16比FP32快近一倍,精度几乎无损,GPU部署必开。
- 批处理:如果同时处理多路摄像头,攒批推理能提升吞吐,但会增加单帧延迟。
- 模型剪枝:把不重要的通道剪掉,参数量能降30%以上,但需要重新微调。
5.3 边缘端部署的坑
在RK3588上部署YOLOv8,我踩过的坑能写一篇。简单说几个:
第一,算子支持不全。YOLOv8里某些激活函数和上采样方式,RKNN工具链早期版本不支持,需要替换成等效算子。转模型前先查工具链的算子支持列表。
第二,量化掉点严重。INT8量化后mAP可能掉5到8个点,解决办法是用真实数据做量化校准,别用随机数据。
第三,内存带宽瓶颈。边缘芯片的内存带宽有限,模型太大跑不满帧率。YOLOv8n在RK3588上能跑到30FPS以上,YOLOv8s就掉到15FPS左右,选模型时要考虑硬件上限。
5.4 后处理与业务逻辑衔接
模型输出的是框和类别,业务系统需要的是"这袋垃圾该投哪个桶"。中间要做几件事:
- 置信度过滤:低于阈值的框直接丢,阈值一般设0.25到0.5之间,看召回和精确的取舍。
- NMS去重:同一个目标可能出多个框,用非极大值抑制合并。
- 投票决策:如果一张图里检测到多个类别,按数量或者面积加权投票,决定整袋垃圾的主类别。
- 异常处理:检测不到任何目标时,返回"无法识别",引导用户重新投放,别硬猜。
6. 几个容易被忽略但很致命的问题
6.1 数据集泄漏:训练集和验证集别来自同一批图
这是新手最容易犯的错。如果你把同一张图增强后的版本分别放进训练集和验证集,验证指标会虚高,实际部署就露馅。正确做法是按原始图像划分,增强只在训练集内部做。我一般按8:1:1划分训练、验证、测试,测试集完全不参与训练和调参。
6.2 类别定义随场景漂移
今天定义塑料瓶是一类,明天业务方说要把矿泉水瓶和饮料瓶分开,类别体系一变,整个数据集和模型都要重来。所以项目初期一定要和业务方把类别定义白纸黑字定下来,预留扩展空间。
6.3 模型更新与版本管理
模型不是训练一次就完事。新垃圾种类出现、场景变化、数据积累,都需要重新训练。我建议每次训练都记录:数据集版本、超参数、代码commit、评估指标。用MLflow或者简单的表格管理都行,别到时候出了问题找不到对应版本。
6.4 实际部署中的光照和镜头维护
再好的模型也架不住镜头脏了、灯坏了。投放口的摄像头要定期清洁,补光灯要检查。我遇到过模型突然精度暴跌,排查半天发现是镜头上有油污,擦干净就恢复了。这种非模型问题,往往比调参更影响实际效果。
7. 写在最后的一点个人体会
这个项目从立项到稳定运行,前后折腾了大半年。最大的感受是:模型只是整个系统的一环,数据质量、类别定义、部署环境、业务衔接,每一环都能决定成败。我见过太多人把精力全砸在改进网络结构上,结果数据集一塌糊涂,最后效果还不如老老实实用YOLOv8基线加干净数据。
如果你正准备做垃圾分类检测,我的建议是:先把数据采集和标注做扎实,用YOLOv8基线跑通全流程,再根据实际短板做定向改进。改进要有数据支撑,别为了改而改。部署时优先保证稳定性和实时性,精度够用就行,别追求论文级的指标。
另外,YOLO系列更新很快,v8之后还有新版本,但核心思想是相通的。把v8吃透,迁移到其他版本成本很低。真正值钱的是你对业务场景的理解和数据处理的能力,这些才是别人抄不走的。