news 2026/9/20 8:55:07

垃圾分类目标检测系统实战:从数据集处理到YOLOv8部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
垃圾分类目标检测系统实战:从数据集处理到YOLOv8部署

简介:一套基于深度学习的垃圾分类目标检测系统源码,面向Python毕业设计、课程实践与深度学习入门人群。项目以目标检测模型为核心,配套后端服务、前端页面与容器化部署配置,帮助学习者快速走通从环境准备到模型推理的完整流程。压缩包共118个文件,大小约7.36MB,主要包含Python脚本、YAML/JSON配置文件、前端小程序代码、图片素材及教程文档,结构清晰便于按模块二次开发。源码内含模型训练与推理脚本、交互式教程notebook及Docker部署配置,既能直接运行验证效果,也方便定制训练自己的垃圾分类场景。资源已有113人学习浏览,适合需要完整项目参考、快速搭建演示系统的开发者。

1. 垃圾分类目标检测系统的真实难点:数据,不是模型

“基于深度学习的垃圾分类目标检测系统”这个题目,在毕业设计里出现频率极高,网上标着“源码”的仓库也不少。但真正把代码跑通,再把摄像头对准一堆混合垃圾时,问题就来了:矿泉水瓶能框出来,但透明塑料袋和一次性餐盒经常漏检;烟蒂太小根本检测不到;玻璃瓶和陶瓷碎片在特征上几乎没有区分度。这说明垃圾分类目标检测和通用目标检测(行人、车辆)有本质区别——类别体系杂、类间相似度高、小目标多。常见做法是把通用目标检测的流程直接套过来,然后用 YOLOv8 训练公开数据集。但整套系统能不能用,拼的不是模型,而是数据组织、标注体系、训练参数和部署细节。这篇文章不讨论某个具体仓库,而是把做这类毕设源码时应该自己掌握的完整路径讲清楚:数据集怎么挑、标注怎么转换、训练参数怎么调、摄像头推理怎么写、最后怎么验收和导出模型。

2. 垃圾分类数据集选型与 YOLO 格式整理

2.1 公开数据集对比:先搞清楚类名体系再动手

垃圾分类数据集现状比较特殊:有的按材料分类(glass、paper、metal、plastic),有的按投放类型分类(可回收、有害、厨余、其他),有的直接是细粒度物品种类(矿泉水瓶、易拉罐、电池)。毕设的展示逻辑通常需要落到“可回收/有害/厨余/其他”这个四分类体系,但模型直接训练四分类很容易混淆——因为同为可回收物,玻璃瓶和铁罐的纹理差异很大;而一个沾了油的塑料餐盒,在视觉上更接近白瓷盘。我一般这样处理:保留细粒度类别作为模型输出,在展示层做一次映射归并。

下面是几个公开数据集的实际差异,这是选型时最关键的判断依据:

数据集类别数标注形式适合场景
TrashNet6 类图像分类标注材料分类演示,转换后可直接用于检测
TACO80 类目标检测框标注野外复杂背景垃圾,细粒度类别全
华为云垃圾分类40 类中文图像分类标注中文类别名、贴近国内生活垃圾
Kaggle Garbage Classification6 类图像分类标注适合快速验证迁移学习流程

挑选时注意两件事:第一,标注框质量比数量重要,很多数据集的框有大量漏标和越界坐标;第二,多个数据集合并时,同一个视觉物体可能同时出现在不同类别名里,比如 TrashNet 的 plastic 和 TACO 的 plastic bottle 不能直接共用 ID。常见的映射规则是把“矿泉水瓶、塑料瓶、塑料容器”这类聚合成一个可回收类,而不是让模型去学“某种塑料的材质”。

2.2 用 Python 脚本把 VOC 标注转成 YOLO 格式

从开源社区拿到的检测数据集,最常见的交付格式是 PASCAL VOC 的 XML 标注。YOLO 系列训练需要的是归一化的 txt 标注文件,一个图片对应一个同名 txt,每行格式是class_id x_center y_center width height,坐标全部归一化到 0~1 区间。下面是我常用的转换脚本骨架:

import os import xml.etree.ElementTree as ET from pathlib import Path # 类别映射:把数据集原有类别名映射到目标检测的 ID CLASS_MAP = { "plastic_bottle": 0, "glass": 1, "paper": 2, "metal": 3, "plastic_bag": 4, "battery": 5, } def convert_voc_xml_to_yolo(xml_path: str, out_dir: str, img_w: int, img_h: int) -> None: tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in CLASS_MAP: continue bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 过滤掉太小的标注框,通常是误标或遮挡过严重的目标 if (xmax - xmin) < 2 or (ymax - ymin) < 2: continue x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 越界保护:坐标可能在边界外,必须 clamp 到 [0, 1] x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) width = min(max(width, 0.0), 1.0) height = min(max(height, 0.0), 1.0) lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") txt_path = Path(out_dir) / (Path(xml_path).stem + ".txt") txt_path.write_text("\n".join(lines), encoding="utf-8") # 用法:遍历 train 和 val 两个目录,分别调用上述函数 # img_w / img_h 从 XML 的 <size> 节点读取,不要在外部写死

这段代码里最容易理解错的地方是宽高取值:YOLO 格式的widthheight是归一化后的框宽框高,不是图片宽高。很多转换脚本直接照搬目标检测框架里的旧代码,忘了x_center必须归一化到图片宽度,跑训练时 loss 直接爆掉。越界保护这一段不是可选项——原始标注经常有画到图片外的框,不 clamp,训练后期会出现莫名其妙的 NaN。类别映射字典建议直接对应到训练数据集的data.yaml里的 class 列表顺序,顺序一旦定义,整个训练过程就不能再改。

2.3 验证集不能随机切:按场景分组拆分

处理视频采集来的垃圾数据时,连续帧高度相似。如果按照图片随机划分训练集和验证集,同一段视频的前后帧会被分到两边,模型在验证集上的表现会虚高,答辩时现场演示效果和指标严重不符。正确做法是按场景或视频片段分组拆分。

from sklearn.model_selection import GroupShuffleSplit # 假设每条样本的路径是 dataset/video_001/frame_0123.jpg # group 取 video_001,保证同一视频的所有帧只进训练集或验证集 def build_group_list(image_paths): groups = [] for p in image_paths: # 按目录名作为场景 ID 是最省事的做法 groups.append(os.path.dirname(p).split("/")[-1]) return groups splitter = GroupShuffleSplit(n_splits=1, test_size=0.15, random_state=42) train_idx, val_idx = next(splitter.split(image_paths, groups=groups))

这里random_state固定下来后,多次重复实验的划分完全一致,跑对比实验时模型权重、数据增强、超参数才能公平比较。群组划分对垃圾分类这种存在场景漂移的任务尤其重要——食堂场景的垃圾以餐盒、纸巾为主,办公室场景以纸张、塑料瓶为主,如果验证集恰好包含了全部食堂数据,模型的真实泛化能力就被高估了。

毕设源码里如果自带数据集,第一件事就是用这种分组方式重新划分训练集和验证集,而不是直接用别人的 txt 标注文件名列表。很多公开源码的标注文件和图片名称对不上,转换完之后用下面的命令检查一遍缺失比例:

ls images/train | wc -l ls labels/train | wc -l # 找出有图无标注、有标注无图的具体文件名 for f in images/train/*.jpg; do n="${f##*/}"; n="${n%.jpg}.txt"; if [ ! -f "labels/train/$n" ]; then echo "MISSING: $n"; fi; done

这个检查必须在训练前完成。我见过不少案例:图片 3000 张,标注只有 2800 份,训练时 YOLO 自动跳过缺失标注的图片,数据量白白少了 7%。

3. YOLOv8 垃圾分类目标检测的训练参数与流程

3.1 模型选型:为什么垃圾分类场景推荐 YOLOv8s

目标检测目前的主流路线是 YOLO 系列,具体到版本选择,YOLOv5、YOLOv8 和 YOLO11 现在各自有稳定用户群。垃圾分类检测这种中型目标为主、类别数量不超过 20 类的任务,YOLOv8s 是平衡性最好的选择:anchor-free 的检测头省去了锚框聚类步骤,C2f 模块在轻量化的前提下保留了多尺度特征表达能力,默认配置下有 11M 左右的参数量,单张 1080Ti 显卡能轻松训起来。对比 YOLOv5,v8 最大的变化是去掉了 anchor 机制,模型输出的解码链路更简单,训练时不需要再关心 anchor 尺寸是否和数据集匹配,这对换数据集的工作流非常友好。

R-CNN 系列和 DETR 这类 Transformer 检测器在垃圾场景不会比 YOLO 强,原因很实在:垃圾图像普遍背景杂乱、目标遮挡严重,这类场景下 anchor-free 的 YOLO 训练收敛更快,部署时对推理框架的兼容性也最好。毕设系统如果涉及实时摄像头识别,YOLOv8s 能轻松跑满 50 FPS 以上,而 DETR 类的实时性明显吃紧。多模态目标检测方案在垃圾辨识上有研究价值,但对毕设周期来说工程量偏大,不建议入坑。

3.2 训练前准备:环境、data.yaml 和预训练权重

训练环境的搭建步骤不能省,但也不需要复杂。我一般先用 conda 建一个独立环境,避免系统 Python 环境被搞乱:

conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu118

这里指定了 CUDA 11.8 的 PyTorch。实际版本要根据本机显卡驱动决定,NVIDIA 驱动版本决定了能用的 CUDA 版本上限。安装完先跑一行命令验证 GPU 是否被正确识别:

python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

数据集配置文件是训练前最容易出错的文件。新建garbage.yaml并放在数据集根目录下:

# 垃圾检测数据集配置文件 path: /data/garbage_dataset # 数据集根目录,绝对路径最保险 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 # 类别列表顺序必须与第 2.2 节的 CLASS_MAP 中的 ID 一一对应 names: 0: bottle 1: glass 2: paper 3: metal 4: plastic_bag 5: battery

names的顺序和标注文件里的 class_id 必须严格对应,这是训练集和验证集共用的唯一字典。改类别名时不要手工一个个改 YAML,直接用文本替换脚本处理,避免手误导致所有类别标签整体错位。

3.3 训练命令与关键超参数

数据集不大(几百张到几千张)的情况下,直接微调 COCO 预训练权重,训练命令如下:

yolo detect train \ model=yolov8s.pt \ data=garbage.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ mosaic=1.0 \ close_mosaic=10 \ patience=20 \ device=0

参数的具体影响我按训练过程的实际观察顺序说明:

  • epochs:垃圾数据通常不会超过 5000 张,100 轮足够。数据量特别少(每类少于 100 张)时,epochs 提到 150,但要在第 80 轮左右盯一下是否开始过拟合。
  • imgsz:输入分辨率。640 是速度与精度的均衡点,小目标多时提到 800 或 960,代价是训练时间增加约 1.6 倍。
  • batch:显存不够就降到 8,但 batch 太小会让 BatchNorm 统计量不稳定,垃圾分类这种数据分布差异大的场景尤其明显。
  • mosaic:数据增强中的马赛克拼接,对垃圾目标的尺度变化和遮挡有很好的鲁棒性提升,默认 1.0 即可。
  • close_mosaic:最后 10 轮关闭马赛克增强,让模型在接近真实分布的数据上做微调,这一步能显著降低验证集 loss,是 YOLO 训练的标准操作。
  • patience:验证指标连续 20 轮不提升就提前停止。这个参数配合results.csv里的验证精度做观察。

训练过程中,我一般盯confusion_matrix.png而不是只盯 loss 曲线。loss 下降只说明模型在训练集上学到了东西,但垃圾分类的类间相似度很高,瓶子和玻璃杯容易互相混淆,单纯等训练结束再看结果会浪费大量时间。训练结束后用 Ultralytics 自带的验证命令看最终指标:

yolo detect val model=runs/detect/train/weights/best.pt data=garbage.yaml

输出里的mAP50-95对垃圾分类任务比较苛刻,因为同类垃圾的框大且形状不固定,一般 mAP50 到 0.85 以上,系统就可以拿到现场试了。

4. 摄像头实时垃圾分类检测的 Python 推理与部署

4.1 用 OpenCV 读取摄像头并接入 YOLO 推理

训练完之后,把模型接到摄像头是毕设源码里最核心的交付部分。常见做法是用 OpenCV 捕捉帧,交给 YOLO 预测,然后把结果画回画面。这里有一个容易踩坑的点:OpenCV 默认读入的图像是 BGR 通道顺序,而 YOLO 训练时用的是 RGB,不转换的话检测框位置基本没影响,但分类置信度会明显下降,特别是绿色和蓝色物体容易互相错认。下面是完整的实时推理逻辑:

import cv2 from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") cap = cv2.VideoCapture(0) # 0 代表默认摄像头 # 设置输入尺寸与置信度阈值,避免画面卡顿和误检过多 model.predict(source="", stream=True, imgsz=640, conf=0.35, verbose=False) while cap.isOpened(): ret, frame = cap.read() if not ret: break # 核心操作:BGR -> RGB,否则颜色类别的置信度会明显下降 rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = model.predict(rgb_frame, imgsz=640, conf=0.35, verbose=False) for r in results: for box in r.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cls_id = int(box.cls[0]) conf = float(box.conf[0]) label = f"{model.names[cls_id]} {conf:.2f}" cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("garbage detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

conf=0.35这个阈值不是固定的。垃圾分类场景下,烟蒂、瓶盖这类小目标置信度天然偏低,调高到 0.5 会大量漏检;但调低到 0.2,一个反光的桌面就可能被识别成玻璃。合理做法是先用 0.3 跑一段视频,观察误检最集中的是哪些类别,再针对性地单独过滤。代码里verbose=False可以减少终端刷屏,否则每帧的推理日志会拖慢循环。

4.2 加入类别过滤与防抖逻辑

直接逐帧推理还有一个问题:分类结果在连续帧上会抖动。一个瓶子在某个角度被识别成 bottle,换个角度置信度跌到阈值以下,画面里就出现目标闪烁。毕设演示时这种抖动非常掉价。解决方案是做一个短窗口的投票机制:

from collections import deque window = deque(maxlen=10) # 保留最近 10 帧的分类结果 # 在每一帧的检测结果里,找出画面中面积最大的目标作为“主目标” # 把该目标的 cls_id 压入 window,取众数作为最终输出 def majority_vote(window): if not window: return None return max(set(window), key=window.count) # 主循环内调用: # largest = 当前帧面积最大的目标类别ID # window.append(largest) # final_cls = majority_vote(window)

窗口长度设为 10 意味着每帧输出会滞后约 300 毫秒,对展示场景来说完全可接受,但能消除大多数单帧误检。这种滑动窗口的思路可以推广到厨余垃圾和其他易混类别,后续做性能优化时,它的计算成本几乎可以忽略。

4.3 输出结果到前端或硬件的转接层

很多毕设源码不止本地显示,还需要把识别结果传给 Web 页面或嵌入了 LCD 的垃圾桶硬件。常见做法是用 FastAPI 封装一个预测接口:

from fastapi import FastAPI from pydantic import BaseModel from ultralytics import YOLO app = FastAPI() model = YOLO("best.pt") class Query(BaseModel): image_path: str @app.post("/detect") def detect(q: Query): results = model(q.image_path, conf=0.35, verbose=False) detections = [] for r in results: for box in r.boxes: detections.append({ "class": model.names[int(box.cls[0])], "conf": round(float(box.conf[0]), 3), "bbox": list(map(int, box.xyxy[0].tolist())) }) return {"detections": detections}

这个接口的设计意图是把模型推理和业务层解耦。前端只需要上传图片路径,拿到类别和坐标后做自己的展示逻辑。注意这里传的image_path是服务器本地路径,如果前端是浏览器,需要改成接收图片上传字节流再交给model.predict处理。

5. 从混淆矩阵到 ONNX 导出:验收模型的三个关键操作

训练完并不是结束,毕设源码最怕的是现场演示翻车。我每次拿到一个训练好的模型,都先做三件事:看混淆矩阵、测小目标短板、导出 ONNX 备用。

第一,看混淆矩阵。Ultralytics 训练结果里会生成confusion_matrix.png,找到矩阵里非对角线的高亮格,那就是模型最容易搞混的类别对。比如 Glass 被识别成 Plastic Bag,说明训练数据里这两个类别的样本形态太接近,或者玻璃类的样本太少。处理方式有两种:针对性补充数据,或者修改data.yaml中该类的权重。Ultralytics 训练命令里可以加class_weights参数,但更稳妥的方式是先用增强手段扩充少数类,比如对玻璃瓶做随机旋转、亮度扰动,让模型学到更多形态。

第二,小目标检测效果不好时,先别急着换大模型。YOLOv8s 在 640 分辨率下对烟蒂这种小目标本身就吃力。常见做法是把imgsz提到 896,然后用多尺度训练:

yolo detect train \ model=yolov8s.pt \ data=garbage.yaml \ epochs=80 \ imgsz=896 \ batch=8 \ close_mosaic=10

输入分辨率提高后,小目标的特征图尺寸变大,检测率会显著上升。代价是训练时间变长且显存占用翻倍,如果 GPU 扛不住,就保持 640 训练,在推理时单独对大图做切片。

第三,导出 ONNX 模型,为部署做双保险。推理端不一定总跑在带 GPU 的机器上,导出 ONNX 后可以用 OpenCV DNN 做纯 CPU 推理:

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640

这个命令在当前目录下生成同名.onnx文件。之后在纯 CPU 环境里用cv2.dnn.readNetFromONNX加载,推理速度比直接调用 YOLO 的 PyTorch 推理快很多,对树莓派、嵌入式开发板这类低算力设备是必要步骤。垃圾分类现场部署时,这个 ONNX 文件可以作为备用推理内核,一旦 GPU 驱动出问题,CPU 模式仍能完成现场演示,不至于整套系统瘫痪。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 8:51:27

基于Python+MySQL+ECharts的图书馆数据可视化系统开发实践

简介&#xff1a;面向毕业设计场景的 Python 图书馆大数据可视化分析系统完整项目包&#xff0c;整合 Python 后端、MySQL 数据库、前端展示与说明文档&#xff0c;解决图书馆数据分散、查询分析依赖手工统计、结果不够直观等问题。系统基于 Pandas、NumPy 完成数据清洗与计算&…

作者头像 李华
网站建设 2026/9/20 8:51:16

cursor-skills 同步 AGENTS.md 后,read 给 Agent 用:Base URL 走 TaoToken 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 8:50:50

NASA开放数据API实战:Python获取与处理航天数据

1. 项目概述&#xff1a;NASA数据接口的价值与应用场景NASA作为全球顶尖的航天机构&#xff0c;其开放数据门户&#xff08;data.nasa.gov&#xff09;提供了超过32,000个数据集&#xff0c;涵盖地球观测、天体物理、气候研究等多个领域。这些数据通过API接口向公众开放&#x…

作者头像 李华