刚才有朋友给我抛了个需求做一个垃圾分类检测系统要求输入一张图片或一段视频能自动识别垃圾类别并提示投放指引。乍一听好像就是套一个图像分类模型但真做起来才发现里面坑不少——数据集质量、类别体系划分、小目标漏检、环境泛化每个环节都能让你心态崩掉。这篇文章我把整个项目的完整流程、踩坑记录和最终落地方案整理出来给后面要做类似视觉识别项目的人一个参考。需要说明的是我这里采用的是“检测分类”两阶段结合的思路而不是直接用单模型端到端处理。原因后面会细讲。适不适合你取决于你的业务场景是静态图还是实时视频、类别粒度是四大类还是细分几十类。文章会覆盖完整方案你可以根据实际需求裁剪。1. 项目要解决的真实问题1.1 一个看似简单但容易翻车的AI需求垃圾分类检测核心任务听起来很简单给一张图判断图里是干垃圾、湿垃圾、可回收物还是有害垃圾。但你如果直接上手去搜数据集、下个分类模型开始训练大概率会被现实狠狠教育。第一垃圾的形态极其多变。一个矿泉水瓶完整的、压扁的、带标签的、不带标签的、在阳光下反光的在分类模型眼里可能就是四类东西。第二真实场景往往是多物体混叠——垃圾桶里一个塑料袋套着外卖盒旁边还搭着一根香蕉皮你要模型给出一个判断既要看主体还要看边缘遮挡。第三国内垃圾分类普遍按四分类执行但四分类内部的复杂度差异巨大可回收物里瓶子、纸箱、金属罐长得完全不像厨余垃圾里的骨头的残渣、果皮、剩饭颜色纹理接近到让人崩溃。所以这个项目从需求分析上就不能简单定义成“图像分类”而应该定义成“复杂背景下的多标签检测与识别”。这就直接决定了后面的技术选型和数据策略。1.2 我最后定的技术路线检测分类而不是纯分类经过比较我最终选了“目标检测精细分类”串联的路线而不是拿一个分类网络直接硬扛。纯分类方案比如ResNet、EfficientNet直接读整图输出类别在可控背景、单物体居中的场景下效果尚可但只要进入真实场景——比如桌面一堆杂物、垃圾桶内部、户外地面——模型就会频繁翻车。因为分类模型天然没有“找目标”的能力它只能对整张图做全局判别背景干扰一大特征就被稀释了。目标检测模型YOLO系、SSD、Faster R-CNN则先框出图中每一个可能是垃圾的目标区域再对区域做类别判断。它的优势是天然支持多目标同时识别不需要人工裁剪。模型输出带位置信息后续可以接机械臂抓取、语音播报等联动操作。对小物体、遮挡目标有相对独立的判断空间不容易被全图噪声带偏。最终的架构是检测模型负责把所有“疑似垃圾”的框找出来分类头直接输出细分类别。我用的检测框架把分类和回归放在同一个模型里输出端有类别概率和边框坐标这就够了。没有单独再挂一个分类网络那样流程太重推理速度也扛不住实时视频流。2. 数据是垃圾分类这类项目的命门2.1 公开数据集的问题和类别体系选择做视觉项目的人都知道一句话模型效果的上限由数据决定。垃圾分类数据集虽然网上有现成的但我实际用下来发现三个硬伤。一是类别分布极度不均衡。公开数据集中塑料瓶、纸箱这类常见可回收物动辄上千张但像碎陶瓷、污染纸张这类“其他垃圾”可能只有几十张模型对这些冷门类别的学习严重不足。二是背景过于干净。数据集里大量图片都是白底棚拍图拿到真实场景测试时泛化能力断崖式下跌。三是标注质量参差不齐。部分数据集直接通过搜索引擎批量抓图标注错误率并不低用这种数据训练模型会学到错误特征。所以我在项目初期就决定公开数据集只作为起点不能作为唯一依赖。类别体系上我按国内四分类主干做了微调拆成8个细分类别塑料瓶、纸箱纸壳、金属罐、玻璃瓶、厨余垃圾、有害垃圾电池/药品、其他干垃圾、织物。这样既保留了实际投放的可操作性又给模型提供了足够区分的特征空间。如果你做的是社区落地项目建议先和运营方确认导览口径否则你分得太细用户反而不一定用得上。2.2 数据补全与标注实操确定了类别后数据补全我分三条线走公开数据集清洗把自带的标注信息过一遍剔除明显错误的框和类别标签只保留置信度高的部分。自行拍摄补充我拉了朋友帮忙在办公室、小区垃圾投放点、户外路边三个场景拍了一批真实照片专门补充公开数据集里缺失的遮挡、光照变化、混叠堆放情况。针对性爬取补漏对模型初期测试中容易混淆的类别比如纸盒和塑料盒、玻璃瓶和陶瓷碎片单独收集更多样本。标注工具用的是开源的labelImg导出Pascal VOC格式。我的建议是标注时框略紧一些——不要包含过多的背景边缘但也不要切到目标主体。框太小会让模型学到截断特征框太大则引入背景噪声。我踩过的坑是刚开始标得太随意有些框把旁边的杂物也包进去了结果训练出来模型对“杂物”本身产生了响应误检率飙升。2.3 数据增强和类别平衡标注完成后原始数据大概5000多张这个量级对YOLO类模型来说偏少尤其是细分8类平均每类只有600多张。我的做法是做了三层处理一是离线增强对样本量不足的类别做水平翻转、随机裁剪、色彩抖动、HSV扰动、高斯模糊等操作把稀缺类别的样本数拉上来。注意翻转对文字类垃圾比如带标识的药品包装不太友好旋转90度会让文字倒置容易学偏这种情况我做了人工筛选。二是Mosaic在线增强训练时开启Mosaic把4张图拼接成一张输入。这个操作对垃圾检测特别有用——因为四张图拼在一起模型被迫在小尺寸目标上学习特征对小物体检测能力提升明显而小物体恰恰是垃圾场景里的高频情况。三是损失函数层面的类别平衡给样本少的类别在损失计算时乘以更高的权重系数。我在nc分类头配置里没有用默认的均衡权重而是根据各类样本数手动设了一组权重让模型在训练时不会为了“多数类”牺牲“少数类”的准确率。做完整理后训练集约12000张含增强验证集1000张测试集500张左右三个集合确保没有重叠。3. 模型选型与训练3.1 为什么选YOLO而不是更强力的两阶段模型目标检测模型选型时我比较过YOLO系列和Faster R-CNN。Faster R-CNN的精度理论上更高尤其在小目标检测上优势明显但它的速度太慢——CPU上单帧推理要几百毫秒到秒级GPU上也难以达到实时视频流的需求。垃圾分类检测的实际落地场景里用户体验要求基本是相机拍到、结果立刻反馈让人在那儿等两秒还不如直接告诉他怎么扔。YOLO系是单阶段检测速度极快在GPU上轻松跑到几十帧每秒精度也在可接受范围内。我最终选用了当时成熟度较高的YOLOv5版本——不是说它最强而是它的工程化程度最好预训练权重丰富、文档齐全、数据格式兼容性好出了问题网上随便一搜就有解决方案。如果你现在从零开始做用更新版本的YOLO系列也可以核心思路不变。这里补充一个关键点迁移学习。我没有从零训练模型而是用了在COCO数据集上预训练好的权重作为起始点只对输出层做了针对8个类别的调整。预训练模型已经学会了通用的边缘、纹理、形状特征这些底层特征对垃圾识别同样适用。从零训练的话在1万多张数据上很难收敛到实用精度而迁移学习让训练时间缩短了至少三分之二最终效果也更好。3.2 训练参数与迁移学习的关键配置训练配置上我踩了不少坑这里直接给出最终稳定复现的参数组合输入分辨率640x640。512以下小目标检测能力不足但超过640后显存占用和训练时间代价明显。Batch size 16学习率0.01配合余弦退火调度。学习率这里特别留意一开始我用默认的0.02训练到中期loss出现震荡降下来之后收敛就稳定了。Epoch 300但实际在200左右就收敛了后面主要是靠早停机制避免过拟合。开启Mosaic、MixUp增强关闭Triplet以外的随机擦除实验发现随机擦除对垃圾这种纹理敏感的目标反而有副作用。训练过程中的观察指标我除了看mAP之外特别留意每一类的PR曲线精确率-召回率曲线。mAP高不一定代表每类都好比如可回收物类的mAP能达到90%以上但有害垃圾可能只有70%这时候就要回到数据层面找原因是样本量不够还是背景太类似还是标注本身有误。3.3 调优从“五五开”到“九成准确率”的关键操作第一次跑完训练测试集上的mAP只有大概0.55实拍场景更加惨不忍睹——拿瓶子在镜头前晃一晃模型经常把它识别成纸箱。我复盘了三个主要原因也给了对应的解决方案类别混淆集中在可回收物内部。塑料瓶、玻璃瓶、金属罐都是“圆柱体反光”仅靠RGB图像确实难分。针对这个问题我做了两点补充一是在训练集里加了大量带手部、带背景的实拍图让模型学会借助场景线索辅助判断比如瓶子出现在餐桌上多半是塑料或玻璃二是在后处理阶段引入了“易混淆类别对”的规则约束——当模型在塑料瓶和玻璃瓶两个类别间的置信度都低于阈值时默认按概率更高者输出但增加一条提示语“请注意确认材质”把不确定性暴露给用户而不是让模型强行拍板。小目标漏检严重。垃圾桶旁边的小烟头、瓶盖模型完全不管。这个问题的核心是训练数据的尺度分布和实际场景不匹配。我做了一个针对性操作把训练图像中面积占比小于1%的目标框特别标记出来统计数量如果偏低就用切片把大图切成四块分别检测再合并结果来补充小目标样例。同时把Mosaic增强的开启比例从默认的1.0调低到0.5——因为Mosaic虽然对尺度多样性有帮助但会让目标缩得更小小目标反而更难学。背景过拟合模型记住了“白底特征”。公开数据集里大量白底棚拍图模型学到的是“背景干净瓶子”。真实场景一换性能立刻掉。我把测试集里公开数据的比例降到30%实拍数据占70%用这个标准重新评估模型发现mAP从0.55提升到0.82左右。随后又对训练集做了白底图的降采样让真实场景图片占比达到60%最终实拍场景下mAP稳定在0.86关键类别塑料瓶、纸箱、厨余垃圾的识别率能做到90%以上。4. 系统实现4.1 整体架构检测模型训练好后要落地成一个能用的系统我把整体架构拆成三层输入层支持单张图片上传、摄像头实时视频流、RTSP拉流三种方式。前端页面做一个简单的交互界面用户拖拽图片或点开摄像头即可识别。推理层加载训练好的权重对输入帧做目标检测输出类别、置信度和边框坐标并映射到对应的投放指引文案。输出层在图片上绘制检测框和标签同时显示“可回收物-塑料瓶-请投入蓝色垃圾桶”这类投放引导。整套系统我用FastAPI做后端接口前端用了简单的HTMLJavaScript没有上重型框架方便部署和演示。模型部分用OpenCV做帧读取和预处理检测推理用的PyTorch整个推理链路在GPU环境下单帧处理耗时约15毫秒CPU环境下约200毫秒满足实时性要求。4.2 推理代码核心推理代码不算复杂关键是处理好输入输出的数据格式。我把简化版本贴在这里你可以直接参考import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression from utils.torch_utils import select_device # 加载模型 device select_device(0 if torch.cuda.is_available() else cpu) model attempt_load(best_weights.pt, map_locationdevice) model.eval() # 类别映射 CATEGORY_NAMES [塑料瓶, 纸箱纸壳, 金属罐, 玻璃瓶, 厨余垃圾, 有害垃圾, 其他干垃圾, 织物] CATEGORY_GUIDE { 塑料瓶: 可回收物请投入蓝色垃圾桶, 纸箱纸壳: 可回收物请投入蓝色垃圾桶, # 其他类别同理 } def detect_image(image_path): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 统一resize到640x640保持宽高比并用灰边填充 h, w img.shape[:2] scale 640 / max(h, w) nh, nw int(h * scale), int(w * scale) resized cv2.resize(img, (nw, nh)) canvas np.full((640, 640, 3), 114, dtypenp.uint8) canvas[:nh, :nw] resized # 归一化并转tensor tensor torch.from_numpy(canvas).permute(2, 0, 1).float() / 255.0 tensor tensor.unsqueeze(0).to(device) # 推理 with torch.no_grad(): pred model(tensor)[0] pred non_max_suppression(pred, conf_thres0.4, iou_thres0.45) # 处理输出还原坐标到原图尺寸 results [] if pred[0] is not None: for *xyxy, conf, cls in pred[0]: x1, y1, x2, y2 [int(v * max(w, h) / 640) for v in xyxy] category CATEGORY_NAMES[int(cls)] results.append({ category: category, confidence: float(conf), bbox: [x1, y1, x2, y2], guide: CATEGORY_GUIDE[category] }) return results这里有个小细节值得注意resize时用灰边填充而不是直接拉伸是为了防止目标比例失真影响检测效果。垃圾分类中瓶子和圆罐的比例特征很重要直接拉伸会把圆变扁、长变短模型很容易判断错。4.3 接入摄像头和批量处理摄像头实时检测是另一块常见需求。我实现了一个 VideoStream 类用独立线程读取帧缓冲主线程负责任何阻塞式推理确保画面不卡顿。核心逻辑class VideoStream: def __init__(self, source0): self.cap cv2.VideoCapture(source) self.ret, self.frame self.cap.read() self.running True self.thread threading.Thread(targetself.update, args()) self.thread.daemon True self.thread.start() def update(self): while self.running: self.ret, self.frame self.cap.read() def read(self): return self.frame配合检测线程每帧调用一次 detect_frame实测在笔记本自带摄像头上能达到25帧以上的流畅度。批量处理场景比如一次性识别一个文件夹里的几百张图则直接写了个多线程池8线程并行推理吞吐量从单线程的每秒2张提升到每秒10张左右。5. 常见问题与排查技巧实录5.1 识别不准的几类典型原因做这类项目90%的时间都耗在“效果不对”的排查上。我根据自己踩过的坑整理了一份速查表遇到类似问题可以直接对号入座现象常见原因排查方向某一类准确率特别低样本量不足或背景单一统计每类样本数补拍不同场景照片训练loss正常但测试mAP低过拟合训练集和测试集同分布增加真实场景测试数据检查是否有数据泄漏实拍时把瓶类互相搞混形态特征过于接近缺乏材质线索加入手部、桌面等场景上下文或补充特写数据小物体完全检测不到模型下采样后小目标特征消失调高输入分辨率用切片检测或调低NMS阈值检测框抖动一帧有一帧无置信度阈值设置过高接近边界把conf_thres从0.5降到0.3然后靠NMS过滤冗余框多个物体堆叠时只检出最显眼的一个标注数据缺遮挡样本单独采集堆叠场景标注时把被遮挡但可见部分也标注出来这里面最值得强调的就是遮挡场景。真实垃圾桶里垃圾是堆在一起的不是排队等你识别。我去小区垃圾投放点拍了一批“垃圾堆”图标注工作量和单物体图完全不是一个量级——一个图里可能十几个框很多框只有一两百像素大小。但这个补充让系统的实用性和之前比完全是两个水平。5.2 部署和推理环节的坑模型训练好之后部署阶段也会遇到一些和训练无关但很恶心的问题。一个是环境版本兼容。PyTorch、OpenCV、CUDA 版本组合一旦不匹配轻则警告重则直接跑不起来。我的建议是尽量用requirements.txt锁定版本别在部署机器上临时升级依赖。有一次我在一台机器上直接用pip安装最新版OpenCV结果读RTSP流时出现花屏排查了半天发现是OpenCV版本变了默认解码方式回滚后才正常。另一个是模型序列化格式的选择。PyTorch默认的.pt文件虽然方便但生产环境部署时我建议转成TorchScript格式。这样不依赖原始Python环境和模型定义代码在纯C环境或者嵌入式设备上也能跑加载速度还更快。转换命令很简单model.eval() example_input torch.randn(1, 3, 640, 640).to(device) traced_model torch.jit.trace(model, example_input) traced_model.save(best_weights.torchscript)还有一个小坑是摄像头权限。浏览器页面调起摄像头需要HTTPS协议否则会被浏览器直接拦截。我在本地调试时用的是localhost没问题但一部署到远程服务器上摄像头就失效。解决办法是在服务器上挂一个自签名HTTPS证书或者用HTTPlocalhost做内网穿透这取决于你的具体网络环境。6. 结尾做了这个垃圾分类检测系统后我最大的体会是AI项目的成败往往不在模型有多炫而在于你有没有把真实场景的脏乱差考虑进去。从数据增强到阈值调节从类别权重到部署环境每一步都是在和“现实”讨价还价。如果你正准备做一个类似的视觉识别项目记住三点一是数据质量永远优先于模型结构先花70%精力把数据整好二是迁移学习是起步的捷径别从零开始硬训三是最终效果要以实拍测试为准不要在公开数据集上自嗨。这个系统目前还只是一个检测引擎后续可以扩展的方向很多——比如接入硬件分类装置按照识别结果自动分流、做成小程序端随拍随识、或者增加语音交互提示投放指南。技术底座已经打好剩下的就是业务场景的想象力了。希望这篇记录能帮你少走一些弯路。
阅读完成 · 觉得有帮助?