1. 项目缘起与整体设计思路
垃圾分类这件事,说起来简单,做起来是真磨人。我在社区做过一段时间的分类督导志愿者,亲眼见过督导员扯着嗓子喊“纸巾是干垃圾、电池是有害垃圾”,结果居民该扔错还是扔错。人工督导成本高、效率低,而且没法全天候覆盖。后来我就琢磨,能不能用视觉识别的方式,让摄像头自动判断垃圾类别,把结果实时显示在屏幕上,甚至联动分拣装置。这就是我做这个项目的出发点——基于YOLOv8改进算法的生活垃圾图像识别系统。
这个系统的核心任务很明确:输入一张生活垃圾图片或者一段实时视频流,输出图中每个垃圾目标的类别标签和边界框位置。类别通常按四分类标准来:可回收物、有害垃圾、厨余垃圾、其他垃圾。听起来像是一个标准的目标检测任务,但生活垃圾这个场景有它的特殊性——目标形态极度不规则、类间差异小(比如用过的纸巾和干净的纸盒,一个属于其他垃圾一个属于可回收物)、遮挡严重、光照条件复杂。直接用通用检测模型效果并不理想,所以我在YOLOv8的基础上做了一些针对性改进。
适合谁来参考这篇内容?如果你已经跑通过YOLOv8的官方demo,想把它落地到一个具体场景里,那这篇东西对你会有帮助。如果你是完全零基础,建议先把YOLOv8的环境配置和基础训练流程过一遍,再回来看改进部分,不然容易卡在细节里。我会尽量把每个设计决策背后的“为什么”讲清楚,包括我踩过的坑和试错过程,让你少走弯路。
整个项目的技术路线可以概括为:数据采集与标注 → 基线模型训练 → 针对垃圾场景的改进 → 模型评估与调优 → 部署推理。下面我按这个顺序,把每个环节的关键细节拆开来讲。
2. 数据集构建与预处理的关键细节
2.1 垃圾数据集的采集策略与类别定义
数据集是目标检测的地基,地基没打好,后面模型改出花来也没用。我一开始想偷懒,直接用公开数据集,但找了一圈发现,现成的垃圾检测数据集要么类别定义和国内四分类标准对不上,要么场景太干净——都是白底摆拍图,跟实际垃圾桶里的情况差太远。所以最后决定自己采集。
采集渠道有三个:一是用手机在社区垃圾桶旁边拍,不同时间段、不同天气都拍一些;二是从公开数据集中筛选可用图片,重新按四分类标准标注;三是用数据增强的方式扩充少数类。这里有个经验:有害垃圾的样本极度稀缺,因为日常生活中电池、药品、灯管这些东西出现频率低,如果不刻意补充,模型对有害垃圾的识别率会惨不忍睹。我的做法是单独去搜集这类物品的图片,甚至买了一些实物回来摆拍。
类别定义上,我最终确定了四个一级类别,但在标注时细化了二十多个二级标签,训练时再映射回一级类别。这样做的好处是,标注阶段可以保留更细的语义信息,后续如果想做更精细的分类,不用重新标注。比如“塑料瓶”和“易拉罐”都映射到可回收物,但标注时分开标,模型学到的特征更具体。
2.2 标注规范与质量控制
标注工具我用的是LabelImg,后来换成了Labelme,因为Labelme支持多边形标注,对不规则形状的垃圾更友好。但这里有个取舍:多边形标注精度高,但标注成本也高,而且转成YOLO格式的矩形框时会损失信息。我的折中方案是,对形状规则的垃圾(瓶子、盒子)用矩形框,对形状极不规则的(比如揉成一团的塑料袋)用多边形,导出时统一转成外接矩形。
标注质量控制这块,我吃了不少亏。一开始找了两个人标,结果发现同一张图,一个人标了“纸巾”作为其他垃圾,另一个人标了“纸盒”作为可回收物。标注不一致是目标检测里最隐蔽的杀手,模型会学得稀里糊涂。后来我定了一份标注手册,把每个类别的判定标准写清楚,比如“被油污污染的纸盒归为其他垃圾,干净的归为可回收物”,并且每标完一批就抽检10%,发现不一致就返工。
还有一个细节:边界框的松紧度要统一。有的人标框喜欢贴紧目标边缘,有的人喜欢留一点余量。这个不一致会导致模型学到的边界回归目标不稳定。我的要求是框的边缘距离目标轮廓大约2到3个像素,所有标注人员统一标准。
2.3 数据增强与类别平衡处理
垃圾数据集的类别不平衡问题非常突出。可回收物和其他垃圾的样本量可能是有害垃圾的十几倍。直接训练的话,模型会倾向于把大多数目标预测成多数类。我用了三种手段来缓解:
第一种是在线数据增强,在训练时实时做随机翻转、旋转、色彩抖动、马赛克增强。YOLOv8内置了这些增强策略,我重点调了mosaic和mixup的概率。马赛克增强对小目标检测特别有效,因为它把四张图拼成一张,变相增加了小目标的出现频率。
第二种是离线过采样,对有害垃圾和厨余垃圾的图片进行复制,并施加不同的增强变换,让每个类别的样本量大致均衡。但要注意,过采样不能简单复制,否则容易过拟合。我的做法是对少数类图片做更强的增强,比如随机裁剪、添加噪声、调整亮度对比度。
第三种是Focal Loss的引入,这个在后面改进部分会详细讲。简单说就是让模型在训练时更关注难分类的样本,而不是被大量易分类的多数类样本主导。
| 类别 | 原始样本数 | 增强后样本数 | 占比 |
|---|---|---|---|
| 可回收物 | 3200 | 4800 | 30% |
| 有害垃圾 | 680 | 4200 | 26% |
| 厨余垃圾 | 2100 | 4500 | 28% |
| 其他垃圾 | 3800 | 4600 | 29% |
注意:数据增强不是越多越好。我试过把增强倍数拉到很高,结果验证集损失震荡严重,模型学到的更多是增强后的伪特征而不是真实特征。建议增强后的总样本量控制在原始样本量的1.5到2倍之间。
3. YOLOv8基线模型训练与参数调优
3.1 环境配置与训练参数含义解析
环境配置这块,我用的是一台带RTX 3060的台式机,CUDA 11.8,PyTorch 2.0。YOLOv8的安装很直接,pip install ultralytics就搞定了。但有个坑要注意:ultralytics库更新很频繁,不同版本的默认参数和API可能有差异。我建议锁定一个稳定版本,比如8.0.x系列,不要盲目追新。
训练参数是很多人容易迷糊的地方,我挑几个关键的说清楚:
imgsz:输入图像尺寸。YOLOv8默认640,我试过用1280,小目标检测效果确实有提升,但显存占用翻倍,训练速度慢了一倍多。最后折中用了800,在精度和速度之间取了个平衡。batch:批次大小。这个要根据显存来调。RTX 3060 12G显存,imgsz=800时batch=8比较稳,再大就容易OOM。epochs:训练轮数。我设了300轮,但实际在220轮左右就收敛了。YOLOv8有早停机制,patience=50,验证集损失50轮不下降就自动停。lr0:初始学习率。默认0.01,我调到了0.005,因为垃圾数据集不算特别大,学习率太大会导致损失震荡。lrf:最终学习率因子。默认0.01,意思是最终学习率是初始学习率的1%。我用余弦退火策略,让学习率从0.005平滑降到0.00005。weight_decay:权重衰减,防止过拟合。默认0.0005,我加到了0.001,因为垃圾场景的过拟合风险比较高。
还有一个参数叫close_mosaic,意思是最后多少轮关闭马赛克增强。默认是10,我设成了20。因为马赛克增强会让目标变得不自然,最后阶段关掉它,让模型在真实分布上微调一下,对最终精度有好处。
3.2 基线模型训练过程与损失曲线解读
第一次训练我用的完全是默认参数,结果mAP50只有0.72,而且有害垃圾的召回率不到0.5。训练损失曲线倒是降得很漂亮,但验证损失在第80轮之后开始抬头,典型的过拟合。我当时的判断是:模型容量够,但数据不够,而且类别不平衡导致模型对少数类不敏感。
第二次训练我加了数据增强和类别平衡,mAP50提到了0.79,有害垃圾召回率到了0.68。但还是不够。看混淆矩阵发现,厨余垃圾和其他垃圾之间的混淆特别严重,比如剩饭剩菜和用过的纸巾,模型经常搞混。这其实也合理,因为这两类在视觉上确实有重叠——都是软塌塌的、颜色偏暗的团状物。
第三次训练我做了两个改动:一是把输入尺寸从640提到800,二是引入了注意力机制(后面会细讲)。mAP50到了0.85,厨余和其他垃圾的混淆率下降了大约15个百分点。损失曲线也健康多了,验证损失和训练损失同步下降,没有明显的过拟合迹象。
实操心得:看损失曲线不要只看总损失,要分项看。YOLOv8的损失包括边界框损失、分类损失和DFL损失。如果分类损失降不下去,说明类别区分度不够,要考虑加注意力或者改损失函数;如果边界框损失降不下去,说明定位精度有问题,可能要检查标注质量或者调整锚框。
3.3 学习率调度与 warmup 策略
学习率调度对训练稳定性影响很大。我试过固定学习率、阶梯下降和余弦退火三种。固定学习率最容易震荡,阶梯下降还行但需要手动设里程碑,余弦退火最省心,效果也最好。YOLOv8默认用的就是余弦退火,我只需要调好lr0和lrf就行。
Warmup策略也值得说一下。YOLOv8默认有3轮warmup,学习率从很小的值线性升到初始学习率。这个对防止训练初期梯度爆炸很有用,尤其是当batch size比较大的时候。我试过关掉warmup,结果前几轮损失直接飙到nan,模型完全学不动。所以warmup不要关,除非你有特别的理由。
还有一个细节:warmup_momentum和warmup_bias_lr这两个参数。前者控制warmup阶段的动量,默认0.8,后者控制warmup阶段的偏置学习率,默认0.1。我一般不动这两个,除非训练初期就出现严重的梯度问题。
4. 针对垃圾场景的YOLOv8改进方案
4.1 注意力机制引入与位置选择
垃圾图像的特点是目标密集、遮挡严重、类间差异小。标准的卷积操作感受野有限,很难捕捉全局上下文信息。比如一个塑料袋被其他垃圾压住了一半,只看局部特征很难判断它是可回收物还是其他垃圾。注意力机制可以让模型关注更重要的区域,抑制背景噪声。
我试了三种注意力:SE、CBAM和CoordAttention。SE最简单,只做通道注意力,提升有限。CBAM同时做通道和空间注意力,效果好一些,但参数量增加明显。CoordAttention是我最终选的,因为它把位置信息嵌入到通道注意力里,对目标定位特别有帮助。垃圾检测里,位置信息很重要——比如电池通常比较小、形状规则,而厨余垃圾通常是大块的、不规则的。
注意力模块加在哪里也有讲究。我试过加在Backbone的每个C2f模块后面,结果参数量暴涨,推理速度掉了一半。后来改成只加在Backbone的最后两个阶段和Neck的上采样路径上,精度提升差不多,但速度只掉了15%左右。这个取舍很关键,因为实际部署时推理速度是硬指标。
4.2 损失函数改进与难样本挖掘
前面提到类别不平衡的问题,Focal Loss是标准解法。YOLOv8默认的分类损失是BCE Loss,我换成了Varifocal Loss。为什么不用Focal Loss?因为Focal Loss对正负样本一视同仁地降权,而Varifocal Loss对正样本的权重是根据IoU来定的——IoU高的正样本权重大,IoU低的权重小。这在垃圾检测里特别有用,因为有些标注框本身就不太准(垃圾形状太不规则了),Varifocal Loss能降低这些噪声标注的影响。
边界框损失我用了CIoU Loss,YOLOv8默认也是CIoU,但我调了其中的alpha和gamma参数。CIoU考虑了重叠面积、中心点距离和长宽比,对垃圾这种形状多变的目标比较友好。我试过换成EIoU,理论上更好,但实际提升不明显,而且训练不稳定,最后又换回了CIoU。
还有一个改进是难样本挖掘。我在训练后期(最后50轮)启用了在线难样本挖掘,把分类损失最高的那部分样本的权重提高。这个做法有风险,因为如果难样本本身就是标注错误的,会放大错误。所以我在启用之前,先人工检查了一遍高损失样本,把明显标错的清理掉了。
4.3 网络结构微调与轻量化考量
YOLOv8本身有n、s、m、l、x五个尺度。我一开始用的是YOLOv8m,精度不错但推理速度在边缘设备上跑不动。后来换成了YOLOv8s,精度掉了大约3个百分点,但速度提升了一倍多。最终部署在RK3588上,YOLOv8s的推理帧率能到25FPS左右,基本满足实时性要求。
网络结构上我还做了一个小改动:把Neck部分的C2f模块换成了C3模块。C2f是YOLOv8的新设计,特征融合更充分,但计算量也更大。C3是YOLOv5的老设计,更轻量。在垃圾检测这个任务上,我实测发现C3和C2f的精度差异不到1个百分点,但C3的推理速度快了大约20%。这个取舍在边缘部署场景下是值得的。
还有一个细节是输入分辨率的动态调整。训练时用800,推理时可以根据设备性能动态调整。RK3588上我用的是640,精度损失在可接受范围内,但帧率能稳定在30FPS以上。
| 改进项 | mAP50 | 推理速度(RK3588) | 参数量 |
|---|---|---|---|
| YOLOv8s基线 | 0.79 | 18 FPS | 11.2M |
| +CoordAttention | 0.83 | 15 FPS | 12.8M |
| +Varifocal Loss | 0.85 | 15 FPS | 12.8M |
| +C3替换C2f | 0.84 | 19 FPS | 10.5M |
| 最终方案 | 0.85 | 25 FPS | 10.5M |
5. 模型评估、部署与常见问题排查
5.1 评价指标解读与模型评估
目标检测的评价指标比分类任务复杂得多。最常用的是mAP,全称是mean Average Precision。简单说,就是先算每个类别的AP(平均精度),再对所有类别取平均。AP本身是PR曲线下的面积,综合了精确率和召回率。mAP50指的是IoU阈值为0.5时的mAP,mAP50-95则是IoU从0.5到0.95每隔0.05取一个阈值,算出来的平均值再平均。后者更严格,也更能反映模型的定位精度。
我的最终模型在测试集上的表现是:mAP50为0.85,mAP50-95为0.62。分类来看,可回收物的AP最高,达到0.91,因为它的视觉特征最明显(瓶子、罐子、纸盒形状规则)。有害垃圾的AP最低,只有0.76,主要原因是样本太少,而且电池、药品、灯管之间的差异很大,模型很难用一个统一的特征来表示。厨余垃圾和其他垃圾的AP分别是0.82和0.80,混淆主要发生在这两类之间。
除了mAP,我还看了混淆矩阵和PR曲线。混淆矩阵能直观地看出哪些类别容易搞混,PR曲线能看出在不同召回率下的精确率表现。对于垃圾分类这种应用,我其实更看重召回率——宁可把可回收物误判成其他垃圾,也不要反过来,因为误判成其他垃圾只是浪费了回收价值,而误判成可回收物会污染整批回收物。
5.2 RK3588部署实操与性能优化
RK3588是瑞芯微的一款边缘计算芯片,带NPU,算力不错,功耗也低,很适合做垃圾分类的边缘部署。部署流程大致是:PyTorch模型 → ONNX → RKNN。这里有几个坑要重点说。
第一个坑是算子支持。RKNN的NPU不是所有ONNX算子都支持,比如我用的CoordAttention里的某些操作,RKNN就不认。解决办法是用RKNN Toolkit的模型转换工具,它会列出不支持的算子,然后我把这些算子替换成等效的支持算子。这个过程比较繁琐,但没办法,边缘部署就是这样。
第二个坑是量化精度损失。RKNN支持INT8量化,能大幅提升推理速度,但精度会掉。我实测发现,量化后mAP50从0.85掉到了0.81,掉了4个百分点。为了弥补,我用了混合量化——对精度敏感的层保持FP16,其他层用INT8。这样mAP50只掉了1.5个百分点,速度比全FP16快了将近一倍。
第三个坑是输入预处理。RK3588的NPU对输入格式有要求,比如NHWC和NCHW的区别。我一开始没注意,直接传了NCHW的图,结果推理结果完全不对。后来改成NHWC,并且把归一化操作也放到NPU里做,才正常。
部署后的性能:YOLOv8s在RK3588上,输入640x640,INT8量化,推理帧率稳定在25到30FPS,功耗大约5W。这个表现对于社区垃圾桶场景来说完全够用了。
5.3 常见问题速查与避坑指南
在做这个项目的过程中,我遇到了不少问题,这里整理成速查表,方便你遇到类似情况时快速定位。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 训练损失震荡严重 | 学习率太大或batch太小 | 看损失曲线是否上下跳动 | 降低lr0,增大batch |
| 验证损失早早上扬 | 过拟合 | 对比训练和验证损失 | 加数据增强,加weight_decay |
| 某类别AP极低 | 样本太少或标注质量差 | 看混淆矩阵和该类样本 | 过采样,检查标注 |
| 推理结果框位置偏移 | 输入预处理不一致 | 对比训练和推理的预处理 | 统一归一化和通道顺序 |
| RKNN转换失败 | 算子不支持 | 看转换日志 | 替换不支持的算子 |
| 量化后精度掉太多 | INT8量化太激进 | 对比量化前后mAP | 混合量化,敏感层用FP16 |
| 小目标检测效果差 | 输入分辨率太低 | 看小目标的AP | 提高imgsz,加马赛克增强 |
| 模型在边缘设备上跑不动 | 模型太大 | 看参数量和FLOPs | 换更小的模型,剪枝 |
避坑技巧:训练前一定要做一次小规模过拟合测试。拿10张图,训练100轮,看模型能不能把损失降到接近0。如果降不下去,说明模型结构或者数据管道有问题,别急着上全量数据训练,那样只是浪费时间。
还有一个经验:不要迷信SOTA。我试过一些最新的检测模型,理论上指标更高,但在垃圾数据集上表现并不比YOLOv8好多少,而且部署更麻烦。YOLOv8的生态成熟、文档齐全、社区活跃,遇到问题容易找到答案。对于实际项目来说,可落地性比理论指标更重要。
最后分享一个我在实际部署中总结的小技巧:在摄像头端加一个简单的运动检测,只有当画面有变化时才触发推理,静止画面直接跳过。这样能大幅降低NPU的负载,延长设备寿命。实测下来,社区垃圾桶场景下,这个策略能减少大约60%的无效推理。