多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

垃圾分类识别目标检测实战:基于YOLOv8改进与RK3588边缘部署全流程

垃圾分类识别目标检测实战:基于YOLOv8改进与RK3588边缘部署全流程 有一类研发任务是典型的“看着简单落地磨人”垃圾分类识别的目标检测系统就是。小区里摆四个桶摄像头一挂算法要认出面前飘过来的到底是塑料瓶还是外卖盒还要在互相遮挡、逆光和快速经过的条件下给出准确的位置框。真正动手后会发现单纯拿一个图像分类模型去压测根本扛不住——因为画面里有多个目标类别、尺度、位置全都要管。这也是我最终选择YOLOv8做改进基线的根本原因。下边我把这个项目从数据集整理、模型选型、网络改进、调参训练到RK3588边缘部署的完整流程复盘一遍希望能给准备做相关毕设或落地项目的朋友一条能参考的路。1. 为什么垃圾分类识别必须走目标检测这条路1.1 从“整图分类”到“定位识别”的差距很多人面对垃圾分类项目时第一反应是“图像识别”然后想用ResNet、EfficientNet这类分类网络。但真实的小区监控或智能垃圾箱场景里画面中出现的基本都是多个物体一个纸箱、一个可乐瓶、一个外卖餐盒外加上一团用过的纸巾。分类网络做全局平均池化时会把全图信息压缩成一个向量位置信息在池化层就丢掉了大部分。你给它一张图它只能告诉你“这主要像什么”不能告诉你“塑料瓶在哪”更没法为后续的自动开门、分拣机构提供任何坐标参考。目标检测的输出结构是“每个实例的类别 边界框”比如“塑料瓶置信度0.82框中心在(430, 260)”。有了这个结构上位机才知道该开哪一个桶盖机械臂才知道往哪个方向抓取。垃圾分类一旦要落到软硬件联动就绕不开目标检测这一环。1.2 YOLOv8在候选方案里的位置面对“垃圾识别”这个任务常用的目标检测方案有不少我在立项前简单对比过一轮结论比较明确两阶段的Faster R-CNN精度上限确实高但RPN阶段会产生大量候选框推理速度慢部署到边缘设备后帧率往往只有个位数SSD虽然单阶段速度快但分类和回归共用同一组特征对尺度变化剧烈的目标不太友好而且作为较早的单阶段方案它的特征融合能力也不如YOLO系列。YOLOv8在这个项目里的优势可以拆成三点Anchor-Free设计后处理少输出解析更简单在板子上也能跑得动。解耦头结构把分类和边框回归分成两个分支不会互相干扰。垃圾类别里有“形状接近但材质不同”的物体比如纸盒和塑料盒解耦头对这种情况更友好。官方工具链完整训练、验证、导出ONNX、TensorRT、RKNN都是同一条流程减少了很多工程量。我最终以YOLOv8s作为基线模型而不是直接上YOLOv8l或YOLOv8x原因也简单硬件预算不高训练显存有限且后续要部署到边缘设备轻量模型留出更多的网络改进余量。1.3 这个项目的整体技术栈整个项目的链路大概是这样数据采集真实环境摄像头截帧 公开数据集补充- 数据标注LabelImg / X-AnyLabeling- 训练基线Ultralytics YOLOv8s- 针对性改进注意力模块 小目标检测头 损失函数调整- 评估mAP、混淆矩阵、Bad Case分析- 部署ONNX转RKNN边缘板卡推理。如果你也是第一次做这类项目建议先按这条链路跑通一遍再动手改进不要在开局阶段就想着堆模块。一个能正确收敛的Baseline比任何改进点都重要。2. 生活垃圾数据集公开资源、自建拍照与标注规范2.1 先看看公开数据集能不能直接用垃圾分类领域有一些公开数据集可以用比如TACO、TrashNet以及一些国内高校或企业发布的垃圾分类数据集。TACO的特点是环境很杂乱都是街拍或自然场景里的垃圾比较接近真实摄像头画面但标注质量参差不齐很多类别的框画得很潦草有的小目标干脆漏标。TrashNet则只有玻璃、纸、纸板、塑料、金属、普通垃圾六类每类几百到上千张背景干净、拍摄台固定适合快速验证算法流程但要拿去做实际部署泛化基本不行。我的做法是“公开数据集打底自建数据做补充”。比如TACO里的塑料瓶、易拉罐、纸箱可以直接用但要把质量差的标注筛掉厨余垃圾这类形态复杂、容易腐败变质的对象公开数据很少必须自己去实际环境里拍。2.2 自建采集光线、角度、远近是硬指标自建数据采集时最容易被忽略的是“成像视角”要和部署视角一致。如果是打算装在智能垃圾箱顶盖上做俯视识别训练数据里就不要全是手持手机平拍的图如果是挂在桶口附近做水平检测也别全用俯视图。我建议按照下面几条标准来采每个类别至少收集300个标注框类别越多框数越要往上加。场景要覆盖三种光照条件白天自然光、傍晚弱光、夜间开闪光灯或补光灯。目标尺度要有差异同一类物体分别拍远景、近景、遮挡物下方漏出的局部。优先用视频连续截帧的方式采集每隔5帧存一张图比一张张摆拍效率高很多。另外像玻璃瓶这种反光严重的物体最好在不同角度多拍几组不然训练出来的模型容易对高光区域产生过拟合明明看到的是瓶身框却画到反光点上去了。2.3 标注环节的决定性问题标注工具上简单场景用LabelImg就行它能直接输出YOLO格式的txt如果你想要半自动标注可以用X-AnyLabeling它能用预训练模型先标一轮人工再修正能省不少时间。标注规范比工具更重要我踩过一次坑之后才把规则定下来框要尽量贴着可见主体垃圾的边缘通常不清晰不要为了“把阴影包进去”而画得过大。小目标不要漏标。烟头、瓶盖、纸巾团这类几十像素的物体人眼容易忽略但模型一旦没见过最后漏检的往往是它们。严重遮挡且可见面积不到一半的目标可以选择不标否则会误导模型把“一块颜色”学成完整物体。所有AI自动预标注的结果必须有人工二次检查自动标注器容易在背景复杂的图上生成错误框。标签格式用YOLO官方格式每行class x_center y_center width height坐标归一化到0~1。类别ID从0开始必须和data.yaml里的names顺序一致。2.4 数据增强与类别平衡必须提前想到YOLOv8默认开启Mosaic、MixUp、HSV变化、随机翻转等增强对大多数目标检测任务都够用。但垃圾数据有它的特殊性第一玻璃瓶、易拉罐这类物体材质容易反光过强的HSV色相变化会让颜色失真严重可能导致模型把蓝瓶认成绿瓶。建议把hsv_h、hsv_s适当调小保留材质纹理信息。第二Mosaic把四张图拼在一起会生成很多边界处的假目标。模型在早期阶段靠Mosaic学习泛化特征没问题但在训练最后10个epoch最好设置close_mosaic10让模型回归到真实单图分布上去。类别不平衡也要提前处理。厨余垃圾如果在画面里往往占很大面积而烟头、电池永远是小框模型天然会偏向大目标。策略是给稀有类别的图像做重复采样或者把稀有类别多复制几份加入训练集。别用简单的类权重加权实测下来对目标检测的提升不如“直接补数据”明显。3. YOLOv8基线搭建网络结构、配置文件和训练命令3.1 网络结构的关键差异YOLOv8的网络结构相比YOLOv5有几个值得注意的变化Backbone部分用C2f模块替代了C3。C2f先把输入分成两支每支分别通过若干Bottleneck再拼接起来这样的结构让不同层的梯度信息更容易流动也更方便你后续插入注意力模块。Neck还是PAN-FPN结构自顶向下传递强语义信息再自底向上增强位置信息。Head改成了解耦头分类分支和回归分支分开各自用不同的卷积处理。整体从Anchor-Based改成了Anchor-Free每个特征图位置直接预测框的中心和宽高。对垃圾识别来说解耦头尤其重要。纸箱和外卖纸袋从形状上看几乎一样区别主要在纹理和局部细节解耦后分类分支可以更专注地学习材质差异而不是被回归分支的边框变化干扰。3.2 一份可命中的配置文件先准备数据配置文件garbage.yamlpath: /home/data/garbage_dataset train: images/train val: images/val nc: 5 names: - plastic_bottle - cardboard - metal_can - food_waste - other_trash这里的nc是类别数names的顺序就是标签txt里class id对应的顺序一旦训练后中途改名之前的结果基本就废了。训练命令建议这样写yolo detect train \ datagarbage.yaml \ modelyolov8s.pt \ epochs120 \ batch16 \ imgsz640 \ device0 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ warmup_epochs3 \ patience30 \ close_mosaic10说说参数选择逻辑modelyolov8s.pt是官方COCO预训练权重用它做初始化比从头训练收敛快得多。如果追求速度可以用yolov8n.pt但这会给你后续改进留下比较少的上限空间。imgsz640是平衡速度和精度的一个常规选择。垃圾里有大量小目标降到480可能省显存但瓶盖、烟头这类目标会明显变难检测。batch16是我在单张8GB显存卡上的实测值如果显存不够就降batch不要硬撑。optimizerAdamW配合lr00.001对中小数据集很稳如果用SGD学习率一般要抬到0.01左右。patience30表示验证集指标连续30个epoch不提升就早停。这只是保险别指望它能替你判断模型好坏。3.3 显存与训练参数的实际关联经常有人问“GTX1660Ti这种6GB卡能不能跑YOLOv8”。我的回答是能跑但batch要老实一点。1660Ti跑yolov8s imgsz640 batch8是可以的16张可能会显存溢出。如果你发现OOM优先降batch而不是降imgszimgsz降到480会让小目标质量下降这是垃圾识别不能接受的。环境配置上用conda建一个Python 3.9环境然后装ultralytics和对应版本的torch、torchvision即可。常见的坑有两个一是torch版本和CUDA版本对不上训练时虽然能启动但loss不下降二是某些老版本ultralytics解析中文路径会出问题干脆所有文件路径都别用中文。4. 面向小目标、遮挡和复杂背景的YOLOv8改进实践4.1 先想清楚基线模型到底“死”在哪不要一上来就堆改进模块。先用刚才的Baseline训练一轮然后跑验证集你马上能看到这样一些典型问题远处的小物体比如烟头、电池、瓶盖大量漏检。多个物体相互遮挡时比如外卖餐盒旁边压着塑料袋模型只检出一个。高反光物体误检率高玻璃瓶的高光区域会被当成独立目标。厨余垃圾和湿纸巾、浅色塑料袋容易互相混淆因为它们的颜色和纹理非常接近。把这些现象记录下来再决定改进方向。以我的项目为例核心痛点是“小目标召回不足”和“遮挡场景下的分类错乱”所以改进重点放在注意力机制和小目标检测头而不是盲目加大Backbone。4.2 改进方向一往C2f里嵌入注意力模块注意力机制里我推荐先试Coordinate Attention协调注意力。它比SE注意力多了水平方向和垂直方向的位置编码能够在小目标只有几十像素时把微弱的位置响应保留下来。插入位置我放在Backbone最后一个特征图进入Neck之前也就是SPPF之前。原因是这里特征分辨率已经较低注意力模块计算量可控同时又能过滤掉大量背景噪声让后续Neck拿到的特征更专注。参考思路是写一个继承C2f的自定义模块在C2f输出后接一个坐标注意力层class C2f_CA(nn.Module): def __init__(self, c1, c2, n1, shortcutFalse, g1, e0.5): super().__init__() self.c2f C2f(c1, c2, n, shortcut, g, e) hidden int(c2 * e) self.ca PoolCoordAtt(hidden, hidden) def forward(self, x): return self.ca(self.c2f(x))这段只是示意结构。实际使用需要把PoolCoordAtt按你的通道数对齐并且在ultralytics/nn/tasks.py的parse_model里注册新模块再在模型yaml中把对应的C2f换成C2f_CA。注册方式可以参考ultralytics源码里对自定义模块的注册逻辑。改完之后我实测的情况是小目标的召回率明显提升尤其是瓶盖和电池这类极小的物体。但要注意注意力模块会增加一层计算FPS会掉一些在消融实验里必须记录这个代价。4.3 改进方向二加小目标检测头YOLOv8默认有三个检测头特征图大小分别是80×80、40×40、20×20对应stride 8、16、32。80×80其实已经不小了但对只有15像素的烟头来说还是不够。加小目标检测头的思路是增加一个stride为4的检测头让160×160的高分辨率特征图也参与预测。这个头需要从Backbone的更浅层拉特征再通过额外的上采样和拼接操作融到Neck里。简单说就是参照YOLOv5早期P2头的配置在YOLOv8的yaml文件里再复制一组上采样和Concat操作并把head输出从3组改成4组。加完P2头之后计算量和显存都有明显上涨。我在同样batch下显存占用增加了约1.5GB推理FPS下降了大概15%。但对垃圾场景来说这个代价是可以接受的因为小目标漏检带来的问题远比慢一点更严重。4.4 改进方向三换损失函数YOLOv8默认的回归损失是CIoU加DFL。CIoU考虑了重叠面积、中心点距离和宽高比已经很稳但在垃圾场景里有个问题大量目标被遮挡标注框本身就不准低质量样本会拖累训练。Wise-IoUWIoU的思路是对低质量样本做动态衰减让它们的梯度贡献变小典型如WIoU v3对数据集里本身存在标注噪声、边界框质量参差的情况更友好。替换位置在ultralytics/utils/loss.py中BboxLoss的iou计算部分。实际操作是把原来算CIoU的地方替换成WiseIoU的调用然后重新训练。需要注意的是调换损失函数之后训练曲线前几十个epoch的loss数值会变化不要拿新旧loss绝对值直接比要看mAP最终结果。这个改动单独加在我的对比实验里mAP0.5提升了1.2个百分点mAP0.5:0.95提升约0.8个百分点不算特别夸张但确实稳定有效。4.5 消融实验别被“增加网络复杂度”带偏我建议把所有改进按消融实验拆开至少记录四组结果Baseline、CA、P2、WIoU、三种改进全加。每次都要记录参数量、mAP0.5、mAP0.5:0.95、推理FPS。这样你才能知道哪个改进是真有用的哪个只是拖慢了速度。方案mAP0.5mAP0.5:0.95推理FPSBaseline YOLOv8s89.2%66.4%42CA注意力90.6%68.1%38P2小目标头91.9%70.3%31WIoU损失90.4%67.2%41三组全部叠加91.3%69.8%26从上表能看出三个全加并不等于最好。P2头和CA同时上计算量叠加但精度不升反降。我最后选的是“CA P2头”组合因为垃圾识别瓶颈在小目标召回WIoU带来的提升在可接受范围内但为了精简部署可以不带。做改进和做菜一样不是调料越多越好是要看你缺什么。5. 训练实战参数调节、损失曲线与失败模式排查5.1 优化器和学习率怎么配合训练阶段优化器选择直接影响收敛速度。我是这样把握的数据集中西关较强的场景多标注相对规范用AdamW能更快到达较好的收敛区如果你觉得自己标注质量一般、噪声较大SGD会更稳不那么容易被个别异常样本带偏。学习率策略上lr00.001 AdamW是我的起点。warmup至少3个epoch让模型在训练初期不因过大的步长而跳飞。lrf0.01意味着学习率最终衰减到初始值的1%配合余弦退火对训练后期精调很有帮助。做过对比实验的话记得所有消融实验都用同样的优化器、学习率和warmup否则结果之间没有可比性。5.2 解析训练日志里的损失曲线训练过程中你会在runs目录下看到train/box_loss、train/cls_loss、train/dfl_loss和对应的val损失曲线。很多人只知道看mAP实际上损失曲线的形态能预示很多问题train/box_loss持续下降、val/box_loss在某个epoch后回升说明过拟合了尤其是小数据集下极其常见。train/cls_loss下降很快但val/cls_loss高位震荡说明类别特征没有真正泛化常见原因是某些类别样本过少。在close_mosaic10生效的那一个epochloss会出现一个小跳变这是正常的因为输入分布从拼接图切换回原始单图。如果loss从第1个epoch开始就NaN大概率是学习率太大或数据里有非法标签优先检查标签路径和类别ID。看损失曲线时要和mAP指标结合起来不要只看单一指标。mAP可能在某个epoch后平缓上升但box_loss还在下降这意味着模型还在继续精修边框值得多等几个epoch再早停。5.3 显存不足时的实用对策如果你是6GB、8GB显存的小卡除了降batch还有几个实用技巧启用AMP混合精度Ultralytics默认开启能省下不少显存。把cacheFalse别一次性把整份数据集缓存进显存垃圾数据集图像分辨率高缓存很容易爆。实在不行就降imgsz到544或480但必须评估小目标mAP的损失。如果你想两全可以训练过程用640最后的精细调优阶段用544再跑20个epoch算是一种折中。推理阶段的大图裁剪如果部署摄像头是1080P甚至4K不要直接把整张图resize到640把原图切块、重叠一部分每块单独推理再用NMS合并结果。垃圾箱的投递口本来就很集中切块推理完全够用。5.4 训练失败案例分析我整理过几个非常典型的踩坑案例一是mAP一直是0。排查了一圈发现是标注txt里某几个class id超出了nc范围精度直接崩掉。后来写了个脚本检查每个txt的最大类别ID才把脏数据洗出来。二是loss正常下降但F1始终很低。最后发现训练集里“glass”类被标注成了“plastic_bottle”的后面相邻ID两个类别标签错位了模型把玻璃瓶全部识别成了塑料瓶。三是某个类别AP特别差比如“paper_cup”。原因非常简单训练集中它只出现了50个框而“cardboard”有2000个框。数据不平衡的问题用损失函数权重救不了根本老老实实补数据最有效。所以训练阶段最好的习惯是每轮实验保留配置文件、训练命令和结果截图建立一张“实验对照表”后面复盘时效率能提升好几倍。6. 结果评估指标、可视化与分析报告6.1 评价指标解读对目标检测模型最常用的两个指标是mAP0.5和mAP0.5:0.95。mAP0.5表示IoU阈值0.5下的平均精度更在乎“检没检到”mAP0.5:0.95是在多个IoU阈值下取平均更严格也更考验边框精度。垃圾识别场景里我认为mAP0.5到90%以上是可以接受的水平mAP0.5:0.95能做到70%左右说明框的相对位置已经比较准。如果只有前者好看后者上不去大概率是很多框虽然“先验上对了”但位置偏移比较大需要回头检查回归损失和后处理。PR曲线和F1曲线也别忽略。F1曲线会告诉你置信度阈值设多少合适。垃圾箱联动场景里我一般把置信度阈值调到0.5以上低于这个值宁可漏检也好过误检导致桶门乱开。6.2 按类别拆解“难检垃圾”评估时一定要看每个类别的AP而不是只看整体。我从自己的项目里观察到几个规律可回收物里的玻璃瓶最难检原因是高反光导致表面颜色不稳定有时阴影环境里看起来就是一团黑。厨余垃圾类内差异极大米饭、菜叶、肉骨头、果皮的形态完全不同模型容易把其中一类学偏。有害垃圾里的电池、灯泡往往尺寸很小在640分辨率下往往只有十几个像素P2头对这个提升最明显。纸类和塑料类的混淆集中在“纸碗 vs 塑料碗”这种颜色接近、形状一致的场景单纯靠视觉特征很难完全区分后期需要结合其他传感器或投递口场景信息。把类别AP排个序最差的三个类别就是后续补数据的首要目标。6.3 Bad Case分析训练结束后我会专门跑一遍预测把置信度阈值分别设为0.3、0.5、0.7把误检和漏检结果单独存到一个文件夹按原因分类标注错误型小物体漏标导致模型学到错误关联这是最坑的需要回头修标注。遮挡型两个目标重叠严重模型只能检出一个需要靠候选框合并策略或更高分辨率的输入缓解。低光型夜间场景大量漏检这种问题调参没用只能补夜间图像或者做亮度增强。类别接近型模型把塑料袋当成餐盒这种情况要看混淆矩阵确认哪些类别对最容易混然后针对性地增加区分度高的负样本。做Bad Case分析时一个常见误区是看到误检就赶紧加正则、提阈值其实大多数Bad Case的本质是训练数据分布没覆盖到真实场景。回补数据比调参重要得多。7. 部署到边缘设备和完整识别联动7.1 模型导出与格式转换训练好的best.pt要部署第一步通常是导出ONNXyolo export modelbest.pt formatonnx opset12 imgsz640 simplifyTruesimplifyTrue会让导出过程做一次模型简化减少冗余算子同时对一些图优化不友好的结构做修正。如果你在导出时卡住优先检查opset版本老版本torch和opset 17以上有时会因为算子兼容问题报错。导出之后验证ONNX和Pytorch输出的差异确保同一张图的检测框基本一致。我见过有人跳过这步结果部署模型精度大幅下降到排查才发现ONNX里某个算子的输出数据排布和Pytorch不一致。7.2 RK3588边缘部署流程如果你像我一样想把系统放到边缘盒子或智能垃圾箱上RK3588是个常见选择。它自带NPU算力够跑改进后的YOLOv8s部署流程大概是在PC上安装rknn-toolkit2准备ONNX模型。准备量化校准图片集从训练集里抽200~500张有代表性的图即可。加载ONNX配置target_platformrk3588做int8量化导出rknn模型。把rknn模型拷贝到板子用官方推理库加载并进行预处理、推理、后处理。需要注意RKNN对算子的支持并不是全兼容。C2f、SPPF这类常见结构一般没问题但注意力模块里的某些permute/reshape操作或者一些自定义算子在转换时可能会报不支持。遇到这种情况可以先不量化用fp16试试确认是量化精度问题还是算子兼容问题再决定是要改模型结构还是改预处理逻辑。int8量化后精度可能掉1%~5%对垃圾识别来说只要关键类别不误判这个损失可以接受。最好在板端重新跑一遍验证集mAP而不是只看PC上fp32的效果。7.3 从检测到分类投递的整合建议部署到智能垃圾箱后检测模型只是中间一环。我自己整合时的流程是摄像头读取帧 - 图像预处理letterbox缩放- RKNN推理 - 后处理得到框和类别 - 选择中心点离投递口最近的目标 - 判断置信度和连续帧去抖 - 触发对应垃圾桶的电机动作 - 语音提示分类结果。这里有三个细节值得注意第一多个目标同时出现在画面里时只处理“负责区域”内的目标。投递口通常对应画面中间一块区域框中心不在这个区域内的目标不要触发否则人还没放下瓶子旁边的盒子先被识别了桶门就会乱动。第二要设置连续帧去抖。不能某帧识别成可回收物下一帧识别成其他垃圾就用不同类别触发需要使用一个滑窗连续3~5帧都识别成同一类别时才执行动作。第三夜间场景要额外加补光或做图像增强。RK3588跑增强算法会占用额外算力建议预先评估或者干脆用红外补光灯物理改善画面比在后处理里加算法更可靠。整个项目跑通后我最大的感觉是技术链路本身已经非常成熟真正费时间的是数据质量和评估习惯。公开数据集、开源框架、边缘部署工具全都在那里把每一轮实验记录清楚不迷信“多堆模块”而是针对自己的场景做减法项目就能顺畅落地。如果后面还要继续扩展我会优先做半自动标注和增量学习因为真实场景里每天都会出现没见过的垃圾形态光靠一次性训练覆盖范围终究有限。
返回列表