
垃圾分类检测算是我经手过的最“实在”的视觉项目之一。为什么这么说因为从数据整理到模型部署每一个环节都会踩到坑而且每个坑都能直接影响到最终识别效果。很多朋友拿到这个题目第一反应就是“用卷积神经网络跑一下”但实际做下来你会发现真正决定项目上限的往往是数据质量、类别平衡、以及部署时的推理效率这些细节。这篇博文我就把整个完整流程拆开揉碎从方案选型到训练调试再到落地部署把核心步骤和容易翻车的地方都列出来希望给正在做类似项目的同学一个能直接参考的路线图。1. 整体设计思路与方案选型1.1 先搞清楚识别对象是什么垃圾分类检测系统这个名字听着简单但里面其实有两个完全不同的技术方向。一个方向是“分类”——你给它一张垃圾的特写照片它告诉你这是塑料瓶还是易拉罐另一个方向是“检测”——你给它一张街边垃圾桶或者一堆垃圾的场景图它得先从画面里把每个垃圾目标框出来再判断每个框里是什么类别。这两个方向对应的技术路线完全不一样。分类任务用图像分类网络就行比如ResNet、MobileNet这类检测任务则需要用目标检测框架比如YOLO、SSD或者Faster R-CNN。大多数教程默认讲分类但实际场景里“一堆垃圾混在一起”才是常态所以检测才是更贴近真实需求的方向。不过检测模型的数据标注成本比分类高得多很多人做到一半就卡在标注上了。1.2 我的技术选型逻辑结合成本和效果我当时给某智能硬件团队做的模拟项目X采用的是“分类为主、检测为辅”的折中方案先用一个轻量级分类模型对单类垃圾图片做快速判断再在需要定位的场景中使用检测模型做辅助。这个策略的优势在于——分类模型对设备要求低可以跑在树莓派、Jetson Nano这类边缘设备上而检测模型只在小范围触发不会因为持续运行导致算力吃紧。模型方面主力分类网络我选了MobileNetV3-Large一个关键原因是它支持输入尺寸缩放。垃圾分类的图片来源很多样有手机拍摄的、有监控摄像头截帧的分辨率参差不齐MobileNetV3的输入尺寸可以灵活调整如224×224或192×192在识别精度和推理速度之间能找到一个比较舒服的平衡点。检测部分用的则是轻量化的YOLOv5s训练成本和部署难度都比较友好。2. 数据准备与预处理打好地基2.1 数据集从哪来、怎么整理垃圾分类项目最大的门槛就是数据。公开数据集确实有但直接用效果往往不理想。原因很简单——公开数据集的图片大多很“干净”背景单一、光照均匀、物品完整而真实场景里的垃圾是皱巴巴的、脏兮兮的、各种角度都有还可能被遮挡。模型在公开集上跑得很漂亮一换场景就原形毕露这个现象我做项目第一次测试时就踩中了。我的做法是建立一个多层次数据池第一层公开数据集的常见类别图片作为基础训练集第二层自己用手机拍摄的真实垃圾照片覆盖日常可见的各种形态第三层从视频监控截帧和网上找的街拍场景图补充复杂背景整理数据时按可回收物、有害垃圾、厨余垃圾、其他垃圾四大类归拢。这里有一个容易忽略的细节每个大类下还要细分具体物品比如可回收物下面有塑料瓶、玻璃瓶、易拉罐、纸箱等子类。先训练子类模型再映射到根类别比直接训四分类要准确得多因为子类之间的特征区分度更高。2.2 数据清洗和增强的关键细节数据整理阶段最容易翻车的是脏数据。图片模糊、曝光过度、物品占比过小、标签贴错这四类是重灾区。我当时写了一个简单的预处理脚本自动剔除分辨率过低的图片再人肉抽检一遍标签是否对得上。数据增强方面除了常规的随机旋转、翻转、色彩抖动之外我额外用了两个针对性策略一是随机遮挡。给训练图片随机加上一些黑色方块模拟垃圾被部分遮挡的情况。这个操作能显著提高模型在真实场景下的鲁棒性尤其是当多个垃圾堆叠时。二是mixup增强。按一定比例混合两张不同类别的图片。这种方法能减少模型对特定背景的过拟合让模型更关注物体本身的特征。实测下来加入mixup之后模型在验证集上的表现不一定会提升太多但在真实场景图上的泛化能力会明显变好。提示数据和标签的质量直接决定模型上限。哪怕模型结构再先进喂进去一堆错误标签训出来的结果也只能是错的。3. 模型搭建与训练实操从零到能用的全流程3.1 环境配置与代码骨架训练环境我用的是一台带有单张GPU的机器软件栈是PyTorch框架。PyTorch在快速迭代方面确实比TensorFlow顺手不少尤其是调试阶段报错信息更直观。先给出一个最简训练骨架分类模型部分import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models # 数据预处理 data_transforms transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 加载数据集 train_dataset datasets.ImageFolder(data/train, transformdata_transforms) val_dataset datasets.ImageFolder(data/val, transformdata_transforms) train_loader torch.utils.data.DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader torch.utils.data.DataLoader(val_dataset, batch_size64, shuffleFalse) # 加载预训练模型 model models.mobilenet_v3_large(pretrainedTrue) # 替换最后的分类层为4分类 model.classifier[3] nn.Linear(model.classifier[3].in_features, 4) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 定义损失函数和优化器 criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-4)很多人会问为什么要用预训练权重我的回答是除非你的数据集有几万张否则不要从零开始训。ImageNet上预训练出来的模型已经学会了纹理、边缘、形状这些底层特征你只是在它的基础上做迁移学习收敛速度和最终精度都会好很多。3.2 训练策略与参数调优垃圾分类这个任务有一个特殊性同一个物品不同状态下的视觉差异极大。一个完整的塑料瓶和一个被踩扁的塑料瓶在模型看来几乎就是两个不同的物体。所以训练参数设置上学习率不能太大否则模型容易在局部震荡而学不到细微特征。我的经验是分两阶段训练第一阶段冻结backbone只训练分类头学习率设在1e-3跑5-8个epoch第二阶段解冻整个网络将学习率降到1e-4到5e-5之间对整个网络进行微调epoch数量的判断标准不是固定值而是看验证集loss是否还在下降。我一般是每3个epoch保存一次权重等训完之后挑验证集上表现最好的那个模型而不是用最后一个epoch的结果。batch size的选择上如果显存足够尽量用64以上。小的batch size带来的梯度噪声大在细粒度分类任务上会让收敛变慢。当然了如果你用的是CPU训练那就另说了——建议直接上云GPU或者用Colab不然一个4分类模型训十几个小时确实熬人。3.3 检测模型的训练要点YOLOv5s的训练相对复杂一些因为需要处理标注框。标注工具我用的是LabelImg导出为YOLO格式。这里有一个容易踩的坑标注框的坐标需要归一化到0到1之间并且是相对于图片宽高的比例。YOLO训练过程中的关键参数# YOLOv5训练指令示例 python train.py --data garbage.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch-size 16img参数决定了训练时缩放到的分辨率。640是默认推荐值太大比如1280会显著增加训练时间但精度提升有限太小比如320则对小目标很不友好。batch size 16在显存有限的机器上是一个安全选择。训练时如果发现loss前期不下降优先检查标注文件是否正确而不是急着调参。4. 模型评估与优化纸上指标和真实场景要分开看4.1 评估指标解读模型训完之后不能只看准确率一个指标。垃圾分类场景存在明显的类别不平衡问题——比如“其他垃圾”样本数量远远多于“有害垃圾”如果只看准确率模型只要把所有东西都判成其他垃圾就能获得很高的分数但是这个模型毫无实用性。所以必须看每个类别的精确率、召回率和F1分数。我当时用sklearn的classification_report直接输出各类别指标重点盯着两个容易混淆的类别看可回收物和厨余垃圾。这两个大类在视觉上经常有交集比如一个沾了食物的塑料盒你说它属于可回收还是厨余人都会犹豫模型更会出错。4.2 混淆分析和错误样本复盘训练完之后我建议不要急着部署。把验证集里预测错误的图片全部打出来人肉看一下模型到底错在什么地方。这一步很多人嫌麻烦直接跳过但恰恰是这一步最能发现数据的短板。我当时做这个复盘时发现了一个规律模型对完好的、干净的单品垃圾识别得很准但对多件垃圾叠在一起的图片识别率骤降。这个发现直接改变了我的数据策略——从公开集里挑了一百多张多目标场景图单独做了一轮标注和微调。后面再测压叠场景的识别率提升明显。4.3 模型轻量化和推理加速部署阶段要考虑模型体积和推理速度。MobileNetV3-Large本身已经挺轻了但还可以压缩。PyTorch提供了量化接口可以把模型从FP32压缩到INT8体积缩小近4倍推理速度翻倍代价是精度略微下降——在垃圾分类这种任务上1%-2%的精度损失完全能接受。# PyTorch量化示例 import torch.quantization as quant model.qconfig quant.get_default_qconfig(fbgemm) quant.prepare(model, inplaceTrue) # 在验证集上跑一轮以统计激活值范围 calibrate(model, calib_loader) quant.convert(model, inplaceTrue) # 导出为ONNX格式 dummy_input torch.randn(1, 3, 224, 224).cpu() torch.onnx.export(model, dummy_input, garbage_model.onnx, opset_version11)经验之谈是如果模型在边缘设备上跑建议导出ONNX再转到TensorRT或者OpenVINO这比纯PyTorch推理快得多。我之前在Jetson Nano上直接跑PyTorch模型一帧要300多毫秒转成TensorRT之后降到80多毫秒完全够实时了。5. 实际部署与演示把模型变成能用的东西5.1 命令行推理工具的实现模型做得再好没有落地界面就只能算个实验品。我最早做的是一个Python命令行工具从摄像头或者图片文件读入实时输出识别结果。推理部分的核心逻辑很简单import torch from PIL import Image from torchvision import transforms def predict_image(model, image_path, class_names, device): img Image.open(image_path).convert(RGB) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) img_tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): outputs model(img_tensor) _, predicted torch.max(outputs, 1) probs torch.nn.functional.softmax(outputs[0], dim0) confidence, _ torch.max(probs, dim0) return class_names[predicted.item()], confidence.item()注意最后一个细节softmax后取的概率是置信度一般在垃圾图片比较标准的时候能到0.9以上但遇到模糊图片就会掉到0.5、0.6左右。这个置信度信息非常有用应该作为输出的一部分呈现方便用户判断结果是否可靠。5.2 Web端快速展示选项如果要给非技术背景的人演示命令行就不够直观了。我后来用Flask写了一个极简Web服务允许用户上传图片前端显示识别结果和置信度。整个服务代码量不大核心就是一个上传接口加一个模型推理封装from flask import Flask, request, jsonify import torch from PIL import Image import io app Flask(__name__) app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: no file uploaded}) file request.files[file] img_bytes file.read() # 转换为模型输入格式 image Image.open(io.BytesIO(img_bytes)).convert(RGB) result, confidence predict_image(model, image, class_names, device) return jsonify({category: result, confidence: confidence}) if __name__ __main__: app.run(host0.0.0.0, port5000)这种方式在局域网内演示的效果很直观手机拍照上传到电脑端几秒就能看到识别结果。而且因为核心是一个HTTP接口后续如果要接硬件设备或者把它集成到更大的平台也只需要在这个接口上做文章。5.3 边缘设备部署的经验最后聊下在树莓派或者开发板上部署的注意事项。这类设备的系统环境不像PC上那么完整装上PyTorch可能就会遇到依赖缺失的问题。有几个坑一定要说首先建议在设备上用一个独立的Python虚拟环境不要直接装到系统里不然依赖一冲突整个设备的环境就乱了。其次由于这些设备的ARM架构问题有些预编译轮子找不到需要自己编译比较费时间。最省事的替代方案是使用英伟达系开发板自带的镜像系统很多常见的库都是预装好的省去不少麻烦。还有一个个人习惯在边缘设备上部署时我会把整个推理流程封装成服务而不是一个单独脚本。因为脚本一旦断掉重启很麻烦但如果是服务可以有进程守护机制自动恢复稳定很多。6. 常见问题与排查技巧实录6.1 模型训练loss不下降怎么办这个问题我看到被问得最多。首先确认数据预处理环节有没有问题特别是Normalize的均值方差是否和训练时一致。如果数据增强太过激进模型也会学不到东西尤其是mixup比例过大时模型看到的全是“四不像”图片会严重影响收敛。排除这些之后再考虑学习率设置。如果还没效果就调低学习率重跑几个epoch看看。我当时遇到过一次loss完全不下降的情况排查下来发现是数据增强中的随机遮挡概率设得太高导致大部分图片的主体都看不到了模型无从学起。6.2 模型对真实场景图片识别率低怎么办这种情况九成是训练集和真实场景分布不一致造成的。解决思路就一个收集更多贴近真实场景的数据来微调。我当时从网上找了大量关于垃圾桶、垃圾站的特征素材也自己拍了一堆专门补充了一轮训练集识别率提升非常明显。还有一种情况是检测模型能框出目标但分类错误那大概率是训练时正负样本比例失衡。解决办法是增加困难负样本的挖掘比例换句话说把容易混淆的类别图片多放一些进去训练。6.3 不同设备上推理速度差距为什么这么大训练机上的GPU推理和部署机上的CPU推理完全是两个量级。如果发现CPU推理速度慢得离谱先别急着优化代码检查一下模型是否绑定了GPU和CUDA的特定算子。我用ONNX导出再转换运行引擎后速度和兼容性都好了很多。6.4 常见问题速查表现象可能原因解决方向训练loss不降数据增强过度、学习率过大/过小检查数据管线调整学习率训练收敛但验证集准确率低过拟合、训练集与验证集分布不一致增加数据量、加正则化、调整验证集真实场景识别率差训练集过于“干净”补充真实场景样本做微调部署端推理慢模型未量化/未转引擎做INT8量化转入推理引擎边缘设备上无法加载模型内存不足、模型过大换更轻量模型如MobileNetV3-Small或做剪枝6.5 几个容易忽略的细节分类类别名称别用中文直接作为文件夹名容易在某些环境上出编码问题用数字编号或者拼音最省心。每次训练前固定随机种子保证结果可复现。这个在调试阶段特别重要不然排bug的时候你根本不知道是这个改动起了效果还是随机性导致的。备份每一个版本的权重文件。尤其是做数据增强、微调实验的时候旧权重的效果可能比新权重还好。每次要覆盖前先确认新模型真的更好不然想回退都没文件用。这一点我是吃过亏的后来就养成了备份的习惯。最后再分享几个心得做垃圾分类检测这个项目最大的体会其实不是模型架构多复杂而是数据工程的投入占比远比想象中高。我在这个项目上大概七成的时间花在了数据清洗、标注和增强上真正调模型的时间不到三成。很多人一开始冲劲十足地跑代码跑到中期发现效果不好回头一看是数据没跟上然后又重新回来整理数据——我一开始也是这么走了弯路。另外就是部署意识要提前。很多人在训练阶段完全不考虑推理环境和性能等到模型训好了才发现部署端跑不动只能从头选轻量化模型前面的训练全白费了。正确的节奏应该是一开始就想好最终要部署在哪里然后反推模型的选型和优化方案。如果后续要把这个系统做得更完善我觉得方向上可以考虑视频流实时检测就是对接摄像头的RTSP流在帧级别连续识别那才算真正达到一个可商用的状态。不过那就是另外一个工作量了先把单张图片的流程跑通打牢地基后面再多的事情也不怕。