
垃圾分类这件事说起来简单做起来是真磨人。我在社区做过一段时间的智能回收设备调研最直观的感受就是居民站在四个桶前面犹豫的那三秒钟背后其实是一整套识别、判断、反馈的链路在支撑。而把这套链路搬到摄像头和边缘设备上让它自动完成看一眼就知道是什么垃圾的任务就是我这段时间折腾的核心——基于YOLOv8改进算法的生活垃圾图像识别系统。这篇文章不打算写成论文摘要而是把我从数据集构建、模型选型、改进思路、训练调参到部署落地的完整过程摊开来讲包括那些文档里不会写、只有真正跑过一遍才会遇到的坑。不管你是刚接触目标检测的学生还是想把这个方向做成实际产品的开发者应该都能从里面找到能直接抄作业的部分。1. 为什么垃圾分类识别不能直接套用通用检测模型1.1 生活垃圾图像的脏乱差到底难在哪通用目标检测模型在COCO、VOC这类数据集上刷到很高的mAP拿来直接检测垃圾结果往往惨不忍睹。原因不是模型不行而是垃圾图像本身的分布和通用数据集差得太远。我总结下来主要有四个难点。第一是类内差异极大。同样是塑料瓶有透明的矿泉水瓶、有绿色的雪碧瓶、有压扁的、有带标签的、有装了一半液体的。它们在像素层面的差异可能比塑料瓶和玻璃瓶之间的差异还大。模型要学的是一个语义概念但输入给它的却是千变万化的外观。第二是类间差异极小。一次性纸杯和塑料杯在低分辨率下几乎一模一样用过的纸巾和干净的纸巾属于其他垃圾和可回收物两个类别但视觉上只差一点污渍。这种细粒度分类问题是垃圾分类识别最核心的挑战。第三是遮挡与堆叠。真实场景里垃圾很少单独摆放往往是堆在一个袋子里、桶里互相遮挡。一个瓶子可能只露出三分之一模型必须靠局部特征判断。第四是光照和背景干扰。社区投放点有顶灯、有阴影、有反光的地砖还有路人经过。这些都会让检测框抖动、置信度忽高忽低。提示如果你打算用公开数据集直接开跑先做好心理准备——公开垃圾数据集的标注质量参差不齐很多框是大概框一下直接训练会引入大量噪声。1.2 四分类标准与检测粒度的取舍国内主流的生活垃圾分类是四分类可回收物、有害垃圾、厨余垃圾湿垃圾、其他垃圾干垃圾。但做检测系统时你不能只做四个类别的分类头那样粒度太粗实际使用中用户需要知道这个具体是什么、该扔哪个桶。我的做法是两级映射检测层识别细类别比如矿泉水瓶易拉罐电池菜叶纸尿裤等再通过一张映射表把细类别归到四大类。这样做的好处是模型学的是视觉上更可分的细类而业务层拿到的是用户能理解的粗类。映射表可以随时调整不用重新训练模型。细类别示例归属大类视觉特征要点矿泉水瓶、易拉罐、纸箱可回收物材质反光、形状规整电池、灯管、药品有害垃圾体积小、颜色鲜艳或金属感菜叶、果皮、剩饭厨余垃圾颜色偏绿黄、形状不规则纸巾、纸尿裤、烟头其他垃圾颜色灰白、无明显反光这个表看起来简单但实际标注时你会发现边界案例特别多。比如沾了油的纸箱到底算可回收还是其他垃圾我的处理原则是以视觉特征为主业务规则为辅。模型只负责看到什么规则层负责怎么归类。这样模型不会因为规则变化而失效。1.3 YOLOv8作为基线的合理性分析选YOLOv8不是因为它最先进而是因为它在精度、速度、工程友好度三者之间平衡得最好。我对比过几个主流方案Faster R-CNN精度不错但两阶段推理速度慢边缘设备上跑不动。SSD轻量但对小目标检测效果一般垃圾里的小目标电池、烟头很容易漏。YOLOv5成熟稳定社区资源多但head结构和损失函数相比v8略旧。YOLOv8anchor-free设计解耦头Task-Aligned Assigner对小目标和密集场景更友好而且Ultralytics的工程封装做得极好训练、验证、导出一条龙。最关键的是YOLOv8的可改进空间大。它的backbone是C2f结构neck是PAN-FPNhead是解耦头每个部分都有成熟的改进方案可以嫁接。对于研究型项目来说这意味着你能在基线之上做出有说服力的对比实验。2. 数据集构建比调模型更重要的脏活累活2.1 数据来源与采集策略我一开始想偷懒直接用公开数据集。试了几个之后发现公开数据集的类别定义和国内四分类对不上而且拍摄场景偏实验室和真实社区环境差距大。最后还是决定自己采。采集渠道主要有三个一是社区投放点的监控截图脱敏处理二是自己拿手机在小区、办公室、食堂拍的三是从公开数据集中筛选出符合类别的样本做补充。采集时我刻意覆盖了不同时间段白天、傍晚、夜间灯光下、不同角度俯拍、平拍、斜拍、不同距离近景、中景。这里有个经验宁可多拍不要少拍。我最初每类只拍了200张训练出来模型对没见过的角度泛化很差。后来补到每类800到1200张效果明显提升。垃圾图像的多样性太高数据量不够模型学到的就是记忆而不是特征。2.2 标注规范与常见标注错误标注用的是LabelImg和Roboflow结合。标注规范我定了几条硬规则遮挡超过70%的目标不标因为标了模型也学不到有效特征反而引入噪声。密集堆叠时只标可见部分框贴紧可见区域不要脑补完整形状。同类目标相邻时框不要重叠太多否则NMS阶段容易互相抑制。模糊到人眼都难分辨的直接跳过不要勉强标。我踩过最大的坑是标注不一致。同一个塑料瓶有时候标了瓶盖有时候没标有时候框到瓶身有时候框到整个瓶子加影子。这种不一致会让模型困惑表现为置信度普遍偏低。后来我做了两件事一是写了一份带图示例的标注手册二是标注完后随机抽100张做交叉复核。复核发现的问题比例一开始有15%改了两轮之后降到3%以下。注意标注质量对最终mAP的影响往往比换一个改进模块还大。如果你时间有限优先把标注做干净而不是急着改网络结构。2.3 数据增强不是越多越好YOLOv8自带的数据增强包括Mosaic、HSV调整、翻转、缩放等。我额外加了几个针对垃圾场景的增强随机遮挡模拟垃圾被部分遮挡的情况用随机色块覆盖图像的一部分。亮度扰动模拟不同光照尤其是夜间灯光和阴影。运动模糊模拟监控画面中移动的垃圾。但增强不是越多越好。我试过把Mosaic概率开到1.0结果模型在小目标上反而变差因为Mosaic把四张图拼在一起小目标被缩得更小。后来把Mosaic概率降到0.5并关闭了最后10个epoch的MosaicYOLOv8默认有close_mosaic参数小目标检测明显改善。增强方式参数建议适用场景风险Mosaic0.5最后10轮关闭提升泛化过高会伤小目标HSVh0.015, s0.7, v0.4光照变化大过高导致颜色失真随机遮挡概率0.3面积10%-30%遮挡场景过高导致欠拟合运动模糊概率0.2监控画面过高损失细节3. YOLOv8改进我试过的几个方向和真实效果3.1 注意力机制不是加了就涨点注意力机制是改进论文里最常见的操作。我试了SE、CBAM、ECA和CoordAtt四种加在backbone的不同位置。结论可能和很多论文相反不是所有位置加注意力都涨点加错位置反而掉点。我的实验结果是在backbone的C2f模块之后加CoordAtt协调注意力对垃圾分类这种需要区分位置和形状的任务帮助最大mAP50涨了约1.8个点。原因是CoordAtt把位置信息编码进通道注意力对瓶子在画面哪个位置、朝向如何这类信息更敏感。而SE和ECA提升不明显CBAM在neck部分加反而让小目标变差因为它对空间注意力的计算会抑制小目标的响应。代码上CoordAtt的接入方式是在C2f输出后插入import torch import torch.nn as nn class CoordAtt(nn.Module): def __init__(self, channels, reduction32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) self.pool_w nn.AdaptiveAvgPool2d((1, None)) mip max(8, channels // reduction) self.conv1 nn.Conv2d(channels, mip, 1) self.bn1 nn.BatchNorm2d(mip) self.act nn.ReLU(inplaceTrue) self.conv_h nn.Conv2d(mip, channels, 1) self.conv_w nn.Conv2d(mip, channels, 1) def forward(self, x): identity x n, c, h, w x.size() x_h self.pool_h(x) x_w self.pool_w(x).permute(0, 1, 3, 2) y torch.cat([x_h, x_w], dim2) y self.act(self.bn1(self.conv1(y))) x_h, x_w torch.split(y, [h, w], dim2) x_w x_w.permute(0, 1, 3, 2) a_h torch.sigmoid(self.conv_h(x_h)) a_w torch.sigmoid(self.conv_w(x_w)) return identity * a_h * a_w接入位置建议在backbone最后两个stage之后不要每个stage都加否则参数量和推理时间上去了收益却递减。3.2 损失函数与标签分配小目标漏检的解法垃圾分类里小目标电池、烟头、瓶盖漏检是最头疼的问题。YOLOv8默认用Task-Aligned Assigner做标签分配对中等目标友好但小目标因为IoU低容易被判为负样本。我的改进是在标签分配阶段引入小目标权重。具体做法是在计算对齐度量时对面积小于阈值比如32x32像素的GT框乘以一个大于1的系数让它们更容易被选为正样本。这个改动很小但小目标召回率提升了约4个点。另外损失函数上我把CIoU换成了WIoUWise-IoU。WIoU对低质量样本的梯度做了动态调整垃圾图像里标注质量不高的框比较多WIoU能减轻这些框对训练的负面影响。实测mAP50涨了约0.9个点而且训练曲线更平滑。# WIoU核心计算示意 def wiou_loss(pred, target, iou): # 动态调整梯度权重 beta iou.detach() / (iou.detach().mean() 1e-7) weight beta * torch.exp(beta - beta.max()) return (1 - iou) * weight3.3 轻量化为边缘部署做准备如果只是发论文用YOLOv8l甚至x都行。但要做实际部署尤其是往RK3588这类边缘芯片上搬模型必须轻。我的策略是用YOLOv8n或s做基线再做结构剪枝。具体操作先正常训练一个YOLOv8s然后用BN层的缩放因子做通道剪枝剪掉贡献小的通道再微调。剪枝率控制在30%左右mAP掉约1.5个点但参数量减少近40%推理速度提升明显。RK3588上跑剪枝后的模型输入640x640能到25FPS以上基本满足实时需求。提示剪枝后一定要微调而且微调的学习率要小比如初始lr的0.1倍否则精度很难恢复。4. 训练调参那些让模型突然变好的细节4.1 超参数配置与我的实际取值YOLOv8的默认超参其实调得不错但针对垃圾数据集我改了几个关键值参数默认值我的取值调整理由epochs100200垃圾数据难学需要更多轮batch168显存有限小batch配合梯度累积lr00.010.005小数据集学习率低一点更稳lrf0.010.001最终学习率更低收敛更细warmup_epochs35数据量小warmup长一点close_mosaic1015小目标多提前关Mosaicpatience5080给模型更多耐心这里重点说学习率。我一开始用默认0.01训练loss震荡得厉害mAP忽高忽低。降到0.005之后曲线明显平滑。垃圾数据集的类别不平衡比较严重可回收物样本多有害垃圾样本少学习率太高会让模型偏向多数类。4.2 损失曲线怎么看从曲线判断问题YOLOv8训练完会生成loss曲线和mAP曲线。很多人只看最终mAP其实曲线形态信息量更大。box_loss持续下降但mAP不涨可能是过拟合或者标注框质量有问题。cls_loss震荡剧烈类别不平衡或学习率过高。dfl_loss下降慢分布焦点损失在学边界慢是正常的但如果一直不降说明目标边界模糊标注可能不准。mAP50涨但mAP50-95不涨框的位置不够准可以考虑换损失函数或加更多位置相关的增强。我遇到过最典型的问题是验证集mAP比训练集还高。一开始以为是好事后来发现是验证集和训练集有重叠样本数据划分时没做好去重。重新按图像来源划分后这个现象消失。所以数据划分一定要按场景或来源分不能随机分否则同一场景的相似图像会同时出现在训练和验证集造成虚高。4.3 类别不平衡的处理垃圾数据集里可回收物瓶子、纸箱样本最多有害垃圾电池、灯管最少比例可能到10:1。直接训练模型对有害垃圾的召回率很低。我用了三个手段组合过采样对少数类图像做复制但配合更强的增强避免过拟合。类别权重在损失里给少数类更高权重YOLOv8可以通过修改cls损失实现。focal loss对易分类样本降权让模型关注难样本。三者叠加后有害垃圾的召回率从不到50%提升到78%左右。但要注意过采样不能太过否则模型会对少数类过拟合在真实场景里误检增多。5. 从训练到部署RK3588上的落地实录5.1 模型导出与格式转换训练完的.pt模型不能直接上RK3588需要转成RKNN格式。流程是PyTorch - ONNX - RKNN。每一步都有坑。导出ONNX时opset版本选12比较稳太高或太低都可能遇到算子不支持。导出命令yolo export modelbest.pt formatonnx opset12 simplifyTrue转RKNN时需要指定目标平台和量化方式。我用的是混合量化对精度敏感的层保持FP16其余用INT8。纯INT8量化后mAP掉得比较多约3个点混合量化只掉1个点左右。from rknn.api import RKNN rknn RKNN() rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_dtypew8a8) rknn.load_onnx(modelbest.onnx) rknn.build(do_quantizationTrue, datasetquant_dataset.txt) rknn.export_rknn(best.rknn)量化数据集很关键要从训练集里抽300到500张有代表性的图覆盖各个类别和场景。如果量化数据集偏了量化后的精度会崩。5.2 推理速度与精度的平衡RK3588有三个NPU核心可以多核并行。我用RKNN的core_mask参数开启多核推理速度提升约2倍。但多核调度有开销小模型不一定划算需要实测。配置输入尺寸推理时间mAP50单核 INT864045ms0.82三核 INT864022ms0.82三核 混合量化64028ms0.85三核 混合量化41615ms0.79从表里能看出输入尺寸从640降到416速度提升明显但精度掉得多。如果场景对实时性要求极高可以考虑416否则建议640。5.3 后处理与业务逻辑对接模型输出的是检测框、类别、置信度业务层需要把它变成该扔哪个桶的指令。这里有几个工程细节置信度阈值设太高会漏检设太低会误检。我最终用0.35作为阈值并对低于0.5的框做二次确认比如连续多帧检测到同一目标才输出。NMS IoU阈值垃圾堆叠多NMS阈值设0.5比较合适太高会保留重复框太低会误删相邻目标。多帧投票单帧检测不稳定我用连续5帧的结果做投票取出现次数最多的类别稳定性提升明显。注意业务逻辑里一定要加未知类别的兜底。模型不可能认识所有垃圾遇到置信度都很低的情况应该输出请人工确认而不是强行归类。6. 实测中的意外与排查思路6.1 模型在真实场景翻车的几种典型情况训练指标好看不代表真实场景好用。我遇到过的翻车情况包括情况一反光地面导致误检。社区投放点地砖反光模型把光斑检测成塑料瓶。排查后发现是训练集里缺少反光地面的负样本。补了200张纯背景图无目标作为负样本训练后误检大幅减少。情况二夜间红外画面失效。监控夜间切红外图像变黑白模型对颜色依赖强的类别比如绿色菜叶识别率骤降。解法是训练时加入灰度化增强让模型学会用形状和纹理判断。情况三密集堆叠时框合并。一堆瓶子挤在一起模型只输出一个大框。这是NMS的问题调整IoU阈值和改用Soft-NMS后改善。6.2 排查链路从现象到根因遇到问题不要瞎调参按这个链路走先看数据把出问题的图像拿出来看标注是否正确、是否在训练分布内。再看推理用单张图跑推理可视化特征图和热力图看模型关注哪里。再看指标分类别看mAP和召回率定位是哪个类别拖后腿。最后调模型确认是模型问题后再考虑改结构或调参。我见过太多人一上来就改网络结果问题是标注错了。数据问题占实际问题的70%以上这个比例在我自己的项目里只高不低。6.3 几个容易被忽略的工程细节图像预处理一致性训练时的resize、归一化方式推理时必须完全一致否则精度会掉。类别顺序训练时的类别索引和部署时的映射表必须对齐错一位全盘皆输。模型版本管理每次训练保存模型时把超参、数据集版本、mAP都记下来不然过两周自己都忘了哪个模型是哪个。日志推理服务一定要打日志记录每张图的检测结果和耗时出问题能回溯。7. 这套系统还能怎么继续打磨如果你已经跑通了基线想继续深入有几个方向值得试。一是开放词汇检测让模型能识别训练集里没有的类别这对垃圾这种长尾分布的场景特别有价值。二是多模态融合结合重量传感器或材质传感器弥补纯视觉的不足。三是增量学习让系统能在部署后持续学习新样本而不用每次重新训练。我自己下一步打算试的是把检测和分割结合用YOLOv8-seg做实例分割这样不仅能知道是什么还能知道形状和边界对堆叠场景的分离更有帮助。另外轻量化方向还可以试知识蒸馏用大模型教小模型在保持速度的同时提升精度。最后分享一个我在实际项目里体会最深的点垃圾分类识别不是一个纯算法问题而是一个数据、算法、业务三者咬合的系统工程。模型再准如果业务逻辑没兜底、数据分布没覆盖、部署环境没适配上线后照样出问题。把每个环节都做扎实比追求某个模块的涨点更有价值。