1. 项目缘起与整体设计思路拆解
垃圾分类这件事,从政策推行到现在,真正落地到每个家庭、每个楼栋的时候,最难的其实不是“愿不愿意分”,而是“分得对不对”。我在实际接触这类需求的时候发现,大部分人站在垃圾桶前面那几秒钟,脑子里是懵的——奶茶杯算可回收还是其他垃圾?沾了油的纸盒要不要洗?电池到底往哪个口扔?这些细碎的问题,靠贴一张宣传海报根本解决不了。所以当我第一次看到“基于深度学习的生活垃圾智能分类系统”这个题目的时候,我的第一反应是:这是一个真实痛点驱动的项目,而不是为了做而做的课程作业。
这个系统的核心逻辑很直接:用户掏出手机,打开微信小程序,拍一张或者选一张垃圾的照片,系统在后台跑一个训练好的视觉模型,把这张图归类到四大生活垃圾类别里(可回收物、厨余垃圾、有害垃圾、其他垃圾),然后把结果返回给用户。整个链路看起来简单,但真正做起来,涉及的东西一点都不少——前端小程序、后端服务、模型训练、数据集构建、论文撰写、开题中期材料,一整套流程走下来,基本上把一个小型AI应用从零到一的完整闭环都覆盖了。
我之所以选择微信小程序作为前端载体,而不是做一个独立的App,理由很实在。第一,小程序的开发成本低,不需要用户下载安装,扫码即用,传播门槛极低;第二,小程序天然具备拍照和相册调用的能力,调用wx.chooseImage或者wx.chooseMedia就能拿到图片,传给后端做推理,交互路径短;第三,对于学生项目或者小型团队来说,小程序的审核和发布流程相对可控,不需要处理复杂的应用商店上架问题。当然,小程序也有它的局限,比如包体积限制、不能直接跑大模型推理,所以模型必须放在服务端,这就引出了后端架构的设计。
模型选型上,我用了YOLOv5。这里需要解释一下为什么选它。垃圾分类本质上是一个图像分类任务,但YOLOv5是目标检测框架,看起来好像不太对口。实际上,在实际场景里,用户拍的照片往往不只是一个干净的物体,可能是一个垃圾桶里的一堆东西,或者桌面上散落的几种垃圾。如果只做整图分类,模型很容易被背景干扰。用YOLOv5做检测,可以先定位到图中的垃圾主体,再对检测框内的区域做分类,这样鲁棒性会好很多。而且YOLOv5的生态非常成熟,训练速度快,部署方便,支持导出ONNX或者TorchScript,后端集成起来不费劲。对于这个项目来说,YOLOv5s这个轻量级版本就够用了,参数量小,推理速度快,精度在垃圾分类这种类别数不多的场景下完全能打。
数据集这块是整个项目的地基。我一开始想直接用网上公开的垃圾分类数据集,但实际拉下来一看,问题不少:类别标注不统一、图片质量参差不齐、有些类别的样本严重偏少。所以后来我花了大量时间做数据清洗和补充采集。最终的数据集覆盖四大类,每类下面再细分小类,比如可回收物下面有塑料瓶、纸箱、易拉罐等,有害垃圾下面有电池、灯管、药品等。整个数据集大概在几千张的量级,训练集、验证集、测试集按7:2:1划分。这个规模对于YOLOv5s来说是比较合适的,既能训出可用的模型,又不至于训练时间过长。
开题报告和中期检查这两块,很多人觉得是走形式,但我实际写下来发现,它们逼着我把项目的技术路线、时间节点、预期成果想清楚。开题报告里我重点写了国内外研究现状、技术方案对比、可行性分析;中期检查则是对前半段工作的复盘,包括数据集构建进度、模型初版训练结果、小程序原型完成情况。论文部分则是整个项目的系统性总结,从问题背景、相关工作、方法设计、实验验证到系统实现,一条线串下来。这几份材料其实是相互关联的,开题定方向,中期纠偏差,论文做沉淀,如果前面想清楚了,后面写起来就是水到渠成的事。
2. 数据集构建与模型训练的核心细节
2.1 数据采集与标注的实操要点
数据集的质量直接决定了模型的上限,这句话在垃圾分类场景里体现得淋漓尽致。我最初的做法是直接从公开数据集里扒图,结果训练出来的模型在真实场景下表现很差,原因很简单:公开数据集的图片大多是摆拍的干净物体,背景单一,而用户实际拍的照片背景杂乱、光照不均、角度随意。所以后来我调整了策略,数据来源分成三块:一部分是公开数据集筛选后的高质量样本,一部分是自己用手机在小区垃圾桶、厨房、办公室等真实场景下拍的,还有一部分是从合规的图片素材库里补充的。
采集的时候有几个细节值得注意。第一,要覆盖不同的光照条件,白天自然光、晚上灯光、逆光、阴影下的都要有,不然模型一到晚上就瞎。第二,要覆盖不同的拍摄角度,俯拍、平视、斜角都要涉及,因为用户拍照不会像实验室那样规规矩矩。第三,要包含遮挡和堆叠的情况,比如几个垃圾混在一起,这对YOLOv5的检测能力是个考验,但也是真实场景的常态。第四,负样本也很重要,比如空垃圾桶、干净的桌面,这些图标注为“无目标”,能帮助模型减少误检。
标注工具我用的是LabelImg,标注格式选YOLO格式,也就是每个目标一行,包含类别索引和归一化后的中心点坐标、宽高。标注的时候有个经验:边界框不要贴得太紧,稍微留一点边距,因为太紧的框在数据增强(比如随机裁剪、缩放)之后容易把目标切掉一部分,反而影响训练。另外,对于同一张图里多个同类目标,要逐个标注,不要合并成一个大框,否则模型学到的定位能力会退化。
数据增强这块,YOLOv5自带的增强策略已经比较丰富了,包括Mosaic、随机缩放、随机裁剪、色彩抖动、翻转等。我在此基础上针对垃圾分类场景做了一些调整:色彩抖动幅度调大了一点,因为垃圾的颜色差异很大,模型需要对颜色变化更鲁棒;Mosaic的概率保持默认的1.0,这个增强对小目标检测帮助很大;翻转只用了水平翻转,垂直翻转没开,因为垃圾上下颠倒的情况在现实中很少见,开了反而引入噪声。
2.2 YOLOv5模型配置与训练参数选择
模型配置文件我选的是yolov5s.yaml,类别数改成实际的类别数。这里有个容易踩的坑:如果你用的是预训练权重,改类别数之后,检测头的部分需要重新初始化,但骨干网络和 neck 部分的权重可以加载。YOLOv5的训练脚本会自动处理这个,但你要确保--weights参数指向的是预训练权重文件,而不是从头训。从头训在小数据集上很容易过拟合,收敛也慢。
训练参数方面,我实际跑下来的配置是这样的:输入图像尺寸--img 640,batch size设成16(受限于显存,如果有更大的卡可以上32),epochs设了100轮,优化器用SGD,初始学习率0.01,动量0.937,权重衰减0.0005。学习率调度用的是余弦退火,预热3个epoch。这些参数不是拍脑袋定的,640的输入尺寸是YOLOv5的默认推荐值,在精度和速度之间比较平衡;batch size 16是在显存约束下的折中,太小会导致训练不稳定,太大则可能泛化变差;100轮是基于验证集loss曲线观察后定的,大概在70到80轮左右模型基本收敛,后面主要是微调。
训练过程中我重点盯两个指标:mAP@0.5和各类别的precision/recall。垃圾分类场景下,类别不平衡是个大问题,可回收物和其他垃圾的样本量通常远多于有害垃圾。我的做法是在数据集划分时尽量保证每类样本量相对均衡,如果实在没法均衡,就在训练时对少数类做重采样或者调整损失函数的类别权重。YOLOv5本身没有直接暴露类别权重的参数,但可以通过修改loss计算部分的代码来实现,不过对于这个项目来说,通过数据层面的均衡处理已经够用了。
训练完成后,模型导出成ONNX格式,方便后端用ONNX Runtime做推理。导出命令大概是python export.py --weights best.pt --include onnx --img 640。导出之后一定要做一次推理验证,确保导出的模型和原模型输出一致,我遇到过导出后输出维度对不上的情况,排查下来是opset版本的问题,改成opset 12就正常了。
2.3 模型评估与迭代优化
模型训完之后,不能只看训练日志里的loss,一定要在独立的测试集上做评估。我用的评估指标包括mAP@0.5、各类别的precision、recall和F1分数。第一版模型跑下来,整体mAP@0.5大概在0.82左右,但有害垃圾这一类明显偏低,recall只有0.65左右。分析原因,主要是有害垃圾的样本量少,而且外观差异大(电池、灯管、药品长得完全不一样),模型学到的特征不够充分。
针对这个问题,我做了两件事:一是补充有害垃圾的样本,特别是不同品牌、不同型号的电池和灯管图片;二是在推理阶段加了一个后处理逻辑,当模型对某个检测框的置信度处于中间区间(比如0.3到0.5之间)时,不直接丢弃,而是结合类别先验做一次二次判断。这个逻辑在小程序端体现为:如果置信度不够高,给用户的提示是“不确定,建议人工确认”,而不是硬给一个可能错误的结果。这个设计在实际使用中反馈不错,因为垃圾分类这件事,给错答案比说“不确定”更让人恼火。
第二版模型补充数据后重新训练,有害垃圾的recall提升到了0.78左右,整体mAP@0.5到了0.86。这个水平对于一个小程序应用来说已经够用了,毕竟用户拍的照片质量参差不齐,模型不可能做到100%准确,关键是给出合理的结果和置信度提示。
3. 微信小程序与后端服务的完整实现
3.1 小程序端交互设计与关键代码
小程序的页面结构不复杂,核心就三个页面:首页、拍照/选图页、结果展示页。首页放一个醒目的“拍照识别”按钮,下面附上四类垃圾的简单说明,让用户有个基本认知。拍照页调用微信的媒体选择接口,用户可以直接拍照或者从相册选图。拿到图片后,先在小程序端做一次压缩,因为原图可能好几MB,直接上传既慢又浪费流量。压缩用wx.compressImage,质量设成80,宽度限制在1080px左右,这样既能保证模型输入够用,又能把图片体积降下来。
上传用的是wx.uploadFile,把压缩后的图片传到后端接口。这里有个细节要注意:小程序的网络请求域名需要在后台配置合法域名,本地调试的时候可以在开发者工具里勾选“不校验合法域名”,但正式发布前一定要配好,否则线上会请求失败。上传的时候我加了一个loading状态,因为推理需要时间,用户需要知道系统在干活,不然会以为卡死了。
结果展示页会把模型返回的类别、置信度、以及该类垃圾的投放建议展示出来。投放建议是提前存在后端的一个映射表里的,比如识别出“电池”,就返回“有害垃圾,请投放至红色垃圾桶,注意绝缘处理”。这个映射表看起来简单,但实际写的时候要查不少资料,确保建议准确、表述清晰。另外,结果页还加了一个“结果有误?”的反馈按钮,用户点击后可以把误判的图片和正确类别提交回来,这些数据积累起来就是后续模型迭代的宝贵素材。
小程序的UI我尽量做得简洁,配色用了绿、蓝、红、灰对应四类垃圾,视觉上直观。没有堆太多花哨的动效,因为这类工具型小程序,用户要的是快和准,不是好看。实测下来,从点击拍照到看到结果,整个流程在正常网络下大概3到5秒,主要耗时在后端推理和网络传输上。
3.2 后端推理服务的搭建与部署
后端我用的是Python的Flask框架,轻量、上手快,对于这种单体推理服务来说足够用。核心接口就一个/predict,接收POST上传的图片,做预处理(resize到640x640、归一化、转成模型输入格式),然后调用ONNX Runtime做推理,最后把检测结果做NMS后处理,映射成类别名称和置信度,返回JSON。
预处理这块要和训练时保持一致,这是很多人容易忽略的点。训练时用的是YOLOv5的letterbox方式做resize,也就是保持宽高比、不足的部分用灰色填充,推理时也必须用同样的方式,否则输入分布不一致,精度会掉。我在代码里直接复用了YOLOv5官方仓库里的letterbox函数,确保前后一致。
推理速度方面,在CPU上跑YOLOv5s,单张图片大概200到400毫秒,加上网络传输和小程序端的处理,整体响应在可接受范围内。如果对速度有更高要求,可以上GPU或者用TensorRT加速,但对于这个项目的使用场景来说,CPU已经够用,没必要增加部署复杂度。服务部署在一台普通的云服务器上,用Gunicorn做WSGI服务器,Nginx做反向代理,配置了HTTPS,因为小程序要求网络请求必须是HTTPS。
这里有个实操经验:ONNX Runtime的线程数要合理配置,默认可能会把CPU吃满,影响其他服务。我在初始化session的时候设置了intra_op_num_threads,根据服务器的核数做了调整,一般设成核数的一半左右比较稳妥。另外,模型文件在服务启动时加载一次,常驻内存,不要每次请求都重新加载,否则性能会很差。
3.3 前后端联调与异常处理
联调阶段遇到的问题不少,我挑几个典型的说。第一个是图片上传的格式问题,小程序端wx.uploadFile传的文件,后端接收到的文件名可能不带扩展名,Flask的request.files拿到的文件对象需要手动处理,我用的是先读成字节流,再用PIL的Image.open从BytesIO里加载,这样不依赖文件名。第二个是跨域问题,虽然小程序请求不受浏览器同源策略限制,但如果后端同时要给网页端用,就需要配CORS,我用了flask-cors,配置了允许的来源。
异常处理方面,后端对几种情况做了兜底:图片解码失败返回400,模型推理异常返回500并记录日志,检测不到任何目标时返回一个“未识别到垃圾,请重新拍摄”的提示,而不是空结果。小程序端对网络超时、上传失败也做了提示,引导用户重试。这些细节看起来琐碎,但实际用起来,能不能给用户一个清晰的反馈,直接决定了体验的好坏。
4. 常见问题排查与项目材料撰写经验
4.1 训练与部署中的典型问题速查
整个项目做下来,踩过的坑不少,我整理了一个速查表,方便后来者对照排查。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 训练loss不下降 | 学习率过大或过小、数据标注错误、预训练权重未正确加载 | 先检查标注文件格式和类别索引是否正确,再用小学习率试跑几个epoch观察loss趋势 |
| 验证集mAP远低于训练集 | 过拟合、数据分布不一致 | 增加数据增强、检查验证集是否混入了训练集图片、补充更多真实场景样本 |
| 导出ONNX后推理结果异常 | opset版本不匹配、输入尺寸不一致 | 指定opset 12重新导出,核对导出时的img尺寸与推理时一致 |
| 小程序上传图片失败 | 合法域名未配置、图片体积过大、HTTPS证书问题 | 检查后台域名配置,确认压缩逻辑生效,验证证书有效性 |
| 后端推理服务响应慢 | 模型重复加载、线程数配置不当、图片未压缩 | 模型常驻内存,调整ONNX Runtime线程数,前端做好图片压缩 |
| 某些类别识别率低 | 样本量不足、类别间外观相似 | 补充该类样本,考虑增加细分类别或引入注意力机制 |
除了表里的问题,还有一个比较隐蔽的坑:数据集的类别索引和模型配置里的类别顺序必须严格一致。我有一次因为标注时类别顺序和data.yaml里的names顺序对不上,导致模型把可回收物识别成了有害垃圾,排查了大半天才发现是索引错位。所以每次改类别配置,一定要从头到尾核对一遍。
4.2 开题、中期与论文的撰写心得
开题报告的核心是把“为什么做”和“怎么做”讲清楚。我在开题里重点写了三块:一是垃圾分类的政策背景和实际痛点,用具体的场景说明需求真实存在;二是技术方案的对比,比如为什么选YOLOv5而不是Faster R-CNN或者SSD,从精度、速度、部署难度几个维度做了分析;三是时间规划,把数据集构建、模型训练、小程序开发、系统联调、论文撰写几个阶段的时间节点列清楚。开题不需要把每个技术细节都写透,但逻辑链条要完整,让评审老师一眼能看出这个项目是可行的。
中期检查的重点是“做到哪了”和“遇到什么问题”。我把前半段的工作做了量化呈现:数据集规模、标注数量、初版模型的mAP、小程序原型的完成度。同时如实写了遇到的问题,比如有害垃圾识别率偏低、小程序图片上传偶发失败,以及对应的解决思路。中期检查不是走过场,它其实是一次很好的自我复盘,逼着你去审视进度是否合理、方案是否需要调整。
论文是整个项目的沉淀,结构上我按照“绪论—相关工作—方法设计—实验与分析—系统实现—总结与展望”来组织。绪论讲背景和意义,相关工作综述垃圾分类和深度学习目标检测的现有研究,方法设计详细描述数据集构建、模型结构、训练策略,实验部分给出量化结果和对比分析,系统实现讲小程序和后端的工程细节。写论文的时候,数据和图表一定要真实,实验对比要有说服力,不要为了凑篇幅堆砌无关内容。我的经验是,项目做扎实了,论文自然有东西写;项目做得虚,论文怎么编都别扭。
4.3 项目后续可扩展的方向
这个系统目前是一个可用的原型,但离产品化还有距离。我个人觉得有几个方向值得继续做。一是模型轻量化,把YOLOv5s进一步压缩,尝试用MobileNet或者ShuffleNet做骨干,甚至量化到INT8,这样推理速度能再上一个台阶,也有机会把模型放到端侧做初步筛选。二是多目标识别,现在系统主要针对单张图里的主要垃圾,如果用户拍了一堆混合垃圾,能不能逐个框出来分别给结果,这个体验会更好。三是用户反馈闭环,把用户提交的误判数据自动收集、定期回流到训练集,形成模型迭代的正循环。四是增加语音播报和适老化设计,垃圾分类的受众里有不少老年人,操作和阅读上需要更友好。
我在实际做这个项目的过程中最大的体会是,AI应用的价值不在于模型多复杂,而在于能不能真正解决一个具体场景里的具体问题。垃圾分类这个题目看起来朴素,但把数据集、模型、小程序、后端、材料这几块串起来,中间涉及的技术决策和工程细节非常多,每一个环节都有值得深挖的地方。如果你也在做类似的项目,我的建议是先把最小可用链路跑通——哪怕模型精度一般、小程序界面简陋,先让“拍照—上传—推理—返回结果”这条线走通,然后再逐个环节去优化,这样比一开始就追求完美要靠谱得多。