☰
基于YOLOv8的垃圾分类目标检测系统:从数据集到答辩全攻略
2026/9/28 17:40:19 网站建设 项目流程

简介:面向计算机相关专业学生完成毕业设计、课程设计或期末大作业的深度学习实战项目,基于YOLO等目标检测算法实现垃圾分类识别,并配套答辩PPT、设计报告与参考文档。项目代码完整、可独立运行,适合具备基础Python知识、希望快速搭建可演示系统的学习者。资源压缩包共126个文件,大小约66.07MB,主要包含Python源码、ipynb训练与演示脚本、Vue前端页面及JS交互逻辑,另有ONNX模型文件、CSS样式、SQLite数据库、Dockerfile与YAML部署配置等,覆盖从模型训练、后端服务到前端展示的完整闭环。当前已有199人参与学习下载。借助这份资料,读者能拿到整套可直接运行的检测系统,以及答辩PPT、三份Word报告/模板、实践大作业参考、PDF说明等文档,便于对照源码理解目标检测思路、快速补齐项目文档并打磨答辩材料,是冲刺高分毕设的实用参考。

1. 从“能跑起来”到“能答辩”:这套垃圾分类目标检测系统到底在做什么

同一个宿舍楼里,有人用三个月磨一个垃圾分类app,有人一周就把检测系统跑通并拿去答辩拿了高分,差别不在编程基础,而在选了一条可复现、可解释、能扛住答辩追问的技术路线。这篇笔记要讲的,就是一套基于深度学习的目标检测方案:用YOLO系列模型对可回收、有害、厨余、其他四类垃圾做实时识别,配套Python后端和可视化界面,最终以“源码+答辩PPT”的形式交付毕业设计。它不是论文复现,也不是算法创新,而是一个资源有限、时间有限的学生能在本地跑通并给自己加分的完整闭环。

适合谁看?选题是“目标检测”方向但还没定具体做法的本科生,以及想用现有数据集快速做出一版毕设系统、把主要精力留给论文和答辩的人。全文按“数据→训练→系统→答辩”推进,每一步都给可抄的命令和参数。

2. 搞定数据集:没有现成数据时,把公开数据集改造成自己的训练集

2.1 选数据集时的现实考量

做垃圾分类目标检测,最直接的路径是找一个现成的垃圾分类数据集,比如华为云垃圾图片数据集、TrashNet这类公开资源。它们的好处是类别标注齐全、已经被很多人跑通,用起来不容易翻车。但毕业设计有一个隐性要求:答辩老师极大概率会问“数据集是怎么来的”。如果你直接说“网上下载的”,第一轮追问就会落在数据来源合法性、类别定义依据这些点上。所以常见做法是:以公开数据集为基础,补充一部分自己拍摄的样本,把“数据来源与构建”写成论文里独立的一章。

另一个现实问题是Class Balance。垃圾分类四类的样本极其不均衡,厨余垃圾里有剩菜剩饭这类标注难度大的图片,有害垃圾样本总量少。训练前先做一次类别分布统计,比直接开跑训练更省时间。

2.2 数据集划分与目录结构标准化

无论你最终用YOLOv5还是YOLOv8,数据集目录结构都必须对齐模型训练脚本的预期。我的习惯是统一按下面的结构组织:

dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

data.yaml内容如下:

path: dataset/ train: images/train val: images/val test: images/test nc: 4 names: ['recyclable', 'kitchen', 'hazardous', 'other']

这里有个关键点:path字段建议写成相对路径,不要写绝对路径。因为答辩现场可能要换机器演示,绝对路径会导致数据集找不到,这个坑我在自己跑项目时踩过一次,当场把模型路径改成相对路径才救回来。

2.3 标注工具选择与标注格式转换

如果你补充了自己拍摄的图片,或者想把某个只有VOC格式标注的数据集转成YOLO格式,我会选择LabelImg或X-AnyLabeling这类开源标注工具。前者老牌稳定,后者支持自动标注辅助,适合样本量大的场景。

标注完成后,VOC格式的XML转成YOLO格式的txt,转换逻辑不复杂,核心是把XML里的边界框坐标转换成归一化的中心点坐标和宽高:

import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_file, out_dir, class_names): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_names: continue cls_id = class_names.index(cls) box = obj.find('bndbox') x_min = float(box.find('xmin').text) y_min = float(box.find('ymin').text) x_max = float(box.find('xmax').text) y_max = float(box.find('ymax').text) x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: base = os.path.basename(xml_file).replace('.xml', '.txt') with open(os.path.join(out_dir, base), 'w') as f: f.write('\n'.join(lines)) class_names = ['recyclable', 'kitchen', 'hazardous', 'other'] xml_dir = 'VOC/Annotations' out_dir = 'dataset/labels/train' os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): convert_voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir, class_names)

这段脚本的逻辑是遍历XML文件,提取每张图片的尺寸和每个目标的类别及坐标,然后按YOLO格式写入txt。参数说明:class_names的列表顺序决定了类别ID,这个顺序必须和data.yaml里的names顺序一致,否则训练出来的模型类别对应关系全乱。xml_dir和out_dir按你自己的目录结构调整。

2.4 数据增强:避免过拟合的常用组合

YOLO模型在训练时会内置Mosaic、随机翻转、色彩空间变换等增强策略,但这不代表数据准备阶段不能做预处理。对于垃圾分类场景,光照变化和拍摄角度是影响精度的主要因素,所以数据增强的重心放在亮度调整、旋转和透视变换上。我用的是albumentations库:

import albumentations as A train_transform = A.Compose([ A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.8), A.HorizontalFlip(p=0.5), A.Rotate(limit=15, p=0.5), A.RandomSizedBBoxSafeCrop(width=640, height=640, p=0.5), ], bbox_params=A.BboxParams(format='yolo')) # 使用时通过 pipeline 把增强后的结果写回训练集

两点提醒:一是RandomSizedBBoxSafeCrop必须显式指定bbox_params,否则边界框不会随图片裁剪,训练完的检测框全是偏的;二是增强部分和模型训练时Mosaic增强不要叠加过猛,否则厨余垃圾里的糊状物会被增强得面目全非。一般建议增强系数保持在0.3~0.5之间。

3. 训练环节:模型选型、参数设置与训练结果判断

3.1 为什么用YOLOv8而不是Faster R-CNN或SSD

做毕设系统,模型选型有三个核心诉求:精度够用、训练时间可接受、部署简单。Faster R-CNN两阶段检测精度上限更高,但训练和推理速度慢,在CPU机器上跑一个界面演示会卡到没法看。SSD速度可以但小目标检测能力弱,垃圾图片里的小物体比较吃亏。YOLO系列在速度和精度的平衡上更合适,YOLOv8对新手友好,训练脚本封装完整,ultralytics库一条命令就能启动训练,对毕设来说是最稳妥的选择。

如果追一句“为什么不用YOLOv5”,主要考虑是版本维护和文档完整性。YOLOv8在导出模型、调整训练参数、推理调用上都更统一,答辩时被问到也能讲得更清楚。

3.2 最小可用训练命令与参数说明

先安装依赖,再拉训练命令。我在vscode里配Python环境时比较容易翻车的是虚拟环境混用,这里把步骤写成可复现的:

python -m venv venv source venv/bin/activate pip install ultralytics

然后开始训练:

yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=80 \ imgsz=640 \ batch=16 \ patience=15 \ project=runs \ name=trash_detect

参数说明:model=yolov8n.pt表示用YOLOv8n作为预训练权重,n是最小版本,训练快,适合毕设场景。如果你机器显存足够(大于6GB),可以换成yolov8s.pt,精度会略高。patience=15表示连续15个epoch验证集精度不提升就早停,这能帮你把时间省下来。batch=16要根据显存调整,显存不够就降到8。

训练过程中重点看的指标是mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度,垃圾分类这种场景mAP50能到0.85以上就算可用;mAP50-95是更严格的综合指标,不要期望太高。另外注意观察训练集和验证集的loss曲线差距,训练集loss降但验证集loss横盘或反弹,说明过拟合已经开始,需要停止或加增强。

3.3 训练后如何验证模型真的可用

训练结束后直接跑验证命令:

yolo detect val \ model=runs/trash_detect/weights/best.pt \ data=dataset/data.yaml

跑完要看模型对每类垃圾的单独表现。我遇到过的情况是“可回收物”和“其他垃圾”两类混淆严重,因为纸箱和塑料瓶在特定角度下外形接近。这时不要盲目调训练参数,先检查是不是标注本身有模糊边界,比如矿泉水瓶到底算可回收还是其他。

验证通过后导出模型权重,后续系统调用统一用best.pt这个文件,不要用last.pt。

4. 做成一个能演示的系统:Flask后端与可视化界面

4.1 系统的整体架构

毕设答辩时,评委要看的是“一个能用的系统”,而不是一个裸模型。所以我会把系统拆成两个部分:模型推理服务与前端交互界面。模型推理服务用Flask搭建一个轻量API接口,接收前端传过来的图片,返回识别结果;前端界面负责图片选择、结果展示。

4.2 Flask接口核心实现

以下是Flask后端处理图片检测的完整代码:

import cv2 from flask import Flask, request, jsonify from ultralytics import YOLO app = Flask(__name__) model = YOLO('runs/trash_detect/weights/best.pt') @app.route('/detect', methods=['POST']) def detect(): if 'image' not in request.files: return jsonify({'error': 'no image uploaded'}), 400 file = request.files['image'] img_bytes = file.read() img_array = np.frombuffer(img_bytes, dtype=np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) results = model.predict(img, conf=0.45, iou=0.5) detections = [] for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int) conf = float(box.conf[0]) cls_id = int(box.cls[0]) label = model.names[cls_id] detections.append({ 'bbox': [x1, y1, x2, y2], 'confidence': round(conf, 2), 'class': label }) return jsonify({'detections': detections}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

这个接口的逻辑就是把上传的图片从字节流解码成OpenCV格式,用yolo模型推理,再把检测框、置信度和类别拼成JSON返回。参数说明:conf=0.45是置信度阈值,低于这个值的检测框会被过滤掉。如果发现漏检严重,可以降到0.35;如果发现误检多,可以调高到0.55。iou=0.5是NMS的IoU阈值,用来去除重叠框,一般保持0.5即可。

需要注意model.predict每次调用都会重新跑前向推理,在CPU机器上单张图片可能要1~2秒,加上前端展示延迟,整体体验是能接受的。如果要提高流畅度,可以把模型加载放到全局变量里(上面的代码就是这个模式),避免每次请求重复加载权重。

4.3 前端展示页面与本地调试

前端不用做得花哨,一个HTML页面配合JavaScript就能完成演示。核心是选中本地图片、调用接口、展示结果:

<!DOCTYPE html> <html> <head> <title>垃圾分类检测系统</title> </head> <body> <h3>垃圾分类目标检测系统</h3> <input type="file" id="imageInput" accept="image/*"> <canvas id="canvas" width="640" height="640"></canvas> <script> document.getElementById('imageInput').addEventListener('change', function(e) { const file = e.target.files[0]; const formData = new FormData(); formData.append('image', file); fetch('http://localhost:5000/detect', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0, 640, 640); data.detections.forEach(item => { ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 2; ctx.strokeRect(item.bbox[0], item.bbox[1], item.bbox[2] - item.bbox[0], item.bbox[3] - item.bbox[1]); ctx.fillStyle = '#ff0000'; ctx.font = '16px sans-serif'; ctx.fillText(item.class + ' ' + item.confidence, item.bbox[0], item.bbox[1] - 5); }); }; img.src = URL.createObjectURL(file); }); }); </script> </body> </html>

这个页面把检测框直接画在canvas上,类名和置信度显示在框上方。本地调试时先启动Flask后端,再用浏览器打开HTML文件,注意canvas的绘制尺寸固定为640x640,如果传入图片比例不是1:1,显示会变形。这个问题虽然不影响检测结果,但答辩演示时容易被评委指出来。更好的做法是把canvas宽高设为图片原始尺寸,或者在画像前等比缩放。

5. 答辩避坑:五个高频翻车点与排查方案

5.1 环境依赖没锁版本,答辩现场跑不起来

现象:本机训练好模型,答辩前换到教室电脑演示,运行报错ModuleNotFoundError: No module named 'torch'或ultralytics导入失败。

原因:面对这种场景,最常见的原因就是没有做依赖冻结。你在自己电脑上可以用pip freeze把环境导出成requirements.txt,直接打包带走。而且本机系统环境可能装过多个版本的库,一旦换机器所有依赖都得重新装。

解决:项目根目录必须有一份完整的requirements.txt,包括torch、torchvision、ultralytics、flask、opencv-python、numpy。最好到答辩前一周把项目迁移到一台干净虚拟机里,从零按照requirements.txt安装一遍,确保依赖能完整复现。

5.2 数据集路径写死,换机器后全部失效

现象:在A机器上训练时,代码里写的data: C:/Users/xxx/dataset/data.yaml,照搬到B机器后报错File not found。

原因:开发机与答辩机的目录结构不一样。数据集路径一旦用绝对路径,整个项目的复用性就毁了。

解决:把data.yaml里的path改成相对路径(前面2.2节已经演示过),项目根目录作为相对路径的基准。后端和模型加载路径同理,用os.path.join(os.path.dirname(__file__), ...)来定位权重文件。

5.3 答辩演示用测试集图片,被质疑“见过答案”

现象:演示时拿训练集或测试集里的图片跑检测,效果极好,评委随口问“用一张没有训练过的照片试试”,现场翻车。

原因:这不算模型失效,而是演示策略没打好。检测模型对这四类生活垃圾的泛化能力有限,遇到真实桌面照片会有误检或漏检。

解决:答辩前专门留出10张“从网上下载评价不好找的实拍图、或自己用手机拍的不同角度照片”,在答辩前测一遍,记录每张图的识别情况。演示时主动说“下面用一张不在训练集里的实拍图测试”,这是加分项,因为它在展示模型的鲁棒性。

5.4 置信度阈值调太低,误检框把画面糊满

现象:界面显示大量错误的框,一张图里有七八个检测框互相重叠,整个演示效果没法看。

原因:模型对部分易混淆类别(比如透明塑料瓶和玻璃瓶)输出的置信度在0.3~0.5区间徘徊,如果把conf降到0.25,这些弱检测框全部涌出。

解决:尝试验证集上不同conf阈值下的表现,把阈值设置在一个既保留正常检测又不溢出误检的值。实际操作中,把conf设置为0.5左右,配合NMS的iou参数,能压掉大部分误检框。如果单靠调阈值压不干净,等回到数据层面检查两类样本的数量差异。

5.5 答辩PPT里“创新点”写成了“自创算法”

现象:答辩时评穷“你说创新点是改进YOLO的网络层结构,具体改在哪一层?”结果答不上来。

原因:把“用了深度学习目标检测技术”当成创新点,或者把网上模型换了个名字当成自己的改进。这种回答在答辩现场会被追得很深。

解决:毕业设计原创性的正确写法是系统集成创新——比如“设计了一套针对校园场景的垃圾分类识别流程”“结合了数据增强与置信度融合策略”。在论文和PPT里写清楚技术选型对比、系统架构设计、数据集构建过程,这些才是真实可控的完整部分。

6. 进阶给加分:视频流检测与模型量化

如果你还有余力,系统加一个视频流检测功能,演示效果会明显提升。Flask后端用OpenCV读取本地摄像头或视频文件,逐帧送入模型推理:

import cv2 from ultralytics import YOLO model = YOLO('runs/trash_detect/weights/best.pt') cap = cv2.VideoCapture(0) while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.45) annotated = results[0].plot() cv2.imshow('Trash Detection', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这段代码每帧都跑一次推理,CPU机器上帧率只有个位数,但答辩演示时“摄像头实时识别”这句话本身就是加分项。为了提升帧率,可以先把模型导出为TensorRT或OpenVINO格式再做推理加速;如果不想引入额外依赖,简单方法是把输入帧分辨率降低到640x640后再推理。

再提一个量化技巧:把模型导出成FP16精度,在不明显掉点的情况下减小内存占用,换来的推理速度提升大概在10%~20%。用ultralytics库一行就能完成:

yolo export model=runs/trash_detect/weights/best.pt format=engine device=0

这个命令要求本机有GPU和TensorRT环境,没有的话不要强上,毕设系统用原始PyTorch权重已经足够。

我个人的习惯是在答辩前把三个权重文件都准备好:best.pt(精度优先)、best_quantized.pt(速度优先)、原始训练参数记录。这样演示时可以根据实际机器性能快速切换。把“换模型不换接口”做进系统设计里,答辩时讲起来也有说服力。希望这套方案能帮你把毕设从“跑通”做到“讲得清、扛得住问”,少走一段弯路。

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

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

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

立即咨询