
做垃圾图像分类这类项目我前后完整跟过三版从最初拿公开数据集跑个ResNet-50就发朋友圈到后来真正接到分拣线上跑、再到做成一个小程序能拍照识别中间踩的坑比模型层数还多。基于卷积神经网络的垃圾图像分类系统研究与实现表面上是“拿个CNN训练一个分类器”实际上从类别体系怎么定、数据怎么采、增强怎么加、模型怎么压、部署怎么提速每一步都有大量决策点而且这些决策点之间是强耦合的——数据增强加狠了模型泛化好但精度上不去量化做好了速度起飞但垃圾类别的细粒度特征直接糊掉。这篇文章我打算把这条链路从上到下拆一遍重点是讲清楚每个选择的“为什么”以及那些文档里不会写、只有真正跑过一遍才知道的细节。适合正在做课程设计、毕设、竞赛或者想把这套能力接到自己业务里的朋友哪怕你只会调API也能看懂因为我会把每一步的参数来源和计算过程都写出来。1. 项目整体设计与技术路线拆解1.1 先把需求边界划清楚别急着写模型我见过太多人拿到需求第一时间就是torchvision.models.resnet50(pretrainedTrue)然后花两周调参最后发现方向从一开始就偏了。垃圾图像分类这个题目需求边界不划清楚后面全白干。核心要问三个问题输入图像从哪来、类别粒度要多细、延迟和精度谁优先。输入来源决定了整个系统的复杂度天花板。如果是固定工位的工业相机光照稳定、背景单一、物体位置相对固定那模型要做的事情其实很轻甚至用轻量网络加传统颜色特征融合就能做到95%以上。但如果是手机随手拍背景可能是地板、桌面、手、塑料袋叠在一起光照有逆光有阴影那模型要扛的就是“领域随机性”数据增强和泛化能力的重要性远超网络深度。我之前做过一个对比实验同一个ResNet-18在固定工位数据集上top-1是96.2%直接拿去测手机拍的照片掉到71%左右这个落差就是场景差异带来的。类别粒度也得提前定死。四分类可回收、厨余、有害、其他是最常见的也是最好落地的但它有个天然缺陷——类内差异极大类间边界模糊。比如“可回收”里面有塑料瓶、纸箱、易拉罐、玻璃瓶视觉特征天差地别而“其他”和“可回收”之间的边界很多时候连人都要犹豫。所以我一般建议做两级体系一级粗分类保证可用性二级细分类用于后续分拣策略。细分类控制在20到60类之间比较舒服再往上单类样本量就很难保证了。场景类型输入特点推荐粒度精度目标延迟预算固定工位分拣线光照稳定、背景单一、物体居中二级细分类20-60类top-1 ≥ 95%单帧 ≤ 50ms移动端拍照上传背景杂乱、光照多变、有遮挡一级粗分类4-8类top-1 ≥ 85%端侧 ≤ 200ms巡检机器人视角倾斜、运动模糊一级粗分类检测top-1 ≥ 88%≤ 100ms教学演示/毕设数据集干净一级粗分类top-1 ≥ 90%不敏感延迟预算是很多人忽略的。训练阶段用多大的输入分辨率直接决定推理的FLOPs。224×224的输入ResNet-50大约是4.1 GFLOPs换成320×320就是8.4 GFLOPs左右翻了一倍。如果目标是端侧200ms以内那MobileNetV3或者ShuffleNetV2这类网络配上224分辨率才是合理组合。1.2 传统特征工程为什么在这里不够用有人会问一定要用卷积神经网络吗HOG加SVM、LBP纹理加颜色直方图这些方法在十年前就能跑垃圾识别为什么现在不用了。答案不是“CNN更先进”这种废话而是传统方法在三个地方会系统性失效。第一是形变和视角。一个被捏扁的塑料瓶和一个完整的塑料瓶HOG的梯度方向分布几乎完全不同但它们的语义类别是一样的。CNN靠卷积核的局部感受野加池化带来的平移不变性再叠加数据增强带来的形变鲁棒性能把这部分差异吃掉传统手工特征做不到。第二是脏污和遮挡。垃圾最大的特点就是“不干净”水瓶上有水珠、纸箱上有胶带、果皮上有氧化斑。颜色直方图对此极其敏感一个被咖啡渍污染的纸杯颜色特征会直接跑偏到“厨余”那一侧。CNN的多层抽象能学到纹理和结构的组合特征对局部污染有更强的容错。第三是类内多模态。像“可回收”这一类里塑料、纸张、金属、玻璃的底层视觉统计特性完全不同用一套手工特征描述它们本质上是在强行拟合一个多峰分布。卷积神经网络靠大量参数和数据驱动能自然地把这些子模态分开。我做过的对照实验里LBP加颜色矩加SVM的组合在干净数据集上能到82%左右但把脏污和遮挡样本混进去直接掉到63%。同一个数据集ResNet-18迁移学习能稳在93%以上。这个差距不是调参能补回来的是表征能力的代差。1.3 分层架构与各层职责整个系统我倾向于拆成四层每层职责单一方便单独迭代。数据层负责采集、清洗、标注、版本化。这一层最容易被当成“随便搞搞”但实际上它决定了模型精度上限。我的习惯是把每次采集的数据打上批次号用类似DVC或者至少用一个CSV清单管理记录文件名、类别、采集设备、采集时间、标注人、复核状态。后面模型精度异常时第一件事就是查数据版本对不对。训练层负责数据管道、模型定义、训练循环、评估。这一层要保证可复现随机种子、增强参数、超参都写进配置文件不要硬编码。我吃过一次亏同一个脚本跑两次结果差三个点最后发现是DataLoader的worker里用了没设种子的随机增强。推理层负责模型加载、预处理对齐、后处理、批量调度。这里有个隐蔽问题训练时的预处理必须和推理时严格一致。Normalize用的均值和方差、Resize的插值方式、是否中心裁剪任何一处不一致都会让精度掉一到两个点而且你很难定位。应用层就是接口和界面负责拍照、上传、返回结果和置信度。这一层我建议始终返回top-k置信度而不是单标签因为垃圾识别里第二第三候选往往更有参考价值尤其是“其他”这个兜底类别。2. 数据集构建与预处理的核心细节2.1 类别体系与标签规范怎么写类别体系不是拍脑袋列几个名字就完事。真正影响训练的是标签规范也就是“什么样的图该打什么标签”。我在项目里会写一份标注手册大概三四页核心内容包括类别定义、典型样本、易混淆样本的处理原则、不可判定样本的处置方式。举个例子一个沾了油污的外卖餐盒材质是塑料属于可回收但污染严重。到底打可回收还是其他不同标注员会给出不同答案这种不一致直接变成标签噪声。我的处理原则是污染程度超过表面积的50%就打“其他”否则按材质打。这类规则必须写死并且配图说明。另一个高频问题是“多标签图”。一张图里同时有塑料瓶和纸箱怎么办。我的做法是分类任务只保留主体目标主体判定标准是面积占比最大且视觉中心最近如果两者面积接近就裁切成两张图分别标注。这样处理的好处是模型学到的特征更干净坏处是丢失了“共存关系”但对分类任务来说这个损失可以接受。类别数量上我建议初期控制在6到10类跑通全流程后再扩展。四分类体系的实际落地效果通常比想象中差因为“其他”这个类会吸走太多边缘样本导致它变成一个巨大的垃圾桶类混淆矩阵里“其他”的召回率往往最低。2.2 采集、清洗与半自动标注的实操数据采集这块我总结的经验是“宁要多样不要量大”。三千张高质量、场景多样的图和三万张同一角度连拍的图前者训练出来的模型强得多。采集时要有意识地覆盖维度光照自然光、逆光、阴影、室内暖光、冷光、背景桌面、地面、袋子、堆叠、角度俯视、平视、斜45度、状态完整、压扁、撕裂、脏污、部分遮挡。一个实际可用的采集方法是“场景轮换”每次采集固定一个变量变化其他保持不变。比如这一批只用自然光加桌面背景下一批只用室内暖光加地面背景。这样能保证每个变量都有足够的样本覆盖而不是所有变量混在一起导致某些组合样本极少。清洗环节我一般做三件事。第一是去重用感知哈希pHash算相似度汉明距离小于等于5的视为重复只保留一张。这个步骤能砍掉10%到30%的连拍冗余。第二是去坏图模糊、过曝、目标占比过小的直接剔除。可以用拉普拉斯方差判断模糊阈值设在100左右比较合适。第三是类别平衡初筛统计各类数量超过均值三倍的类先抽样。标注环节全人工标三千张大概要两到三天效率太低。我的做法是半自动先用一个粗糙的初始模型哪怕只有70%精度跑一遍输出预测置信度置信度大于0.9的自动打标0.5到0.9的人工复核小于0.5的人工从零标。这套流程能把人工量压到原来的三分之一左右。注意自动打标的样本一定要抽检我抽检过一批发现模型对深色玻璃瓶系统性地误判为“其他”如果不抽检这个偏差会一路传下去。2.3 数据增强的边界在哪里数据增强是最容易用力过猛的地方。我在早期项目里把RandAugment、Mixup、CutMix、RandomErasing全开训练集精度死活上不去验证集也不涨排查了一周才发现是增强破坏了垃圾类别的判别性特征。垃圾识别有几类特征是不能乱动的。颜色是强特征尤其是塑料、纸张、玻璃的区别很大程度靠色相和饱和度区分。所以ColorJitter的强度要克制我的经验值是brightness0.2、contrast0.2、saturation0.2、hue0.05hue一定不能大超过0.1会让蓝色塑料瓶变成绿色直接语义漂移。纹理也是关键特征比如纸张的纤维感、金属的反光。高斯模糊、运动模糊这类增强要慎用或者只在小概率下使用。我的设置是随机模糊p0.1模糊核3×3。真正有效且安全的增强是几何类的RandomResizedCropscale设0.6到1.0ratio设0.75到1.33、水平翻转、小角度旋转正负15度以内。RandomResizedCrop的scale下限不要低于0.5否则经常把目标裁掉一半模型学到的是局部纹理而不是整体形状。Mixup和CutMix我用得比较谨慎。CutMix在垃圾数据上效果还行因为它保留了局部真实纹理alpha设1.0概率0.3左右。Mixup会让两张图叠加产生半透明效果这在现实里不存在容易让模型学到虚假特征我一般只在细分类任务上开很低概率粗分类直接关掉。还有一个特别管用的增强随机背景粘贴。把目标抠出来贴到随机背景上能极大缓解背景偏置。垃圾数据集最常见的偏置是“所有图都在白桌子上拍的”模型学到的是桌子而不是垃圾。这个增强实现起来不复杂用简单的阈值分割或者现成的分割模型出mask就行成本不高收益很大。注意增强参数一定要在验证集上验证不要照抄论文配置。别人的数据集是自然图像垃圾图像的分布特性完全不同。2.4 数据集划分与防泄漏划分这件事我见过最典型的错误是随机划分导致数据泄漏。同一批次连拍的照片光照、背景、甚至物体摆放都高度相似随机划分会把它们分散到训练集和验证集模型在验证集上表现虚高五到十个点一上真实场景就崩。正确的做法是按“采集批次”或“场景组”划分。我会给每条数据打一个group_id代表同一场景、同一批次、同一物体的多次拍摄然后按group划分保证同一group的所有样本只出现在一个集合里。sklearn里可以用GroupShuffleSplit实现成本很低效果立竿见影。比例上我一般用70/15/15但有个前提验证集和测试集的类别分布要和真实场景一致。如果真实场景里“其他”占60%那测试集也应该接近这个比例否则评估出来的指标没有参考意义。训练集可以适度重采样做平衡但验证测试集一定要保持真实分布。还有一个细节是难例集的单独维护。我会额外维护一个两百张左右的“难例集”专门放模型反复判错的样本包括遮挡严重、类别模糊、极端光照的。这个集合不参与训练只用来做版本间的回归测试。每次模型更新都跑一遍如果难例集精度下降即使总体精度上升也要警惕因为难例往往对应真实业务里最值钱的场景。3. 模型选型与训练调参实操3.1 骨干网络的横向对比与选型依据选骨干网络这件事我的判断标准是三条参数量、推理延迟、在目标数据上的实际精度。论文里的ImageNet精度参考价值有限因为垃圾图像和ImageNet的分布差异很大。网络参数量输入224时FLOPs端侧延迟(参考)适用场景MobileNetV3-Small2.5M0.06G~8ms端侧实时、精度要求中等MobileNetV3-Large5.4M0.22G~15ms端侧主力性价比高ShuffleNetV2 x1.02.3M0.15G~12ms极低算力设备ResNet-1811.7M1.8G~35ms服务端、精度优先ResNet-5025.6M4.1G~70ms离线批量、精度上限高EfficientNet-B05.3M0.39G~25ms精度与速度平衡我的实际经验是数据量小于一万张的时候MobileNetV3-Large加迁移学习基本就能到ResNet-50的95%精度速度快三倍以上性价比碾压。数据量超过五万张、类别超过三十类之后大模型的优势才逐渐显现。还有一个容易忽略的点是输入分辨率。垃圾识别的关键判别信息往往在细节上比如瓶盖的螺纹、包装上的印刷字样、材质的光泽。224分辨率下这些细节会糊掉我实测过把输入从224提到320细分类精度能涨两到三个点代价是延迟增加约一倍。如果是服务端部署这个交换很划算如果是端侧就得权衡。另一个思路是把分类和检测拆开。真实场景里图像往往包含多个物体和大量背景直接做整图分类模型会被背景干扰。我的做法是先上一个轻量检测模型把目标框出来裁切后再送分类网络。检测模型用YOLO系列的小模型就够了整体链路延迟增加不多但分类精度提升明显尤其是多物体场景。3.2 三段式迁移学习的训练节奏迁移学习不是简单地把预训练权重加载进来就完事冻结策略和分层学习率对结果影响很大。我用的是三段式。第一段冻结backbone只训练分类头。学习率设1e-3用Adam或者SGD加动量都行训练5到10个epoch。这一段的作用是让随机初始化的分类头先收敛到一个合理区域避免一开始就用大梯度把预训练特征冲垮。这一段结束后训练集精度通常能到70%以上。第二段解冻backbone的后半部分通常是最后两个stage用分层学习率backbone部分1e-4分类头1e-3。训练15到20个epoch。这一段是精度提升的主要阶段模型开始适应垃圾图像的特定特征。第三段全网络解冻统一用余弦退火的学习率初始值5e-5训练20到30个epoch。这一段提升幅度不大一般一到两个点但对最终精度有稳定作用。要注意这一段的过拟合风险最高早停和权重衰减都要开。学习率的设置有个经验公式可以参考线性缩放规则lr base_lr × batch_size / 256。base_lr取0.1SGD。比如batch_size是64那lr就是0.025。这个规则在迁移学习场景下要再除以10左右因为预训练权重已经很好了不需要大学习率。优化器我一般用SGD加动量0.9加Nesterov配合权重衰减1e-4。Adam收敛快但最终精度常常略低于调好的SGD尤其是细分类任务。如果追求快速出结果Adam配1e-3也能用。损失函数方面交叉熵是基础但我通常会加两个东西标签平滑0.1缓解标注噪声带来的过拟合如果类别不平衡严重加类别权重或者换Focal Lossgamma设1.5到2.0之间。垃圾数据集里“其他”类经常占比过高不加权的话模型会倾向于全predict成“其他”召回率看着不错但毫无用处。3.3 关键超参的计算过程与设置batch size的选择不是越大越好。我的判断依据是显存和BN层的行为。BatchNorm在batch size小于16时统计量不稳定训练会抖。所以下限是16常用32或64。如果能上到128以上学习率可以相应放大但要注意小数据集上大batch容易过拟合泛化会变差。输入尺寸的计算要结合目标物体的实际像素占比。假设原图是1920×1080目标物体在图中占宽度的三分之一那就是640像素宽。如果缩放到224物体只剩75像素细节全丢。这种情况下要么提高输入分辨率要么先裁切再缩放。我一般会统计数据集里目标框的尺寸分布确保缩放后最短边不低于96像素。训练轮数和早停策略要一起设计。我的设置是最大60个epoch监控验证集top-1精度patience设8也就是连续8个epoch不涨就停。同时保存最佳权重而不是最后一个epoch的权重这一条能稳定带来半个点左右的收益。混合精度训练AMP现在基本是标配能把显存占用降三到四成训练速度提升20%到40%精度损失几乎可以忽略。开启方式很简单from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for images, labels in train_loader: optimizer.zero_grad() with autocast(): outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()要注意的是AMP和某些自定义损失函数可能不兼容出现NaN的时候先关掉AMP定位问题别一上来就怀疑学习率。3.4 训练过程的监控指标与不收敛排查只看loss和accuracy是不够的。我训练时至少监控五个量训练loss、验证loss、训练top-1、验证top-1、学习率。如果训练loss下降但验证loss上升是过拟合如果两个都不降是欠拟合或者学习率有问题如果训练loss剧烈震荡多半是学习率太大或者batch size太小。还有一个我必看的指标是混淆矩阵。总体精度90%可能藏着某些类别的灾难性表现。有一次我的模型总体精度92%看着挺好结果混淆矩阵显示“玻璃”类的召回只有41%全被判成了“其他”。这种问题在总体指标上完全看不出来但在实际使用中是致命的。不收敛的排查顺序我一般是这样的先确认数据管道没问题打印几个batch看看图像和标签对不对再确认预处理和归一化没问题统计输入均值方差是否接近0和1然后检查学习率是否过大把lr除以10试一次最后才怀疑模型结构或者损失函数。按这个顺序八成问题在前两步就能定位。提示训练前一定要做一个“过拟合小样本”的测试。取20张图让模型训练到接近100%精度。如果做不到说明代码有bug别浪费时间调参。4. 模型压缩与部署落地4.1 导出、量化与精度损失的权衡训练完的pth文件不能直接上生产体积大、加载慢、依赖训练框架。标准流程是导出ONNX再做量化。导出ONNX的时候有几个坑。一是要固定输入尺寸动态轴虽然支持但会让后续优化受限二是要执行一次前向确认输出一致导出后用ONNX Runtime跑一遍和PyTorch输出对比误差应该在1e-5以内三是要注意算子兼容性某些自定义算子导不出来得提前替换。量化我一般做Post-Training QuantizationPTQint8量化后模型体积能压到原来的四分之一推理速度提升两到三倍。但量化对精度的损伤在垃圾识别任务上比较敏感因为颜色和纹理的细微差异容易在量化噪声里丢掉。我的量化流程是这样的准备500到1000张有代表性的校准图覆盖所有类别用per-channel对称量化然后对比量化前后的精度。如果掉点超过1.5个点就改用混合量化把第一层和最后一层保持fp32或者对敏感层做特殊处理。实际测试下来MobileNetV3-Large的int8量化掉点在0.8到1.2个点之间可以接受ResNet系列掉点会稍微大一些。# 导出ONNX示例 python export_onnx.py \ --weights best_model.pth \ --input-size 320 \ --opset 12 \ --output model.onnx # 量化校准 python quantize.py \ --model model.onnx \ --calib-dir ./calib_images \ --calib-samples 800 \ --per-channel如果端侧部署还要考虑算子支持情况。有些推理框架对某些激活函数或者归一化层支持不好会退化到CPU执行速度直接崩。这种情况要么换激活函数要么在导出前做算子融合。我一般会在目标设备上实测别只看PC上的benchmark。4.2 服务端接口设计与并发处理服务端的核心设计原则是预处理必须和训练时完全一致不能有任何偏差。我见过太多“训练95%上线85%”的案例最后查出来是推理时用了不同的Resize插值方式。接口我一般设计成POST /predict接收multipart图像返回JSON包含top-3类别和置信度外加一个is_confident字段。这个字段很有用置信度低于阈值时前端可以提示用户重新拍摄避免给出误导性结果。阈值我一般设在0.6左右可以通过验证集上的置信度分布来确定。并发处理上模型推理本身是计算密集型的多线程反而会争抢资源。我的做法是单进程加批量推理请求先入队攒到batch_size或者超时50ms就一起推理。这样吞吐能提升三到五倍。要注意批内图像尺寸不一致时要paddingpadding方式要和训练时一致。模型加载用单例别每次请求都加载。服务启动时预热几次推理把算子编译、内存分配的开销提前吃掉避免第一个请求超时。4.3 端侧推理的性能优化路径端侧部署的瓶颈通常在内存带宽而不是算力。优化路径我按收益排序第一是降低输入分辨率从320降到256延迟能降三成精度掉一个点左右第二是换更轻的骨干MobileNetV3-Large换Small延迟减半精度掉两到三个点第三是int8量化延迟降四到五成第四是算子融合和内存复用。实际测试中一个320输入的MobileNetV3-Largeint8量化后在中等性能的手机上单张推理大约30到50ms加上预处理和后处理端到端能控制在100ms以内用户体验是可以接受的。端侧还有一个细节是图像预处理要用GPU或者NEON指令加速纯CPU做Resize和Normalize可能比推理本身还慢。常见的做法是把预处理也做成算子集成进推理图或者用框架提供的图像处理模块。5. 常见问题与排查技巧实录5.1 精度类问题的速查路径精度问题分两种训练时就上不去和训练很好但上线就崩。这两类的排查路径完全不同。训练上不去先看是不是欠拟合。表现是训练集精度也低那就加大模型容量、减少正则、提高学习率、检查标签有没有错。标签错误是最隐蔽的我遇到过一次标注文件里有两百多张图的类别整体偏移了一位导致模型怎么训都卡在80%。后来写了个脚本把每类的前50张图可视化出来人工过一遍才发现问题。上线就崩八成是分布偏移或者预处理不一致。排查顺序先确认推理预处理和训练一致把同一张图分别走训练管道和推理管道对比张量差异再确认测试集和真实场景分布是否一致统计两边的亮度、尺寸、目标占比分布最后才怀疑模型泛化能力。现象可能原因排查方法解决方向训练精度高验证精度低过拟合对比loss曲线加增强、加正则、减容量训练验证都低欠拟合或标签错误小样本过拟合测试加容量、查标签某类召回极低类别不平衡或特征混淆看混淆矩阵加权损失、补样本验证高但上线低分布偏移/预处理不一致张量对比对齐预处理、补真实数据精度波动大随机种子/BN不稳定固定种子重跑增大batch、固定种子5.2 工程类问题的排查清单工程问题里最常见的是内存泄漏和推理结果不稳定。内存泄漏通常出在DataLoader的worker没关、图像没释放、或者推理时的中间张量累积。我的习惯是在训练循环里定期打印显存占用如果每个epoch涨一点基本可以确定是泄漏。推理结果不稳定指同一张图多次推理结果不同。这种情况先检查模型是不是忘了eval()Dropout和BatchNorm在训练模式下会带来随机性。再检查是不是有随机增强混进了推理管道这个错误很常见因为很多人直接把训练用的transform复用到推理上。还有一个隐蔽问题是ONNX导出的动态shape。如果导出时用了动态batch但推理时喂了固定batch某些框架会自动padding导致结果偏移。我的做法是导出时就固定shape推理时严格按shape喂数据。部署环境的数值精度差异也要注意。某些推理框架默认用fp16某些用fp32混合使用会导致轻微的结果差异。这个差异在分类任务上一般不影响最终标签但如果你的业务依赖置信度做阈值判断就得统一精度。5.3 几条不写进文档的实操经验第一条别迷信公开数据集。网上的垃圾数据集大多是摆拍的背景干净、物体完整、光照均匀训练出来的模型在真实场景里表现会打很大折扣。我的做法是公开数据集只用来做预训练或者初始化最终模型一定要在自己的场景数据上微调哪怕只有两千张。第二条置信度一定要做校准。神经网络输出的softmax概率往往过于自信模型说0.95的样本实际正确率可能只有0.85。用温度缩放做后处理校准成本极低效果明显。具体做法是在验证集上拟合一个温度参数T推理时把logits除以T再过softmax。T通常在1.2到2.0之间。第三条保留一个“拒识”通道。真实业务里总会有模型没见过的物体强行分类只会给出错误答案。我的做法是设置置信度阈值加一个基于能量分数的OOD检测两个条件任一不满足就返回“无法识别”。这个设计能显著降低误判带来的负面影响比硬提升精度划算得多。第四条模型版本管理要跟上。每次训练记录数据版本、超参、随机种子、评估指标模型文件用版本号命名。我吃过一次亏线上出问题想回滚结果发现上一版模型被覆盖了只能连夜重训。第五条评估指标要和业务对齐。业务关心的是分拣准确率那评估时就该看每一类的精确率和召回率而不是只看总体top-1。一个总体92%的模型如果“有害垃圾”这一类召回只有70%在实际使用中就是不合格的因为漏检有害垃圾的代价远高于其他类别。