☰
基于YOLOv8的垃圾桶满溢检测:从数据标注到部署的完整落地路径
2026/10/1 9:27:38 网站建设 项目流程

简介:基于YOLOv8的智能垃圾桶满溢检测项目,面向计算机视觉、深度学习相关专业的毕业设计或课程设计场景,内置完整源码、可视化界面、配套数据集与部署说明,可直接运行,适合需要快速落地项目的学生或开发者。压缩包共八个文件,包含三个Python脚本(覆盖界面展示、视频检测、训练入口)、三个模型权重文件(预训练权重与训练所得权重)以及两个说明文档,整体约15.91MB,结构紧凑,便于下载后快速上手。目前已吸引44人浏览学习。项目代码经过完整测试,运行后可输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,覆盖模型训练到评估的主要环节,答辩展示时能提供扎实的过程证据,适合直接作为课题交付或在基础上扩展功能。

1. 用YOLOv8做垃圾桶满溢检测:一份能拿去当毕设的完整落地路径

拿到《基于YOLOv8的智能垃圾桶满溢检测》这个题目,很多同学的第一反应是解压压缩包直接看源码,但最划得来的打开方式其实是先想清楚一件事:满溢检测本质上是在监控画面里判断垃圾桶的垃圾堆积程度是否已经触发清运需求。它解决的是人工巡检成本高、反馈不及时的问题,这也是它适合毕设的原因——目标检测、数据集制作、可视化界面、部署演示四条线全都能覆盖。这篇文章不猜源码里写了什么,只按常规毕设的完整技术闭环讲透选型理由、数据准备、训练参数、常见坑和演示方法,新手能照着复现,熟手能查缺补漏。

2. 满溢检测为什么锁死YOLOv8:场景约束、模型选型与标签设计

2.1 垃圾桶场景为什么不是"分类"而是"检测"问题

先看一个容易走偏的选题点:垃圾桶满溢检测,到底该用分类模型还是检测模型?很多人第一反应是"这不就是判断满没满",于是直接上ResNet做二分类。真实场景里这套做法会很快翻车,原因在摄像头视角。

常见部署位置有两个,一个是垃圾桶正上方俯拍,另一个是侧面斜下方拍摄。无论哪种角度,画面里通常不止一个垃圾桶,而且桶的位置在画面里是固定的,变化的只有垃圾的高度、颜色和轮廓。分类模型能回答"这张图里有没有满溢",但它回答不了"哪个桶满了"。起步可以不管,一旦现场有三个并排桶,方案就立不住了。检测模型能同时给出位置框和类别,后续界面展示、联动告警、按桶编号输出记录,全部依赖这个位置信息。

另一个原因是鲁棒性。垃圾桶外有行人、车辆、树木穿过,分类模型会把整张图的全局特征拿来判断,很容易被干扰;检测模型只对目标区域内的特征做判定,背景干扰的影响天然更小。所以这个选题的正确起点是目标检测,不是图像分类。

2.2 YOLOv8对比YOLOv5和SSD:毕设怎么选回本的模型

确定要用检测模型后,候选就是SSD、YOLOv5、YOLOv8这几套。SSD是经典但吃亏在要手动调anchor,它把anchor的宽高比、尺度当成超参暴露给你,实际调起来非常玄学,不同数据集要重新试,毕设周期根本耗不起。YOLOv5和YOLOv8之间,能力差距不是质变的,但对毕设来说YOLOv8有几个细节更省事:网络结构上v8把C3模块换成了C2f,梯度回传路径更丰富,小目标特征保留更好;检测头改成anchor-free的decoupled head,分类和回归分支分开,收敛更快;标签分配用的是TaskAlignedAssigner,不需要你手动指定anchor尺寸。

从硬件角度算一笔账。毕设实验室常见的是GTX 1660 Ti这种6GB显存卡,YOLOv8s在这个卡上跑batch为8、imgsz为640是能稳定落地的;如果只有CPU,YOLOv8s在Ubuntu 20.04上搭CPU环境也能训练,只是慢,但推理做演示完全够用。相比之下SSD的精度撑不起满溢这种"垃圾边缘不规则"的检测目标。还有一个网络结构上的得分点:答辩时被问到"为什么选YOLOv8",你至少能说出C2f、decoupled head、anchor-free这三个词背后的设计动机,而不是只会说"精度高"。

2.3 标签怎么定:一个框解决,还是两段式检测

标签体系是这个项目里最容易被低估的设计决策。常见做法有三种思路:只检测垃圾堆、检测垃圾桶+垃圾堆、先检测桶再判断填充高度。只检测垃圾堆的问题在于,垃圾桶本身没有参与模型逻辑,一旦桶被移走或者视角换了,模型不知道桶在哪,误报率很高;先检测桶再做高度判断听起来严谨,但实现里要额外维护一个视野映射关系,毕设工作量直接翻倍。

我一般推荐做两个类别:bin(垃圾桶)和overflow(满溢垃圾)。overflow框选的是桶内垃圾堆积超出正常水平、明显溢出桶口的区域,模型只要在画面里同时检出bin和overflow,就判定为"满溢"。这个定义让标注直观、指标可解释,答辩时也说得清。

data.yaml 的常规写法:

path: data train: images/train val: images/val names: 0: bin 1: overflow

注意path建议写成绝对路径,否则换机器跑的时候,相对路径找不到数据会直接报AssertionError: train dataset not found。names的编号必须和标注文件里的类别编号一致,这是训练环节最常出问题的地方。

3. 解压数据集后的第一件事:标注自查与最小环境搭建

3.1 数据集五步自查:先别急着开训

拿到一份现成数据集,直接扔进 YOLOv8 开训是风险最高的操作。别人的标注规范和你以为的很可能不是一回事。我每次拿到新数据都会先做五件事,全做完再碰训练命令。

第一步,统计样本总量和类别分布。用 Python 快速扫一遍:

import os from collections import Counter labels_dir = "data/labels/train" counter = Counter() total_boxes = 0 for fname in os.listdir(labels_dir): if not fname.endswith(".txt"): continue with open(os.path.join(labels_dir, fname), "r", encoding="utf-8") as f: for line in f: cls = int(line.strip().split()[0]) counter[cls] += 1 total_boxes += 1 print("样本文件数:", len([f for f in os.listdir(labels_dir) if f.endswith(".txt")])) print("各类别框数:", dict(counter)) print("总框数:", total_boxes)

这个脚本的作用是确认类别编号是从 0 开始、没有出现cls超出names范围的情况。常见问题是类别编号从 1 开始,或者标注文件里混了-1之类的占位符号。顺手统计 box 总数,如果overflow的框数连bin的十分之一都不到,后面训练基本可以预见满溢类别会漏检。

第二步,把标注画到图上,肉眼抽查。这一步能发现很多"统计看不出来"的问题:框是不是把整只垃圾桶都框进去了、垃圾堆框是不是标到了桶外、有没有框完全偏离目标。画一张抽查图不复杂,用 OpenCV 读图、画矩形、保存即可,抽查 20-30 张就够了。

第三步,检查标签格式。YOLO 的 txt 格式是class x_center y_center width height,四个数值都是归一化到 0-1 之间的浮点数。如果看到的坐标是整数,大概率是 Pascal VOC 或 COCO 转出来的中间格式,必须转。

第四步,看图像尺寸分布。有些数据集混着 1920x1080 和 640x480 的图,YOLOv8 训练时会做 letterbox 缩放,短边被严重拉伸会影响小目标检测。要么统一分辨率,要么训练时把imgsz调到和主流尺寸接近。

第五步,检查 train/val 划分方式。如果数据集是从监控视频抽帧来的,必须确认同一段视频的连续帧没有被同时分进 train 和 val。这个泄漏导致的假象非常隐蔽,后面避坑章专门讲。

3.2 环境组合:Ubuntu 20.04 CPU 版和 GPU 版的差异

环境搭建是老生常谈,但对这个项目,版本组合直接决定后续三天是顺利还是折腾。很多人在 Ubuntu 20.04 上搭 YOLOv8 的 CPU 环境时,照着网上的教程装了个完整的 CUDA toolkit,其实 CPU 推理完全不需要 CUDA,装了反而可能和驱动冲突。

可靠的做法是直接用 conda 建独立环境,按需选择 torch 版本:

conda create -n yolo python=3.9 -y conda activate yolo pip install ultralytics # CPU 版本 pip install torch==2.0.1 torchvision==0.15.2 # GPU 版本(先确认驱动支持的 CUDA 版本) pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118

CPU 版和 GPU 版最大的差别就在torch的安装来源。CPU 版下载体积小、不会报CUDA error: no kernel image;GPU 版要先看nvidia-smi里 Driver 版本支持的 CUDA 版本号。GTX 1660 Ti 装 CUDA 11.8 的 torch 是常规操作,太新的 cu121 也能用,但没必要。

还有一个坑:不要直接pip install -r requirements.txt覆盖 torch。项目里给的 requirements.txt 往往锁的是作者本机的版本,你一旦装了,torch 可能被降级或升级到和你显卡不匹配的版本。正确顺序是先把 torch 装好,再装 ultralytics,最后才考虑 requirements 里剩余的辅助包。

3.3 labelme标注转YOLO格式:那段转换脚本要注意的边界

不少数据集是用 labelme 标注的,导出的是 JSON 文件,里面存的是多边形点坐标。YOLO 需要的是归一化中心点和宽高。转换脚本的逻辑不难,边界情况却很磨人。

import json import os import glob CLASS_MAP = {"bin": 0, "overflow": 1} def convert_labelme_to_yolo(json_path, out_dir, image_size): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_w, img_h = image_size lines = [] for shape in data["shapes"]: label = shape["label"] if label not in CLASS_MAP: continue points = shape["points"] # [[x1,y1],[x2,y2],...] xs = [p[0] for p in points] ys = [p[1] for p in points] xmin, xmax = min(xs), max(xs) ymin, ymax = min(ys), max(ys) # 归一化到 [0,1] x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{CLASS_MAP[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") out_path = os.path.join(out_dir, os.path.basename(json_path).replace(".json", ".txt")) with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 调用示例(注意 image_size 要和原图一致,否则坐标全偏) for jp in glob.glob("labelme_data/*.json"): convert_labelme_to_yolo(jp, "yolo_labels", image_size=(1920, 1080))

这个脚本有三个边界必须检查:一是image_size必须和原图真实尺寸一致,很多 JSON 里不存尺寸,你得从原图读,否则 x_center 会算错;二是points里如果只有一个点(有些 labelme 版本支持单点标注),min(xs)和max(xs)相等,width 算出来是 0,这种样本必须跳过;三是类别编号 0 在 YOLO 里的含义是"第一类",如果你的 CLASS_MAP 把空标签设成 0,会出现所有框都变成背景。转完之后随机抽几个 txt,人工对照原图确认坐标没有整体偏移。

4. 训练命令与参数:从跑通到能用,还差哪三步

4.1 一行训练命令和参数调法的含义

数据集自查完、环境装好后,训练只是命令的问题。但"能跑"和"能用"是两回事。最小可用的训练命令是这一行:

yolo detect train data=data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=8 device=0

拆开看每个参数的取舍。model=yolov8s.pt表示加载 COCO 预训练权重,对毕设来说这个选择比从头训练划算得多,收敛快、mAP 高,而且yolov8s是体积和精度平衡点。epochs=100是常见区间,60 轮基本能看出模型行不行,但满溢检测这种目标外观差异不大、同一个桶在一天不同光照下长相差很多的任务,100 轮更稳。imgsz=640是默认值,如果你的数据集里有大量小目标,可以试 960,但 GTX 1660 Ti 上显存会吃紧,训练时间也变长。batch=8是 6GB 显存的常见上限,显存报错CUDA out of memory时先减到 4,这是最直接的解决方式。

device 参数要注意:只有一块 GPU 时写device=0,没有 GPU 时删除该参数或写device=cpu。CPU 训练不是不能跑,只是 100 轮可能要好几个小时,建议先用epochs=10验证数据加载和 loss 计算没有报错,再开长训练。

训练结束后,weights 目录下会有best.pt和last.pt,评测和导出一律用best.pt。看训练是否正常,不要只看终端输出的那个 mAP 数字,要进入下一个环节看曲线和混淆矩阵。

4.2 判断训练成果的量化指标:loss曲线、mAP50、混淆矩阵

很多同学训练完只盯着一张results.png,其实 YOLOv8 训练过程会自动保存results.csv,里面有每一轮的 train loss、val loss、mAP50、mAP50-95 等完整记录。你可以直接用 pandas 画出 loss 曲线,这是毕设报告里最有说服力的素材之一。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") # 列名可以打印 df.columns 确认,ulogic 版本不同列名略有差异 plt.figure(figsize=(10, 6)) for col in ["train/box_loss", "val/box_loss"]: plt.plot(df["epoch"], df[col], label=col) plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.grid(True) plt.savefig("loss_curve.png", dpi=150)

判断标准很简单:val loss 在下降说明模型在学;val loss 先降后升而 train loss 一直降,是过拟合的信号,这时候要么加数据增强、要么提前停。看 mAP50 时,垃圾桶满溢检测的 mAP50 到 0.85 以上才算基本可用,低于 0.7 说明数据或标签有问题,调参救不回来。

混淆矩阵同样重要,YOLOv8 会在results/confusion_matrix.png里输出归一化混淆矩阵。重点看overflow类别是不是大量被预测成bin,如果是,说明两个类别的形态学区分度不足,需要在标注阶段把 overflow 框选得更严格,让正负样本边界清晰。

4.3 可视化界面拆解:视频流、推理、结果展示之间的结构

给项目加可视化界面,最常见的路线是 PyQt5、Tkinter 或者 Flask Web 页面。不管用哪种,好的结构都是三层:视频流读取、模型推理、结果渲染。如果源码自带的界面把这三层写在一个文件里,建议你拆开,这样后续换模型、加功能都好改。

我一般习惯保存一个独立的推理脚本,界面直接调用它,这样界面调试和模型调试互不干扰:

import cv2 from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") cap = cv2.VideoCapture("test_video.mp4") # 换成 0 可以读取摄像头 while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model.predict(frame, conf=0.5, imgsz=640, verbose=False) for r in results: boxes = r.boxes for box in boxes: cls = int(box.cls[0]) conf = float(box.conf[0]) x1, y1, x2, y2 = map(int, box.xyxy[0]) label = f"{model.names[cls]} {conf:.2f}" cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255) if cls == 1 else (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255) if cls == 1 else (0, 255, 0), 2) cv2.imshow("YOLOv8 Trash Detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

这段代码的关键是model.predict的conf参数。满溢检测场景下,conf=0.5是常见起点,如果漏检明显就降到 0.3;如果现场误报太多就调高到 0.6。分类别调阈值是更精细的做法:bin用 0.5,overflow用 0.3,因为漏掉一个满溢桶比错报一个空桶代价更大。界面上建议把满溢状态额外弹窗或者置红色高亮,仅仅画一个框在毕设答辩现场不够直观。

5. 避坑:数据集与部署阶段最常翻车的5个问题

5.1 现象一:训练 loss 直接 NaN

现象:训练刚开始几轮,终端输出里box_loss变成nan,之后一直不恢复,模型权重直接报废。

原因:最常出在数据标注上。某个标注文件里出现了width或height为 0,或者归一化坐标超出 0-1 范围,导致 loss 计算出现无穷值。另一个可能是学习率设得过大,用了项目里改过的lr0=0.1之类激进参数。

解决:先跑一个数据清洗脚本,扫描所有 txt 里坐标数值,把width<=0或height<=0或坐标不在[0,1]区间的文件挑出来。学习率方面,把lr0改回默认的0.01。如果还无法恢复,尝试降低 batch 大小,某些显卡驱动下 batch 过大也会触发数值溢出。

5.2 现象二:训练 loss 正常下降,但 mAP50 不到 0.5

现象:train loss 一直在降,val loss 也在降,模型训练过程看起来一切正常,可验证集 mAP50 始终上不去。

原因:最大的嫌疑是 train/val 划分泄漏。如果数据集来自同一段监控视频按帧抽取,相邻帧几乎一样,模型在训练帧上已经见过验证帧的内容,但对象遮挡、光照变化仍然让它泛化不了。另一种原因是标注框本身就画错了,模型终究没法学到正确的目标定义。

解决:按时间序列划分数据,取前 80% 的帧做 train、后 20% 做 val,绝不随机打乱。重新检查标注框是否贴住目标边缘,尤其注意垃圾堆的框是否把大量背景墙划进了正样本。

5.3 现象三:满溢目标几乎全部漏检

现象:模型能很好检测垃圾桶,但满溢类别基本不打框,偶尔出的框置信度也极低。

原因:类别不平衡。overflow样本数量远小于bin,模型学习到的先验偏向多数类。这是垃圾满溢场景的通病——满溢状态持续时间短,采集到有效样本本身就难。

解决:优先补数据,把满溢样本增加上来,这是最有效的。如果时间不允许,可以给overflow类别设更高的 loss 权重,YOLOv8 里可以用cls参数调分类 loss 比重。还有一个低成本的土办法:把满溢判定阈值降低、在接口逻辑里要求"检测到 bin 的同时检测到 overflow 才触发告警",这样类别没被模型漏掉,误报也能被抑制。

5.4 现象四:CPU 环境推理太慢,界面卡成 PPT

现象:模型训练完,在 CPU 机器上跑可视化界面,推理一帧要几百毫秒,视频根本带不动。

原因:YOLOv8 自带 PyTorch 推理在 CPU 上效率不高,且大尺寸输入会显著放大耗时。很多人直接把 1920x1080 的原图送进模型,imgsz 没限制,耗时自然爆炸。

解决:推理时把 imgsz 固定为 640,模型内部会做 letterbox,输入尺寸降下来,速度提升明显。如果还是慢,把模型导出成 ONNX 用 ONNX Runtime 跑 CPU 推理,通常能快 2-3 倍。界面设置里加一个"推理分辨率"选项,默认 640,答辩现场可以根据电脑性能实时调整。

5.5 现象五:可视化界面显示的框和视频画面明显错位

现象:摄像头画面里垃圾桶在左上角,但模型画出来的框标在画面中央,目标位置完全对不上。

原因:YOLOv8 的 letterbox 预处理会在输入图片四周填充灰边,模型输出的检测框是基于填充后的坐标系的。界面代码如果直接拿这个坐标往原始画面上画,就会整体偏移,填充越多偏得越狠。

解决:推理时拿到结果后,用results[0].plot()让模型自己把框画在 letterbox 处理后的图上,再裁剪掉灰边输出给界面。或者严格记录 letterbox 的填充参数,反向换算到原图坐标。最容易踩坑的是:在窗口里显示时画面又做了一次缩放,坐标还要再乘一个缩放系数才匹配。

6. 把模型装进现场:导出、ROI与演示视频的验证法

6.1 导出ONNX:一行命令和省掉环境问题的收益

训练完best.pt只是第一步,答辩现场最怕的不是模型不准,而是演示机器上没有安装正确版本的 PyTorch 和 CUDA。导出 ONNX 是最稳妥的后悔药:

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

导出后用 ONNX Runtime 做推理,只需要pip install onnxruntime,连显卡都不需要。导出后务必检查输出的 batch 维度,默认导出的模型可能固定 batch 为 1,界面里如果用了 batch 推理会报错。验证导出的模型精度和原版是否一致,用同一张测试图对比两次推理的框坐标,偏差小于 0.02 就算正常。

6.2 ROI遮罩:让满溢检测只发生在摄像头画面里指定的区域

现场演示时,画面角落往往会混入不属于任务目标的杂物。一个高价值的技巧是给检测区域加 ROI 遮罩,让模型只关注画面中真正的垃圾桶区域。方法是预设一个多边形掩码,推理前把掩码外的像素置黑:

import cv2 import numpy as np roi_points = np.array([(100, 200), (500, 200), (500, 700), (100, 700)], dtype=np.int32) mask = np.zeros(frame.shape[:2], dtype=np.uint8) cv2.fillPoly(mask, [roi_points], 255) def apply_roi(frame): masked = frame.copy() masked[mask == 0] = 0 return masked

这个做法的价值不只是提升精度,更重要的是演示可控性:摄像头抖动、行人路过、光照突变都不会在画面外区域产生误报。答辩时展示这一层的设计,能直接说明你理解"模型能力只是系统的一部分,约束条件才是工程落地"。

我自己的习惯是,拿到任何别人给的模型,第一件事不是看指标,而是重新标注 20-30 张自己拍的现场照片去测它,只有跑通这一步,这个压缩包里的东西才算真正属于你。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询