生活垃圾分类这件事真正落到执行层面就会发现光靠人眼和自觉远远不够。小区里四个桶摆在那儿投错的人照样投错后端分拣线上工人一天要弯几百次腰。我最早接触这个方向是想给社区做一套能自动识别垃圾类别的工具——摄像头拍一张系统直接告诉你这属于可回收物还是厨余垃圾顺便把位置框出来。这就是标题里提到的生活垃圾分类目标检测系统底层用 YOLOv8 做检测界面用 PyQt5 搭配套一份标注好的数据集和完整训练代码目标是让一个懂点 Python 的人拿到手就能跑起来。它适合两类人一类是刚入门目标检测、想找个完整项目练手的学生和转行者另一类是真有分类识别需求、想快速验证效果的开发者。整套东西不需要服务器集群一台带独显的普通电脑就能完成训练和推理。1. 项目整体设计与技术选型思路做目标检测项目第一个要拍板的不是写代码而是想清楚这条路走不走得通。垃圾分类识别本质上是一个多类别、形态差异大、遮挡严重的检测任务塑料瓶、纸箱、电池、烟头这些目标在尺寸、颜色、纹理上差别巨大。我一开始也纠结过用分类模型还是检测模型分类只能告诉你这张图里主要是厨余垃圾但实际场景里一张桌上可能同时有易拉罐、纸巾和果皮分类模型直接歇菜。所以检测是刚需必须能定位、能多目标同时输出。1.1 为什么把检测框架定在 YOLOv8YOLO 系列发展到 v8最大的变化是 Ultralytics 把训练、验证、导出、部署这套流程全部收进了一个统一接口一条命令就能完成从数据到模型的闭环。这对个人开发者和小团队来说极其友好你不用再自己写数据加载器、自己拼损失函数、自己搞学习率调度。相比两阶段的 Faster R-CNN 系列YOLOv8 是单阶段检测速度快一个量级在普通显卡上就能跑到实时相比 YOLOv5v8 换成了 anchor-free 的检测头去掉了锚框那套需要手工聚类的麻烦事同时引入了 C2f 结构和解耦头小目标的召回率明显更好一些。模型尺度上YOLOv8 提供 n、s、m、l、x 五个版本参数量从 3.2M 一路到 68.2M计算量从 8.7 GFLOPs 到 257.8 GFLOPs。做垃圾分类这种类别不太多、目标不算极小的任务我实测下来yolov8n 和 yolov8s 是性价比最高的两档。n 版本在 GTX 1660 Ti 这种级别的卡上640 分辨率下能跑到 60 FPS 以上精度对于十来类的分类识别完全够用如果追求更稳的 mAPs 版本多花一倍算力精度通常能涨 2 到 4 个点。m 以上就有点浪费了除非你要识别的是非常密集的小目标。1.2 界面为什么选 PyQt5 而不是别的界面这块其实可选方案不少Web 端、tkinter、PyQt5 都行。我最终选 PyQt5核心原因是它和 OpenCV、PyTorch 这些库能无缝衔接——检测线程里直接用 OpenCV 读帧、模型推理转成 QImage 往 QLabel 上一贴就完事了。如果走 Web 方案你得额外起一个后端服务前后端还要通信传图片本地部署的门槛立刻上去了。tkinter 虽然自带但控件太少做个稍微像样的界面就得自己画还不美观。PyQt5 还有个隐性优势是QThread 多线程模型成熟。检测是计算密集任务如果直接在主线程里跑推理界面会直接卡死鼠标点都点不动。用 QThread 把推理放到子线程通过信号槽把结果回传主线程刷新界面这是标准做法PyQt5 的信号槽机制天生就是干这个的。另外 PyQt5 跨平台Windows、Linux、macOS 上代码基本不用改打包成 exe 也有 PyInstaller 现成的方案。1.3 整个系统拆成哪几块我把项目拆成清晰的三层这样后续维护和二次开发都方便。数据层负责数据集的存放、标注文件的解析、类别名称映射。这一层跟模型训练和界面推理都要打交道所以配置统一放在一个 YAML 文件里两边都读它避免改了一处忘了另一处。算法层封装 YOLOv8 的加载、推理、后处理NMS、置信度过滤、坐标还原。对外只暴露一个detect(frame)接口界面不用关心模型内部怎么跑。交互层PyQt5 界面负责图片/视频/摄像头输入的切换、检测结果显示、参数调节置信度阈值、IOU 阈值、结果保存。这样分层的好处是哪天你想把 YOLOv8 换成别的模型只需要改算法层的实现界面一行都不用动。2. 数据集准备与标注实操目标检测项目里数据集的功夫占七成模型和训练代码占三成。我见过太多人模型调了半天最后发现是标注质量拖了后腿。这块必须认真做别想着偷懒。2.1 类别怎么定义才合理生活垃圾分类在实际政策里是四大类可回收物、厨余垃圾、有害垃圾、其他垃圾。但直接用四大类做检测是行不通的因为同一个大类里物体形态差异太大模型学起来会很混乱。我的做法是按具体物体细分类别然后再建立物体类别 → 垃圾大类的映射关系。具体类别建议控制在 8 到 15 类之间可回收物塑料瓶、易拉罐、纸箱、玻璃瓶、旧书报纸有害垃圾废电池、废灯泡、过期药品盒厨余垃圾果皮、菜叶、蛋壳其他垃圾烟头、一次性纸巾、陶瓷碎片类别不是越多越好。每增加一类你就需要为它准备至少 200 到 300 张有效标注样本否则小样本类别会严重拉低整体 mAP。我早期图省事加了 20 多类结果有几个类别只有几十张图训练完这些类的 AP 几乎是 0还拖累整体指标后来砍到 12 类才正常。数据来源上优先用公开数据集打底比如公开的垃圾分类图像数据集然后再自己补充拍摄。自采的时候要注意光照多样性——室内暖光、室外强光、阴天散射光都要覆盖否则模型换个环境就翻车。摄像头拍的时候尽量模拟真实使用场景别全是在白背景上摆拍那样训练出来的模型到了复杂背景里基本没法看。2.2 标注工具和标注规范标注工具我用的是 LabelImg安装就一行命令pip install labelImg装完终端敲labelImg就启动了。启动后先把格式切到YOLO 格式这样它直接输出归一化的 txt 文件省去后面转换的步骤。快捷键记牢W画框D下一张A上一张CtrlS保存。标注这件事有几个容易被忽略但影响很大的细节我踩过坑之后总结成下面几条框要贴紧目标边缘但留 2 到 3 像素余量。框太松会让模型学到背景噪声框太紧又会切掉目标边缘特征。我一般让框刚好包住物体轮廓留一点点边。遮挡目标标可见部分。一个瓶子的下半截被挡住了就只标露出来的部分不要脑补完整轮廓。模型看到的就是像素你标了它也学不到看不见的部分。同类物体的边界要一致。比如塑料瓶有的人标到瓶盖有的人不标瓶盖这种不一致会让模型困惑。团队协作时一定要先统一定义。小目标千万别漏标。烟头、药片这类小目标最容易漏而漏标比标错还糟糕因为它会让模型把正样本当负样本学。一张图里同类目标全部标出来。不要只标最明显的那个剩下的当没看见这会制造大量假负样本。标注完成后文件目录结构要整理成标准形式方便 YOLOv8 直接读取dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages 和 labels 下的文件名要一一对应只是后缀不同jpg 对 txt。这个对应关系千万不能错位否则训练时会出现图像和标签对不上的诡异现象损失曲线看着在降但 mAP 死活上不去。2.3 数据集划分与增强策略划分比例我常用8:1:1训练:验证:测试数据量特别少的时候用 7:2:1。划分时要保证每个类别的样本在三个集合里都有分布不能出现某个类别只在训练集里的情况。可以用脚本按类别分层抽样别偷懒直接按顺序切很容易切出分布不均。data.yaml 这个文件是训练时模型读取配置的入口内容大致长这样path: ./dataset train: images/train val: images/val test: images/test nc: 12 names: [plastic_bottle, can, carton, glass_bottle, newspaper, battery, bulb, medicine_box, peel, leaf, cigarette, tissue]注意nc是类别数量names的顺序必须和标注时 LabelImg 里类别 ID 的顺序严格一致。这里最容易出错——LabelImg 里类别列表的顺序决定了 txt 文件里那个数字代表什么如果 data.yaml 里的 names 顺序变了标签就全乱了模型学出来的结果会莫名其妙。数据增强方面YOLOv8 训练时默认就开启了 Mosaic、MixUp、HSV 色彩抖动、随机水平翻转、随机缩放这些。Mosaic 会把四张图拼成一张让小目标获得更多出现机会同时也变相增大了 batch 的有效规模。我个人经验是Mosaic 对垃圾分类这类任务提升明显因为小目标烟头、药片在单张图里占比小拼图之后它们在小图里反而变大了模型更容易学到。不过 Mosaic 在训练最后阶段会关掉close_mosaic参数控制因为拼图后的图像分布和真实图像有差异最后十几轮用真实图像收尾效果更好。3. YOLOv8 模型训练全流程环境和训练这块我尽量写得能直接照着敲。很多人卡在环境上其实只要版本对上了后面基本一路顺。3.1 环境搭建与依赖安装Python 版本建议 3.8 到 3.10太新的版本有时候 PyTorch 还没跟上会踩兼容性的坑。我用 conda 建独立环境避免和系统里的包打架conda create -n yolo python3.9 -y conda activate yolo然后是 PyTorch。这里有个关键点PyTorch 版本必须和你的 CUDA 驱动匹配。先nvidia-smi看一眼驱动支持的 CUDA 版本比如显示 CUDA 11.8那就装 cu118 版本的 torchpip install torch torchvision --index-url https://download.pytorch.org/whl/cu118接着装核心依赖pip install ultralytics opencv-python pyqt5 labelImg装完验证一下yolo version能打印版本号就说明 ultralytics 装好了。再进 Python 敲import torch; print(torch.cuda.is_available())返回 True 才说明 GPU 可用。如果返回 False多半是 torch 版本和 CUDA 对不上或者显卡驱动太旧这一步一定要先解决不然训练会退化到 CPU慢到你想砸键盘。注意装完 PyQt5 之后如果运行界面提示缺少平台插件通常是环境变量的问题检查一下QT_QPA_PLATFORM_PLUGIN_PATH有没有被其他软件改乱。3.2 训练命令与关键参数真正开始训练就是一条命令的事yolo detect train datadataset/data.yaml modelyolov8n.pt epochs150 imgsz640 batch16 device0 workers8看着简单但每个参数都直接影响结果我逐个说明它们的取舍逻辑。参数含义我的常用值调整思路model预训练权重yolov8n.pt从官方 COCO 预训练权重起步比从零训练收敛快得多epochs训练轮数150数据少就减数据多就加配合 patience 早停imgsz输入分辨率640小目标多就上 800 或 960代价是显存和速度batch批大小16显存够就往上加不够就降到 8 或 4lr0初始学习率0.01默认值通常够用不收敛再调patience早停耐心值5050 轮没提升就停省时间device训练设备00 表示第一块 GPUCPU 训练写 cpuworkers数据加载线程8Windows 上设太高容易卡4 到 8 比较稳迁移学习这一步千万别跳过。用官方在 COCO 上预训练好的权重起步相当于模型已经学会了什么是边缘、什么是纹理、什么是物体这些通用特征你只需要让它适应垃圾分类这个具体任务。我对比过从预训练权重起步30 轮左右 mAP 就能到 0.85 以上从零训练100 轮可能还在 0.5 附近晃悠差距不是一点半点。3.3 损失曲线怎么读什么时候该调参训练启动后runs/detect/train/目录下会生成结果文件和权重。loss 曲线是判断训练健康度最直观的东西YOLOv8 会输出三条损失box_loss边界框回归损失、cls_loss分类损失、dfl_loss分布焦点损失v8 特有的。三条损失整体都该是下降趋势前期降得快后期趋于平缓。如果某条损失一直高位震荡不降说明对应模块出了问题。box_loss 不降通常是标注框质量差或者目标尺度差异太大考虑重新检查标注。cls_loss 不降可能是类别不平衡某个类别样本太少或者类别定义有重叠导致模型混淆。验证集损失开始上升而训练集损失还在降这是过拟合的典型信号该早停或者加数据增强了。我一般还会画 mAP 曲线来看效果yolo detect train结束后会生成results.png里面包含 mAP50 和 mAP50-95 的走势。mAP50 到 0.9 以上、mAP50-95 到 0.6 以上对于垃圾分类这类任务就算比较理想了。如果 mAP 卡住不动先别急着调模型回去看数据——十有八九是数据的问题。3.4 模型评估与导出部署训练完先用验证集跑一遍评估yolo detect val modelruns/detect/train/weights/best.pt datadataset/data.yaml它会输出每个类别的 Precision、Recall、mAP以及混淆矩阵。混淆矩阵特别有用能一眼看出模型把哪两类搞混了。我之前做的时候发现模型老把玻璃瓶和塑料瓶混淆看了混淆矩阵确认后专门补了一批不同光照下的玻璃瓶样本这类混淆就明显减轻了。评估满意之后导出模型。如果只是想在自己的 PyQt5 程序里跑直接加载.pt权重最简单如果要在其他框架里部署可以导出 ONNXyolo export modelbest.pt formatonnx opset12 simplifyTrue导出 ONNX 时simplifyTrue会做一层图优化推理能快一点。如果想进一步压榨速度可以导出 TensorRT 引擎但这需要目标机器上装好对应的 TensorRT 环境跨机器部署时要注意版本一致性。4. PyQt5 界面设计与系统集成界面是整个系统露在外面的脸丑不丑另说但一定要稳、要流畅。我见过不少项目模型精度很高结果界面一卡一卡的用户体验直接归零。4.1 界面布局怎么搭主窗口我用 QMainWindow整体分成左右两块。左边是显示区一个大的 QLabel 用来贴检测结果图下面放一行状态文字显示当前帧率、检测到的目标数量。右边是控制区从上到下依次是输入源选择图片、视频文件、摄像头三个按钮参数调节置信度阈值和 IOU 阈值的 QSlider实时生效操作按钮开始检测、暂停、保存结果检测结果列表QListWidget 显示每个检测框的类别和置信度布局用 QHBoxLayout 套 QVBoxLayout 组合比绝对定位靠谱得多窗口缩放时控件会自动调整。这里有个小技巧给主显示区设一个固定的最小尺寸避免用户把窗口拖得太小导致图片被压扁变形。4.2 多线程是界面的生命线前面说过推理不能放在主线程。具体实现是定义一个继承 QThread 的检测线程类在它的run方法里循环读帧、推理然后通过pyqtSignal把检测结果图像 目标列表发回主线程。这里有个信号传参的坑pyqtSignal 传递的数据类型要提前声明好传 numpy 数组的时候建议先转成 QImage 或者用 object 类型声明否则可能报类型不匹配。我最早直接传 ndarray程序时好时坏后来统一转成 QImage 再传就再没出过问题。线程的启停控制也要做细。用户点暂停的时候不能直接 terminate 线程会崩而是在 run 循环里检查一个标志位标志位为 False 就跳过推理只读帧或者直接等待。停止的时候要把线程 join 回来再退出程序不然会留下僵尸进程。4.3 那些让人抓狂的界面问题PyQt5 的环境问题特别多我挑几个最典型的说说。第一个是 OpenGL 导致界面无显示。有些机器上跑 PyQt5程序启动了但是没有窗口或者一片黑。这通常是 OpenGL 驱动的问题尤其是虚拟机、远程桌面或者显卡驱动异常的环境。解决办法是在程序最开头、创建 QApplication 之前加上软件渲染的回退设置import os os.environ[QT_OPENGL] software # 或者在 QApplication 之前设置 from PyQt5.QtCore import Qt, QCoreApplication QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL)注意这段代码必须写在QApplication(sys.argv)之前写在后面就没用了。第二个是分辨率适配。在高分屏4K上界面控件会变得极小字都看不清。PyQt5 从 5.6 版本之后支持自动缩放在 QApplication 创建前加上QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)这样界面会跟着系统缩放比例走。如果还不行可以手动设置app.setAttribute或者调QApplication.setStyle。第三个是打包后的运行问题。用 PyInstaller 打包时PyQt5 和 PyTorch 的动态库经常被漏掉导致打包出来的 exe 在别人机器上跑不起来。我的做法是加--collect-all参数把相关库全部收进去打包出来的体积会大一些几百 MB 很正常但胜在稳。5. 常见问题与排查技巧实录做这个项目的过程中踩的坑不少我把它们整理成速查表遇到问题可以直接对号入座。现象可能原因排查与解决训练 loss 正常但 mAP 为 0data.yaml 里 names 顺序和标注不一致检查 LabelImg 类别顺序核对 data.yaml图像和标签对不上images 和 labels 文件错位按文件名一一核对写脚本校验CUDA out of memorybatch 太大或分辨率太高降 batch、降 imgsz或开梯度累积GPU 利用率低数据加载是瓶颈增大 workers把数据集放 SSD界面点开始就卡死推理在主线程改用 QThread 子线程程序启动无窗口OpenGL 驱动异常设置 AA_UseSoftwareOpenGL高分屏界面模糊未开高 DPI 缩放加 AA_EnableHighDpiScaling检测框位置偏移坐标还原时缩放比例算错检查原图尺寸与输入尺寸的映射小目标漏检多分辨率不够提高 imgsz或补小目标样本模型换机器精度下降归一化参数不同统一预处理流程固定参数除了表里这些还有两个我特别想强调的经验。一是阈值不是定死的要按场景调。置信度阈值默认 0.25实际用的时候发现垃圾分类场景下如果关注召回率宁可多报也别漏可以降到 0.15 到 0.2如果关注准确率报出来的必须对就提到 0.4 以上。IOU 阈值控制重叠框的合并目标密集时适当调高。这两个参数我在界面上做成了滑块让用户可以实时调比写死在代码里灵活太多。二是数据永远是第一优先级。我做过一个对比同样的模型、同样的训练参数只是把数据集的标注质量提升了一个档次补漏标、修错误框mAP 直接涨了 6 个点。而调参、换模型结构带来的提升往往只有一两个点。所以当你觉得效果不行的时候先别动代码去数据里找问题。还有一点关于推理速度的。如果你在 GTX 1660 Ti 这类卡上跑yolov8n 加 640 分辨率单帧推理大概 10 到 15 毫秒视频流完全带得动。但要注意第一次推理会特别慢因为模型要加载和预热从第二帧开始才是真实速度。做实时摄像头检测时可以在启动线程后先喂一帧空白图预热避免用户点开摄像头看到前一两秒卡顿。这套系统搭下来从数据采集标注到训练到界面我一个人大概花了两周多的业余时间。如果你只是想快速跑通用公开数据集加 yolov8n 预训练权重一两天就能出个能演示的版本。真正费时间的是数据打磨和界面细节。我个人在实际操作中的体会是先跑通最小闭环再逐步优化——一开始别追求完美精度和华丽界面先让输入图片 → 输出检测框这条路走通后面每优化一处都有正反馈心态会好很多。哦对了如果你用的是 Mac训练会比较难受MPS 后端对 YOLOv8 的支持不如 CUDA 完善建议还是找台带 N 卡的机器来练手。