做垃圾分类检测这个项目最尴尬的阶段往往不是模型训不出来而是费劲训好的模型放在电脑里自己玩可以拿给别人看的时候对方一脸茫然——没有界面没有交互检测结果往哪儿展示都说不清楚。所以我在规划智能生活垃圾检测与分类系统时一开始定的目标就不是“训一个精度还行的YOLOv5模型”而是一套完整的、带可视化管理界面的可演示系统从训练数据集构建到模型推理再到UI落地整条链路都走通。这篇东西就是把整个过程拆开揉碎了讲一遍包括数据集怎么凑、标注怎么定、YOLOv5训练有哪些容易忽略的细节、推理服务怎么封装、UI界面从选型到避免卡顿的实现方式以及最后实测下来的一些真实数据。1. 项目定位与整体架构先想清楚“给谁用、在哪用”1.1 核心需求拆解一个智能垃圾分类系统的需求不同角色理解完全不一样。做算法的人关注mAP和FPS做产品的人关注演示流畅度和交互体验而真正可能在小区或学校里用它的用户只会关心两件事把垃圾放过去它能不能告诉我该扔哪个桶。这个项目的核心目标用户被我定为三类一是在学校做课设或毕设的学生需要有一个能现场演示的完整系统二是社区或单位做环保科普展示需要一个能互动体验的终端三是想入门目标检测并快速做出成果的开发者需要一个相对完整的参考项目。这三类需求交集下来系统必须具备四个能力垃圾图像的检测与分类、带类别标注的可视化结果、本地或摄像头的实时检测、以及一套不依赖命令行操作的图形界面。明确了这一点整个系统的技术边界就清晰了——底层的模型检测能力用YOLOv5来实现上层用Python的GUI框架搭建操作界面中间用推理服务做衔接数据集则既要考虑类别覆盖又要控制标注工作量。1.2 技术选型逻辑与架构分层技术选型上我没有盲目追新。虽然YOLOv8、YOLOv11甚至YOLO26都陆续出来了热搜词里也有不少人问“yolov8训练自己的数据集”但最终仍然选了YOLOv5原因很务实生态成熟度最高踩坑资料最全对于要交作业或快速上手的人来说遇到问题搜得到答案比版本新更重要。对硬件要求相对友好显存不太大的显卡也能跑CPU推理虽然慢但至少能跑。权重文件、预训练模型、导出工具链完整从训练到部署再到后续转ONNX或TensorRT都有现成方案。系统整体分为四层数据层负责图像采集、标注和增强模型层负责YOLOv5的训练、验证和导出服务层封装推理逻辑为界面层提供统一的检测接口界面层负责交互展示包括图片检测、摄像头实时检测和历史记录管理。分层的核心好处是解耦——后面想把YOLOv5换成YOLOv8或者YOLO11只需要替换模型层和服务层的接口实现数据层和界面层完全不用动。在实际工程落地时我强烈建议项目目录从一开始就按功能划分而不是按文件堆叠否则写到后面自己都会迷路。我习惯用下面的目录结构garbage_detection_system/ ├── data/ │ ├── images/ # 原始图像 │ ├── labels/ # YOLO格式标注 │ ├── dataset.yaml # 数据集配置文件 │ └── augment.py # 数据增强脚本 ├── models/ # 模型定义文件 ├── weights/ # 预训练权重和训练产出权重 ├── inference/ │ ├── detector.py # 检测服务封装 │ └── config.py # 模型相关配置 ├── ui/ │ ├── main_window.py # 主界面 │ ├── camera_thread.py # 摄像头线程解决界面卡顿 │ └── result_view.py # 结果展示组件 └── requirements.txt2. 训练数据集的构建分类效果的上限在数据不在模型2.1 数据来源与类别体系设计模型能准确识别多少类垃圾取决于训练集里有多少类、每类多少样本。我在类别设计上参考了生活垃圾分类的国家标准但在“可回收物、有害垃圾、厨余垃圾、其他垃圾”四个大类之下进一步细分为具体物品类别这样对模型训练更友好也不影响界面侧按大类展示。最终确定的类别体系是类别ID类别名称所属大类初始样本数0塑料瓶可回收物12001易拉罐可回收物10002纸板箱可回收物11003玻璃瓶可回收物8004水果残渣厨余垃圾9005剩饭厨余垃圾7006电池有害垃圾6507过期药品有害垃圾5008陶瓷碎片其他垃圾4509烟蒂其他垃圾600为什么不定成“可回收物”“厨余垃圾”这种大类直接训练因为同一个大类内部外观差异极大比如可回收物里的塑料瓶、易拉罐、纸板箱长得完全不一样模型学到的特征容易互相干扰。相反用具体物品类别训练再在UI层把具体类别映射到大类上模型的判别压力小很多准确率也更容易做上去。初始数据集的来源主要有三个公开数据集、自己拍摄采集、网络爬虫补充。公开数据集方面常用的有华为云的垃圾数据集和Kaggle上的垃圾分类数据集质量和类别覆盖各有优劣需要自己整理。但公开数据集普遍存在一个问题——图像质量太好了全是标准视角、单一背景训练出来的模型一到真实场景就“见光死”。所以我自己又拍了一部分办公室里垃圾桶旁边、小区垃圾房、食堂餐盘回收处都拍了一些专门用来模拟真实场景。这部分自采数据在后期提升模型鲁棒性上起了很大作用。2.2 标注规范框怎么打直接影响模型表现标注环节是最枯燥但最不能糊弄的。我用labelImg做标注格式直接导出为YOLO格式的txt文件每行代表一个目标类别ID x_center y_center width height坐标全部是相对于图片宽高的归一化值。标注过程中最容易犯的错误有三类框打得太紧目标边缘被切掉导致模型学到的特征不完整预测框偏小。框打得太松把背景大量包进来尤其是目标周围有其他杂物时会把干扰信息学进去。类别标错相似的物品互相标串比如玻璃瓶和陶瓷瓶、塑料袋和塑料膜这种错误对模型是灾难性的因为模型会学到“同一种东西有两个标签”。我自己的经验是框的边缘应该刚好贴合目标的外轮廓留出1-2个像素的余量就够了。对遮挡目标的处理如果目标被遮挡超过三分之一宁可不标、当作背景也别硬标一个不完整的框如果遮挡较轻可以按可见部分标注但框要尽量贴合可见区域。标注完之后一定要做数据划分。我按照train : val : test 8 : 1 : 1的比例划分并且用脚本随机划分而不是手工拖文件保证每个类别的样本在训练集和验证集中的比例大致一致避免出现某个类别全在验证集里的极端情况。2.3 数据增强用低成本方式扩充样本多样性很多刚入门的人误以为样本不够就疯狂爬图但对垃圾检测这种任务数据增强往往比无脑加数据更有效。YOLOv5本身在训练时会自动做Mosaic增强、随机翻转、HSV色域变化等但我在训练之前又做了一层离线增强专门针对垃圾场景的特点亮度/对比度随机调整。真实场景里光线变化很剧烈尤其是户外垃圾桶附近中午强光和傍晚昏暗差别很大。随机旋转和轻微透视变换。垃圾不会总是正对着镜头瓶瓶罐罐可能横七竖八地躺着。模拟模糊。用高斯模糊模拟运动模糊应对摄像头下快速挥手放垃圾的场景。这里有一个重要的提醒旋转增强不要转90度以上的大角度。垃圾检测里很多物体具有方向性比如电池正负极、易拉罐的拉环过度旋转会打乱物体本身的语义信息。我做的是[-15度, 15度]的小幅度旋转加少量透视变换实测效果比大幅度旋转要好。3. YOLOv5训练过程与参数调优一个配置改错效果天差地别3.1 环境配置的版本对应关系YOLOv5对环境版本极其敏感这一点很多人第一次跑都会踩坑。我最终稳定使用的环境版本是Python 3.8.10 PyTorch 1.8.1 torchvision 0.9.1 CUDA 11.1 cuDNN 8.0.5为什么要强调版本对应因为YOLOv5的很多操作符在不同PyTorch版本下行为不一样尤其是针对不同显卡优化的算子版本不对轻则报错重则训练的损失曲线完全异常但还不报错。如果显卡比较新比如30系、40系建议适当上调CUDA和PyTorch版本但要注意配套的cuDNN版本也要一起变。安装的时候还有一个容易踩的坑是依赖包的版本冲突。YOLOv5的requirements.txt里没有把所有依赖都锁死版本这导致装完OpenCV后可能把numpy升到2.x然后直接兼容性报错。我的解决办法是装完依赖后立刻测试一下import torch和import cv2能顺利加载再进入下一步。3.2 数据配置文件的编写YOLOv5训练前需要准备两个关键配置文件数据配置和模型配置。数据配置文件dataset.yaml内容如下train: data/images/train val: data/images/val test: data/images/test nc: 10 names: [plastic_bottle, aluminum_can, cardboard, glass_bottle, fruit_waste, leftover, battery, expired_drug, ceramic_shard, cigarette_butt]这个文件本身逻辑很简单但路径问题极其坑人。YOLOv5项目里启动训练命令时配置中的train和val路径是相对于你执行训练命令的当前工作目录来解析的。也就是说如果你在项目根目录执行python train.py那么路径要写成data/images/train如果你在上级目录执行命令路径就会解析失败。我建议统一在YOLOv5项目根目录下操作路径写相对路径别写绝对路径否则换台机器就要重新改配置。模型配置文件我直接用了yolov5s.yaml结构深度对垃圾这种类别不算复杂的任务来说足够。如果想更高精度可以换成yolov5m.yaml但显存占用和推理时间都会上升我当时综合考虑采用了s版本。3.3 训练命令与超参数经验训练命令我是这样启动的python train.py \ --data dataset.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --epochs 150 \ --img 640 \ --device 0 \ --workers 8 \ --name garbage_v5s几个超参数的取值逻辑值得展开讲一下batch-size我训练用的显卡是单张RTX 2060 6Gbatch为16已经是显存比较紧张的状态。不要一上来就追求大batch如果训练过程中出现CUDA out of memory优先把batch降到8或4同时检查--img尺寸是不是设得太大。batch太小会导致BN层统计量不稳定所以有一个最低限经验上不要小于8。img-size640是YOLOv5的默认输入尺寸。是否值得用1280对小目标检测来说更大的输入确实能提升召回率但训练速度会明显下降显存占用也是几倍增长。垃圾检测的目标通常不算特别小640够用。epochs150个epoch看起来多但我不建议少于这个数。YOLOv5的损失曲线在前80个epoch下降很快后面就是缓慢精修的过程。我观察过自己的训练日志大约在120个epoch之后验证集上的mAP才开始稳定在较高水平不再有大波动如果只训50个epoch模型处于欠拟合状态分类效果会差一大截。3.4 训练过程中的常见问题排查训练过程中我遇到过几个问题整理成表格供参考现象可能原因解决办法训练loss为NaN学习率过高或标注文件存在0宽度/0高度框调低--lr0检查标注txt是否为空或非法loss不下降且一直很大数据配置路径错误导致训练集没加载到检查训练日志中图片张数是否异常某个类别AP为0该类别样本过少或漏标严重对该类别补充数据检查标注文件显存溢出batch过大或输入尺寸过大降batch、降img、检查后台是否占显存训练速度比其他同配置的人慢很多workers设置过低或数据存储在机械硬盘把--workers设为8或更高数据放到SSD上特别说一下NaN问题。我遇到过两次一次是初始学习率从默认的0.01调到了0.05想加速收敛结果第20个epoch直接NaN另一次是某个标注文件里宽度写成了0模型计算损失时除以了0导致梯度爆炸。用脚本清洗一遍标注数据确保所有box的宽高都为正数比排查半天NaN更高效。4. 模型评估与导出从“能跑”到“能上线”的跨越4.1 评估指标先看PR曲线再谈mAP训练结束后很多人只看一眼results.png里的mAP就草草收工。我建议至少再对比两个指标每个类别的precision和recall以及验证集上的PR曲线。我的最终结果大致是Class P R mAP0.5 all 0.912 0.884 0.921 plastic_bottle 0.934 0.921 0.952 aluminum_can 0.901 0.876 0.913 ...单看总mAP挺好看但细看每个类别会发现烟蒂这一类P和R都偏低原因是烟蒂目标小、颜色和很多浅色背景接近特征不明显。这个结论就说明总指标好不代表每个类别都好。实际部署时烟蒂这一类就需要在UI侧做出提示——置信度低于某阈值时不显示结果避免误报误导用户分类。另外要把--conf-thres和--iou-thres在验证时测一遍。我之前用默认的0.25置信度阈值测mAP看着不错但实际部署时画面上会出现大量低置信度的误检框。经过测试垃圾检测场景把置信度阈值提高到0.4-0.5视觉效果干净很多真正待分类的垃圾不会漏误报却大幅减少。4.2 模型导出PyTorch权重转TorchScript和ONNX训练产出的best.pt是PyTorch格式直接用于推理没问题但如果想脱离训练环境部署或者想被其他框架调用建议导出成TorchScript或ONNX格式。YOLOv5自带了导出脚本python export.py \ --weights runs/train/garbage_v5s/weights/best.pt \ --include torchscript onnx \ --img 640导出过程中要注意--img必须和训练时的输入尺寸一致否则模型内部自适应调整后输出的检测框坐标比例会错位。ONNX导出后可以用onnxruntime做推理速度测试验证导出是否正确不要直接丢到生产环境里才发现坐标不对。导出后的模型大小也有参考意义。YOLOv5s的FP32权重大约在14-15MBFP16量化后可以在一些边缘设备上跑得更快。如果后续想在Jetson Nano这类设备上部署建议量化到FP16推理速度能提升30%-50%精度损失很小。5. 推理服务封装把模型细节和业务逻辑隔离开5.1 检测器的统一接口设计UI界面不应该直接操作YOLOv5的模型对象这样耦合度太高后续换模型会牵一发动全身。我把推理逻辑统一封装成一个Detector类对外只暴露一个方法class GarbageDetector: def __init__(self, weights_path, conf_thres0.45, iou_thres0.45): # 加载模型、配置推理参数 def detect_image(self, image) - list[DetectionResult]: # 输入BGR图像输出检测结果列表每个结果包含类别、置信度、边界框 def detect_camera_frame(self, frame) - list[DetectionResult]: # 输入摄像头帧输出检测结果DetectionResult是一个简单的数据结构包含class_name、confidence、bbox三个字段。这样UI层拿到结果后只需要做展示完全不需要知道内部是YOLOv5还是其他模型。这个封装看起来简单但意义很大。后来我尝试过一次把YOLOv5切成Ultralytics YOLOv8的模型发现只需要改Detector内部的实现UI层一行代码没动。如果你的项目后续可能会换模型这个接口抽象越早做越好。5.2 置信度阈值与NMS参数的实际调试推理阶段两个关键参数是置信度阈值和NMS的IoU阈值conf_thres0.45我实测下来这个值对垃圾检测比较均衡。阈值过高会漏掉一些外形奇怪但确实是目标的情况阈值过低会弹出很多误检框。iou_thres0.45控制两个重叠框是否合并。垃圾场景下目标通常不会密集堆叠这个参数用默认的0.45就够了不需要额外调。但必须说明筛选类别时要注意一个特殊情况同一个物体可能同时被识别成多个类别。比如一个易拉罐置信度最高的类别是易拉罐但第二个预测也标了个玻璃瓶且置信度只差0.1。这种情况下UI界面上显示最高置信度的类别是合理的但如果你观察多了会发现某些相似材质物品的预测结果在几个类别间反复横跳。解决办法是对易混淆类别的置信度单独做一层后处理比如预测结果里同时出现“玻璃瓶”和“陶瓷碎片”且置信度接近可以按训练集里该类别的先验概率做修正。这个手段比较取巧但对演示系统的体验提升很明显。6. UI界面设计与卡顿问题排查用户看到的才是产品6.1 技术选型PyQt5还是Web界面UI层我对比过两条技术路线PyQt5桌面应用和基于Web的界面Flask后端 浏览器前端。PyQt5的优势是打包方便、本地启动快、摄像头调用直接适合做一个独立的桌面演示工具。Web界面的优势是交互效果丰富、界面好看、演示时不用提前装Python环境但摄像头视频流要通过浏览器播放需要额外处理。考虑到项目的核心场景是“打开就能演示”我最终选择了PyQt5。此外PyQt5对OpenCV的VideoCapture集成天然友好读取摄像头帧后直接转换成Qt的QImage显示延迟可以控制在100ms以内。关于界面布局我采用了左右分区左侧主区域当前检测的图像或视频流实时叠加检测框和类别标签。右侧信息栏当前帧的检测统计总垃圾数、各类别名称、置信度、分类建议可回收/厨余/有害/其他、识别时间。底部工具栏打开图片、打开摄像头、拍照检测、历史记录、退出。这样的布局对用户来说信息流是自然的——看到图像、看到识别结果、看到分类建议三步操作不需要切换页面。6.2 摄像头检测导致UI卡顿的根因与解决热搜词里有“ui界面卡顿”这个问题我深有体会。最初版本的摄像头检测逻辑非常简单直接在UI主线程里循环读取摄像头帧然后调用模型推理再刷新界面。结果就是视频画面不流畅点按钮要等半秒才响应拖动窗口时直接白屏。根因是PyQt的主线程被推理操作长期占用。YOLOv5在GPU上推理一帧大约需要30-50ms看起来不多但加上图像预处理、画框、格式转换在CPU上很容易突破200ms主线程一旦阻塞界面事件循环就没法响应。解决方式是引入工作线程。我用QThread单独做一个摄像头线程只负责读帧和推理推理完成后通过信号把结果发给主界面线程去刷新。关键代码如下class CameraThread(QThread): frame_ready pyqtSignal(object, list) # 原始帧 检测结果 def run(self): cap cv2.VideoCapture(0) while not self.isInterruptionRequested(): ret, frame cap.read() if not ret: continue results self.detector.detect_camera_frame(frame) self.frame_ready.emit(frame, results) self.msleep(10) # 控制处理频率避免CPU过载主线程里连接这个信号收到信号后再做画框和界面刷新。这样主线程的事件循环始终畅通窗口拖动、按钮点击不会卡顿。同时要注意frame_ready信号携带的OpenCV图像对象不能跨线程共享后被主线程随意修改最优做法是发射前把图像深拷贝一份避免数据竞争。6.3 画框与分类建议的交互细节检测框的画法我用了QLabel的子类重写paintEvent而不是直接把画好框的图像转成QImage再显示。两种方案都可行但重写paintEvent的好处是图像本身可以保持原始清晰度框和文字用Qt的绘图接口叠加缩放时框的位置能跟着自适应处理。分类建议的逻辑在UI层做了一层映射把10个具体类别映射到四个大类然后显示对应的投放建议。比如检测到“塑料瓶”建议一栏显示“可回收物”同时给出详细提示“塑料瓶属于可回收物请清洗后投入可回收垃圾桶。”这个细节对演示效果提升非常大。用户不关心模型内部怎么分类他们需要的是“这个东西到底扔哪个桶”的明确答案。7. 实测效果与后续扩展方向7.1 性能实测数据整个系统做完后我在三台不同配置的设备上做了对比测试设备推理设备图片检测耗时(ms)摄像头实时FPS说明RTX 2060 6GGPU2830流畅运行推荐配置Apple M1CPU(MPS)1208-10可运行但不够顺滑纯CPU i5-10210UCPU2803-4演示图片可用摄像头会卡这个数据说明如果要在教室或展示场景跑摄像头实时检测一张中端独显还是必要的。如果没有GPU演示图片检测完全没问题但摄像头实时模式就要降低帧率预期了。7.2 从YOLOv5迁移到更新版本模型的思考热搜词里面yolov8/v11/yolo26训练自己的数据集都是高频问题。从YOLOv5迁移到YOLOv8最核心的变化是配置文件格式不同YOLOv8不再需要单独的模型配置文件模型结构在Python代码里直接定义训练命令变成了yolo detect train datadataset.yaml modelyolov8s.pt epochs150 imgsz640 batch16数据集的标注格式完全兼容之前的YOLO格式txt文件可以直接用。实测在同一份生活垃圾数据集上YOLOv8s的mAP比YOLOv5s高1-2个百分点推理速度略慢一点点但差距不大。如果打算上YOLOv8需要关注的是它默认使用Anchor-Free检测头对重复框的表现跟YOLOv5不同需要重新调NMS参数。至于YOLO11这种更新的版本变化更大但除非你需要解决特定问题比如小目标检测能力否则对生活垃圾检测这个任务来说YOLOv5的精度已经完全够用。技术选型的忠告很直白不追新只选稳。7.3 可以继续扩展的三个方向第一边缘设备部署。如果要在Jetson Nano上跑这套系统需要把模型导出成TensorRT格式并用JetPack自带的推理框架加载实测推理速度能跑到30ms以内。这是让系统真正走进社区和校园的关键一步也是我做这个项目时后续最容易见效的优化方向。第二多目标跟踪。目前每帧画面独立检测如果垃圾从画面中移动检测框会跳动。引入ByteTrack或DeepSORT做跟踪不仅检测框稳定还能对每个目标做计数和轨迹统计这个功能对社区垃圾房做流量统计很有价值。第三模型增量更新。社区里的垃圾种类永远不会停下来总会出现训练集里没见过的物品。目前系统的做法是识别为“其他垃圾”兜底但更优的方案是开启在线难例采集——把置信度低于阈值的图片保存下来定期人工补充标注后增量训练让系统慢慢“认识”更多垃圾种类。做完整套系统后我最大的体会是AI项目能不能落地瓶颈往往不在模型精度本身而在数据工程和系统设计的完整度。一个能跑通全流程、界面顺手、交互清晰的系统哪怕模型只是YOLOv5s也比一个精度更高但只能躺在本子里跑命令行脚本的实验项目有价值得多。这套垃圾分类系统的价值正在于此——每一步都没有特别高深的技术但每一步都踩在实处组合起来就是一套真正可演示、可讲解、可扩展的完整作品。