资讯动态

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

发布时间:2026/9/20 8:55:19 来源:尧图企业网站定制
简介一套基于深度学习的垃圾分类目标检测系统源码面向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), encodingutf-8) # 用法遍历 train 和 val 两个目录分别调用上述函数 # img_w / img_h 从 XML 的 size 节点读取不要在外部写死这段代码里最容易理解错的地方是宽高取值YOLO 格式的width和height是归一化后的框宽框高不是图片宽高。很多转换脚本直接照搬目标检测框架里的旧代码忘了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_splits1, test_size0.15, random_state42) train_idx, val_idx next(splitter.split(image_paths, groupsgroups))这里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 显卡能轻松训起来。对比 YOLOv5v8 最大的变化是去掉了 anchor 机制模型输出的解码链路更简单训练时不需要再关心 anchor 尺寸是否和数据集匹配这对换数据集的工作流非常友好。R-CNN 系列和 DETR 这类 Transformer 检测器在垃圾场景不会比 YOLO 强原因很实在垃圾图像普遍背景杂乱、目标遮挡严重这类场景下 anchor-free 的 YOLO 训练收敛更快部署时对推理框架的兼容性也最好。毕设系统如果涉及实时摄像头识别YOLOv8s 能轻松跑满 50 FPS 以上而 DETR 类的实时性明显吃紧。多模态目标检测方案在垃圾辨识上有研究价值但对毕设周期来说工程量偏大不建议入坑。3.2 训练前准备环境、data.yaml 和预训练权重训练环境的搭建步骤不能省但也不需要复杂。我一般先用 conda 建一个独立环境避免系统 Python 环境被搞乱conda create -n yolo python3.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: batterynames的顺序和标注文件里的 class_id 必须严格对应这是训练集和验证集共用的唯一字典。改类别名时不要手工一个个改 YAML直接用文本替换脚本处理避免手误导致所有类别标签整体错位。3.3 训练命令与关键超参数数据集不大几百张到几千张的情况下直接微调 COCO 预训练权重训练命令如下yolo detect train \ modelyolov8s.pt \ datagarbage.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ mosaic1.0 \ close_mosaic10 \ patience20 \ device0参数的具体影响我按训练过程的实际观察顺序说明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 modelruns/detect/train/weights/best.pt datagarbage.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, streamTrue, imgsz640, conf0.35, verboseFalse) 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, imgsz640, conf0.35, verboseFalse) 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()conf0.35这个阈值不是固定的。垃圾分类场景下烟蒂、瓶盖这类小目标置信度天然偏低调高到 0.5 会大量漏检但调低到 0.2一个反光的桌面就可能被识别成玻璃。合理做法是先用 0.3 跑一段视频观察误检最集中的是哪些类别再针对性地单独过滤。代码里verboseFalse可以减少终端刷屏否则每帧的推理日志会拖慢循环。4.2 加入类别过滤与防抖逻辑直接逐帧推理还有一个问题分类结果在连续帧上会抖动。一个瓶子在某个角度被识别成 bottle换个角度置信度跌到阈值以下画面里就出现目标闪烁。毕设演示时这种抖动非常掉价。解决方案是做一个短窗口的投票机制from collections import deque window deque(maxlen10) # 保留最近 10 帧的分类结果 # 在每一帧的检测结果里找出画面中面积最大的目标作为“主目标” # 把该目标的 cls_id 压入 window取众数作为最终输出 def majority_vote(window): if not window: return None return max(set(window), keywindow.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, conf0.35, verboseFalse) 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 \ modelyolov8s.pt \ datagarbage.yaml \ epochs80 \ imgsz896 \ batch8 \ close_mosaic10输入分辨率提高后小目标的特征图尺寸变大检测率会显著上升。代价是训练时间变长且显存占用翻倍如果 GPU 扛不住就保持 640 训练在推理时单独对大图做切片。第三导出 ONNX 模型为部署做双保险。推理端不一定总跑在带 GPU 的机器上导出 ONNX 后可以用 OpenCV DNN 做纯 CPU 推理yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640这个命令在当前目录下生成同名.onnx文件。之后在纯 CPU 环境里用cv2.dnn.readNetFromONNX加载推理速度比直接调用 YOLO 的 PyTorch 推理快很多对树莓派、嵌入式开发板这类低算力设备是必要步骤。垃圾分类现场部署时这个 ONNX 文件可以作为备用推理内核一旦 GPU 驱动出问题CPU 模式仍能完成现场演示不至于整套系统瘫痪。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价