简介:这份资源是基于YOLOv8的垃圾分类识别设计完整项目包,面向深度学习入门者、人工智能课程设计学生及毕业设计开发者,帮助解决垃圾自动分类识别这一典型目标检测任务。包内共11个文件,以Python脚本、YAML配置、预训练权重pt文件、PNG效果图和Markdown说明文档为主,压缩包约10.21MB,涵盖数据集重命名、图像标注辅助、模型配置与识别结果展示等环节。项目围绕可回收、厨余、有害及其他垃圾等类别展开,涉及样本收集、图像标注、数据增强、归一化预处理与多轮迭代训练,最终实现摄像头图像的实时分类。读者可借此获得一套可复用的YOLOv8训练与部署方案,理解边界框标注、数据集管理工具的使用方式,并参考预测效果图与README说明快速上手调试。目前已有32人学习下载,适合作为课程设计或毕业设计的实践参考。
1. 垃圾分类识别为什么选 YOLOv8:从数据集到落地的完整判断
垃圾分类识别这个题目,很多人第一反应是「拿个预训练模型跑一下不就行了」。真做过就知道,坑不在模型本身,而在数据集的类别定义、标注一致性、以及推理时的实际场景差异。YOLOv8 之所以成为这类项目的首选,核心原因有三个:一是它把检测、分割、分类统一到一套框架里,垃圾分类既可以做纯检测(框出物体+类别),也可以做分割(精确到像素边缘),切换成本低;二是 Ultralytics 的工程封装足够成熟,从训练到导出 ONNX、TensorRT 再到边缘设备部署,链路是通的;三是社区资源丰富,预训练权重、数据增强策略、损失曲线可视化这些都有现成方案,不用从零造轮子。
这篇文章面向的是想用 YOLOv8 做垃圾分类识别落地的从业者——不管你是做毕业设计、做嵌入式部署、还是做产线分拣原型,核心诉求都一样:数据集怎么处理、模型怎么训、参数怎么调、部署到板子上怎么跑通。我会按「数据准备 → 训练调参 → 验证排错 → 部署优化」的顺序,把每个环节的可复现步骤和血泪经验讲清楚。如果你手头有一块 RK3588 或者想先在 GTX1660Ti 上把流程跑通,这篇的内容可以直接抄作业。
2. 数据集处理与 YOLOv8 训练环境搭建:从 Labelme 标注到第一轮训练
2.1 垃圾分类数据集的类别设计与标注规范
垃圾分类的类别定义直接决定模型能不能用。常见做法是分四类:可回收物、厨余垃圾、有害垃圾、其他垃圾。但实际标注时会发现,「可回收物」下面有塑料瓶、纸箱、易拉罐、玻璃瓶,类内差异极大;「其他垃圾」又是个筐,什么都往里装。我的建议是:如果你的应用场景是固定品类分拣(比如只分塑料瓶和易拉罐),就按具体物品类别标注,不要硬套四分类;如果是通用场景,四分类可以,但每个类别至少要有 800~1000 张有效标注图,否则模型会严重偏向样本多的类。
标注工具用 Labelme 还是 LabelImg 都行,关键是导出格式。YOLOv8 需要的是 YOLO 格式的 txt 标注:每行class_id x_center y_center width height,坐标全部归一化到 0~1。Labelme 默认导出 JSON,需要转一道。下面这个脚本是我常用的转换逻辑:
import json import os from pathlib import Path # 类别映射:根据你的实际类别修改 CLASS_MAP = { "recyclable": 0, "kitchen_waste": 1, "hazardous": 2, "other": 3 } def labelme_to_yolo(json_dir, output_dir, class_map): """ 将 Labelme JSON 转为 YOLO txt 格式 json_dir: Labelme 标注文件目录 output_dir: 输出 txt 目录 class_map: 类别名到 id 的映射 """ os.makedirs(output_dir, exist_ok=True) for json_file in Path(json_dir).glob("*.json"): with open(json_file, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue # 跳过未定义类别 points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] # 计算归一化中心点和宽高 x_center = (min(xs) + max(xs)) / 2.0 / img_w y_center = (min(ys) + max(ys)) / 2.0 / img_h width = (max(xs) - min(xs)) / img_w height = (max(ys) - min(ys)) / img_h # 裁剪到 [0,1] 防止越界 x_center = max(0, min(1, x_center)) y_center = max(0, min(1, y_center)) width = max(0, min(1, width)) height = max(0, min(1, height)) lines.append(f"{class_map[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") txt_name = json_file.stem + ".txt" with open(os.path.join(output_dir, txt_name), "w") as f: f.write("\n".join(lines)) # 调用示例 labelme_to_yolo("./annotations", "./labels", CLASS_MAP)这段代码的关键点:一是坐标归一化必须用原图宽高,不能搞错;二是要裁剪到 [0,1],Labelme 有时候会标出边界外的点,不裁剪训练时会报错;三是类别映射表要和后续 data.yaml 里的 names 顺序完全一致,否则模型学出来的类别是乱的。
2.2 Ubuntu 20.04 下 YOLOv8 环境配置与 CPU 版本跑通
环境搭建这块,Ubuntu 20.04 是最稳的选择,驱动兼容性好。如果你手头只有 CPU 或者想在服务器上先验证流程,CPU 版本也能跑,就是慢。步骤如下:
# 1. 创建虚拟环境(强烈建议,不要污染系统 Python) conda create -n yolov8 python=3.10 -y conda activate yolov8 # 2. 安装 PyTorch(CPU 版本,如果有 GPU 换成对应 CUDA 版本) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 3. 安装 Ultralytics pip install ultralytics # 4. 验证安装 yolo checksyolo checks会输出环境信息,重点看 Python 版本、PyTorch 版本、以及是否有 GPU 可用。CPU 版本训练速度大概是 GPU 的 1/20 到 1/50,GTX1660Ti 上跑 100 轮可能 2 小时,CPU 上可能要一整天。所以 CPU 版本只适合验证代码能不能跑通,真正训练还是建议上 GPU。
2.3 data.yaml 配置与第一轮训练命令
YOLOv8 的数据配置文件 data.yaml 结构如下:
path: /home/user/dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 test: images/test # 测试集(可选) nc: 4 # 类别数 names: # 类别名,顺序必须和标注时的 class_id 一致 0: recyclable 1: kitchen_waste 2: hazardous 3: other目录结构建议这样组织:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml第一轮训练命令:
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=garbage_v1参数说明:model=yolov8n.pt是最小的预训练权重,适合快速验证;imgsz=640是输入分辨率,垃圾分类场景如果物体较小可以提到 1280,但显存占用会翻倍;batch=16在 GTX1660Ti 上差不多是极限,显存不够就降到 8;patience=20表示 20 轮验证集指标不提升就早停,防止过拟合。训练完成后,损失曲线和 mAP 曲线会自动保存在runs/train/garbage_v1/下,直接看 results.png 就行。
3. 训练参数调优与模型改进:从损失曲线判断问题到注意力机制引入
3.1 损失函数曲线怎么看:三类典型异常与对应调参策略
YOLOv8 训练输出的损失曲线包含 box_loss、cls_loss、dfl_loss 三条。正常情况三条都应该是下降趋势,验证集损失跟随训练集下降但略高。常见异常有三种:
第一种,训练损失下降但验证损失上升,这是过拟合。解决方法是增加数据增强(augment=True是默认开的,可以调mosaic=1.0、mixup=0.1)、减少模型参数量(从 yolov8m 换回 yolov8n)、或者加 dropout。垃圾分类场景如果某些类别样本太少,过拟合会特别明显。
第二种,损失震荡不收敛。通常是学习率太大,lr0=0.01是默认值,但如果你的 batch 很小(比如 4 或 8),学习率要相应降到 0.001 或 0.005。另外检查一下标注有没有问题,比如同一个物体标了多个框、或者框的宽高为 0。
第三种,cls_loss 居高不下。说明分类头学不动,可能是类别不平衡导致的。比如「有害垃圾」只有 50 张图,其他类有 1000 张,模型会倾向于把所有东西都预测成多数类。解决办法是用cls_pw参数给类别加权,或者在数据集层面做重采样。
3.2 用协调注意力机制改进 YOLOv8 检测头
YOLOv8 的检测头是解耦头,分类和回归分开。在垃圾分类场景里,有些物体外观相似(比如塑料瓶和玻璃瓶),单纯靠卷积特征容易混。引入注意力机制可以让模型更关注区分性区域。协调注意力(Coordinate Attention)是一种轻量方案,计算量增加不多,但能提升空间定位能力。
具体做法是在ultralytics/nn/modules/block.py里加一个 CoordAtt 模块,然后在ultralytics/nn/modules/head.py的 Detect 类里插入。代码大致如下:
import torch import torch.nn as nn class CoordAtt(nn.Module): """协调注意力:分别沿 H 和 W 方向做池化,再融合""" def __init__(self, inp, reduction=32): super().__init__() self.pool_h = nn.AdaptiveAvgPool2d((None, 1)) self.pool_w = nn.AdaptiveAvgPool2d((1, None)) mip = max(8, inp // reduction) self.conv1 = nn.Conv2d(inp, mip, kernel_size=1) self.bn1 = nn.BatchNorm2d(mip) self.act = nn.SiLU() self.conv_h = nn.Conv2d(mip, inp, kernel_size=1) self.conv_w = nn.Conv2d(mip, inp, kernel_size=1) def forward(self, x): identity = x n, c, h, w = x.size() x_h = self.pool_h(x) # (n,c,h,1) x_w = self.pool_w(x).permute(0, 1, 3, 2) # (n,c,w,1) y = torch.cat([x_h, x_w], dim=2) y = self.act(self.bn1(self.conv1(y))) x_h, x_w = torch.split(y, [h, w], dim=2) x_w = x_w.permute(0, 1, 3, 2) a_h = torch.sigmoid(self.conv_h(x_h)) a_w = torch.sigmoid(self.conv_w(x_w)) return identity * a_h * a_w插入位置建议在 Detect 头的三个分支之前,对 P3、P4、P5 特征图各加一个 CoordAtt。改完后重新训练,mAP 通常能涨 1~3 个点,但推理速度会降 5%~10%。如果部署在 RK3588 这类边缘设备上,要权衡精度和延迟。
3.3 训练参数含义速查与调参优先级
YOLOv8 的训练参数有几十个,但真正影响大的就那几个。下面这张表是我实际调参时总结的优先级:
| 参数 | 默认值 | 作用 | 调参建议 |
|---|---|---|---|
| lr0 | 0.01 | 初始学习率 | batch 小就降到 0.001~0.005 |
| lrf | 0.01 | 最终学习率系数 | 一般不动,除非损失震荡 |
| momentum | 0.937 | 动量 | 保持默认 |
| weight_decay | 0.0005 | 权重衰减 | 过拟合时提到 0.001 |
| warmup_epochs | 3.0 | 预热轮数 | 小数据集可以降到 1 |
| box | 7.5 | 框损失权重 | 定位不准时提到 10 |
| cls | 0.5 | 分类损失权重 | 分类混淆时提到 1.0 |
| dfl | 1.5 | DFL 损失权重 | 一般不动 |
| mosaic | 1.0 | Mosaic 增强概率 | 小数据集保持 1.0 |
| mixup | 0.0 | Mixup 增强概率 | 过拟合时开到 0.1~0.2 |
| copy_paste | 0.0 | 复制粘贴增强 | 分割任务用,检测可不开 |
调参优先级:先调 lr0 和 batch,再看损失曲线决定要不要加正则化,最后才考虑改损失权重。不要一上来就大改,否则出了问题都不知道是哪个参数导致的。
4. 模型验证与部署前排查:mAP 达标但实际效果差的五个原因
4.1 验证集指标与真实场景的差距分析
训练完看 mAP@0.5 到了 0.9 就以为万事大吉,结果一部署到实际场景就翻车,这是最常见的踩坑。原因通常是验证集和真实场景的数据分布不一致。比如验证集都是白底产品图,实际场景是传送带上的垃圾,光照、角度、遮挡全变了。解决办法是:验证集一定要从真实场景里采,不要用网上下的干净数据集凑数。如果真实场景数据少,至少要做一轮实地拍摄,哪怕只有 200 张,也比没有强。
另一个容易忽略的点是置信度阈值。训练时默认conf=0.25,但实际部署时这个值要重新调。阈值太高会漏检,太低会误检。垃圾分类场景里,误检的代价可能比漏检高(把可回收物分到有害垃圾里,后续处理很麻烦),所以阈值可以适当调高到 0.4~0.5。
4.2 模型导出 ONNX 与 RK3588 部署注意事项
如果要在 RK3588 上部署,标准流程是:PyTorch → ONNX → RKNN。导出 ONNX 的命令:
yolo export model=runs/train/garbage_v1/weights/best.pt format=onnx imgsz=640 opset=12 simplify=Trueopset=12是 RKNN 工具链兼容性最好的版本,simplify=True会做图优化,去掉冗余算子。导出后可以用 Netron 看一下网络结构,确认没有不支持的算子(比如某些自定义注意力模块可能不被 RKNN 支持,需要替换或删除)。
转 RKNN 的步骤大致是:先用rknn-toolkit2加载 ONNX,做量化校准(需要准备 100~200 张校准图),然后导出 rknn 模型。量化时注意:垃圾分类的颜色特征比较重要,如果用 int8 量化导致精度掉太多,可以改用混合量化,把第一层和最后一层保持 fp16。
4.3 推理速度优化:从 GTX1660Ti 到边缘设备的延迟对比
不同硬件的推理延迟差异很大。下面是我实测的一组数据(YOLOv8n,输入 640×640):
| 硬件 | 精度 | 单帧延迟 | 备注 |
|---|---|---|---|
| GTX1660Ti | FP16 | 8~12ms | TensorRT 加速后 |
| RK3588 NPU | INT8 | 25~40ms | 单核 NPU,多核可并行 |
| CPU i7-10700 | FP32 | 120~200ms | 不推荐实际部署 |
| Jetson Orin Nano | FP16 | 15~25ms | TensorRT 加速 |
如果延迟要求高,优先考虑 TensorRT 或 RKNN 量化加速。另外输入分辨率从 640 降到 416 可以提速约 40%,但小物体检测精度会下降,垃圾分类场景里瓶盖、电池这类小目标要谨慎降分辨率。
5. 垃圾分类识别落地避坑:五条血泪经验
5.1 标注不一致导致模型学偏
现象:训练 loss 正常下降,但验证集 mAP 卡在 0.5 上不去,混淆矩阵里某些类别互相混。
原因:多人标注时标准不统一。比如塑料瓶,有人标整个瓶身,有人只标瓶盖;纸箱有人标完整外框,有人只标有字的一面。模型看到同一类物体有完全不同的框,自然学不好。
解决:标注前先定规范文档,明确每个类别的标注边界。标注完成后抽 10% 做交叉检查,IoU 低于 0.5 的框要重新标。如果已经训了一轮发现这个问题,别急着调参,先把标注修干净再训。
5.2 类别不平衡导致小类召回率极低
现象:整体 mAP 看起来还行,但「有害垃圾」这类样本少的类别召回率不到 0.3。
原因:YOLOv8 默认的损失函数对类别不平衡没有特殊处理,样本少的类梯度贡献小,模型倾向于忽略。
解决:三个方案按优先级排——一是数据层面过采样小类,把「有害垃圾」的图片复制多份(注意要做不同的数据增强,不能简单复制);二是用cls_pw参数给类别加权,值设为多数类样本数除以少数类样本数;三是改用 Focal Loss 替换默认的 BCE Loss,但需要改源码,改动较大。
5.3 数据增强过度导致训练不收敛
现象:训练 loss 震荡剧烈,mAP 忽高忽低,甚至出现 NaN。
原因:Mosaic 和 Mixup 同时开太高概率,或者mosaic=1.0加上mixup=0.5,生成的图片语义混乱,模型学不到有效特征。
解决:Mosaic 保持 1.0 没问题,但 Mixup 建议从 0.1 开始试,不要超过 0.3。如果数据集本身已经很大(超过 1 万张),Mosaic 可以降到 0.5。另外copy_paste在检测任务里不要开,那是给分割用的。
5.4 导出 ONNX 后精度下降
现象:PyTorch 模型 mAP 0.9,导出 ONNX 后用 onnxruntime 推理 mAP 掉到 0.7。
原因:通常是 opset 版本不匹配或者某些算子导出时被简化掉了。比如 SiLU 激活函数在低版本 opset 里不支持,会被替换成近似实现。
解决:导出时指定opset=12或更高,加simplify=True让 Ultralytics 自动做图优化。导出后用 Netron 对比 PyTorch 和 ONNX 的结构,看有没有算子被替换。如果还是掉点,试试dynamic=False固定输入尺寸,动态轴有时候会引入误差。
5.5 RK3588 量化后精度暴跌
现象:ONNX 模型在 PC 上 mAP 0.88,转 RKNN 做 int8 量化后 mAP 掉到 0.6。
原因:int8 量化对激活值范围敏感,垃圾分类场景里颜色和纹理特征丰富,量化误差累积后分类头先崩。
解决:一是校准集要覆盖所有类别和光照条件,不能只用几十张图;二是改用混合量化,把分类头的前几层和最后几层保持 fp16;三是如果精度还是不行,放弃 int8,用 fp16 推理,延迟会高一些但精度有保障。RK3588 的 NPU 对 fp16 支持也还行,实测延迟增加约 30%。
6. 从训练到产线:用 TensorRT 加速和置信度后处理把误检压下去
模型训好、部署跑通之后,真正决定能不能上产线的,往往是后处理。垃圾分类场景里,误检的代价很高——把塑料瓶识别成有害垃圾,后续分拣线要停机人工干预。我一般会在推理端加两层过滤:第一层是置信度阈值,第二层是类别特定的 NMS 策略。
置信度阈值不要全局用一个值。我的做法是给每个类别单独设阈值:可回收物 0.45,厨余垃圾 0.5,有害垃圾 0.6,其他垃圾 0.4。有害垃圾阈值最高,因为误检代价最大。这个阈值不是拍脑袋定的,是在验证集上画 P-R 曲线,找 F1 分数最高的点。
NMS 的 IoU 阈值默认是 0.7,但在垃圾堆叠场景里,两个物体挨得很近时会被误抑制。可以降到 0.5~0.6,让重叠的框保留更多。如果用的是 TensorRT 加速,NMS 可以放到 GPU 上做,用EfficientNMS_TRT插件,比 CPU 后处理快 5~10 倍。
TensorRT 加速的完整流程:
# 1. 导出 ONNX(前面已经做过) yolo export model=best.pt format=onnx opset=12 simplify=True # 2. 用 trtexec 转 TensorRT 引擎(FP16 模式) trtexec --onnx=best.onnx \ --saveEngine=best_fp16.engine \ --fp16 \ --workspace=4096 \ --minShapes=images:1x3x640x640 \ --optShapes=images:1x3x640x640 \ --maxShapes=images:4x3x640x640 # 3. Python 推理调用import tensorrt as trt import pycuda.driver as cuda import numpy as np class TRTInference: def __init__(self, engine_path): self.logger = trt.Logger(trt.Logger.WARNING) with open(engine_path, "rb") as f: self.engine = trt.Runtime(self.logger).deserialize_cuda_engine(f.read()) self.context = self.engine.create_execution_context() self.stream = cuda.Stream() def infer(self, input_np): """input_np: 预处理后的 NCHW float32 数组""" # 分配显存、拷贝输入、执行推理、取回输出 # 具体绑定逻辑根据引擎的 binding 名称调整 pass # 使用示例:预处理 -> 推理 -> 后处理(置信度过滤 + NMS)TensorRT 引擎的绑定名称和输出布局跟导出时的配置有关,建议先用trtexec --onnx=best.onnx --dumpLayerInfo看一下输出层的名字和维度。后处理里的框解码要注意:YOLOv8 的输出是[batch, 4+nc, num_anchors],需要转置后再做解码,别搞反了。
最后说一个我踩过的坑:TensorRT 引擎和硬件绑定,在 GTX1660Ti 上生成的 engine 不能直接拿到 Orin 上用,必须重新转。如果产线有多台不同型号的设备,建议在每台设备上单独生成引擎,或者用 ONNX 作为中间格式在设备端首次运行时自动转换。这个流程虽然多一步,但省去了版本不匹配的玄学问题。
希望帮到你。
本文还有配套的精品资源,点击获取