简介:这是一份基于YOLOv8的路面垃圾识别项目代码,面向目标检测学习者、算法工程师以及智慧城市环境监测从业者,解决道路上常见垃圾的自动检测与定位问题,适用于道路巡检、环卫作业、智慧交通等场景。资源共477个文件,压缩包大小32.5MB。其中230个Markdown文档提供详细的使用说明、算法解析与项目贡献指引;130个Python脚本覆盖数据预处理、模型训练、验证和推理全流程;43个YAML文件用于灵活调整模型结构与训练参数;另有C++推理代码、3个预训练权重文件及Dockerfile等,方便跨平台快速部署与实际运行。目前已有163人浏览学习。项目同时提供Python与C++两种推理实现,便于工程部署对比;通过完整代码与配置,可快速复现YOLOv8目标检测流程,并支持在自建数据集上进行迁移训练与参数调优;内置轻量级预训练权重,在保证检测精度的同时兼顾实时性,适合边缘设备运行,也能为研究者理解网络设计、训练策略和部署技巧提供完整参考,是结合理论与实践的高质量项目。
1. 从路面垃圾识别说起:为什么通用检测模型不能直接抄作业
路面垃圾识别不是"拿个YOLOv8练一下就完事"的普通目标检测任务。烟头、纸屑、落叶这类目标在画面里往往只有十几个像素,而路面的树影、裂缝、水渍又会不断制造假阳性。用默认参数训出来的模型,往往对矿泉水瓶检出很好,对烟头却视而不见——这不是模型笨,而是路面垃圾的尺度分布和公开数据集差异太大,直接把COCO那套训练配置搬过来,小目标分支基本在空转。
这类项目的典型落地场景是环卫巡检车、道路养护摄像头和城市网格化巡查。实际需求不是"识别出垃圾",而是"在几百米视频流里不漏掉一个烟头,同时不把阴影当垃圾反复上报"。所以这篇内容围绕YOLOv8展开的完整路径是:数据怎么标才不白标、训练参数针对小目标怎么调、以及部署到边缘设备(比如香橙派5)时怎么保住帧率和召回率的平衡。适合正在做毕业设计、环卫项目预研或自建数据集的工程师参考,我会顺带把踩过坑的参数直接给出来。
2. YOLOv8做路面垃圾识别的数据闭环:标注策略和自建数据集方案
2.1 路面垃圾数据集的特殊性:为什么公开数据集基本不可用
COCO里有potted plant,有bottle,但没有"路面垃圾"这个语义层级。城市道路上的垃圾在物理形态上跨度极大:烟头是毫米级物体,饮料瓶是厘米级,建筑垃圾可能是半米见方。如果直接去网上找现成的"垃圾检测数据集",最常见的问题是类别体系混乱——有的集子把"纸"分成报纸、传单、纸巾三种,有的把塑料瓶和易拉罐统称"容器",这种标注粒度不一致会导致模型学到的特征边界来回震荡。
我一般会建议自建数据集,但不要从零拍摄。常见做法是先采集三个来源:一是城市管理部门的公开监控截图(注意合规授权),二是自己用行车记录仪和手机在不同时段拍(清晨逆光、中午强光、夜间路灯下都要覆盖),三是从Open Images这类大库里按类别关键词筛出路面场景的图片。自建集的数量控制在 2000~3000 张就可以起步,比盲目堆到一万张更重要——路面垃圾的难点从来不是数据量,而是类别平衡和样本多样性。
2.2 类别体系设计:6 类以内是甜区
路面垃圾识别项目里最常见的返工原因不是模型不行,而是类别定义让标注员和模型都崩溃。比如"树叶"和"枯叶"要不要分开?"塑料袋"和"塑料瓶"算不算同一类?我的划分原则是:按清理动作分类,不按材质分类。因为下游工单系统关心的只是"什么工具去清理、派什么车",而不是材料的化学成分。
一个经过环卫项目验证的 8 类方案大致如下:
| 类别名 | 实际对象 | 典型尺度(在1080p图像中) | 标注注意点 |
|---|---|---|---|
| cigarette | 烟头、烟盒 | 10×20 px | 烟头常和落叶叠在一起,框要贴紧烟体 |
| paper | 传单、报纸、纸巾 | 80×120 px | 揉成团的纸按外接矩形标,不标展开面积 |
| plastic_bottle | 饮料瓶、矿泉水瓶 | 60×150 px | 半埋在草丛里的瓶子只标可见部分 |
| can | 易拉罐、金属罐 | 50×100 px | 反光导致的高光区域不额外切框 |
| leaf | 落叶、枯叶堆 | 40×80 px | 成堆落叶按堆标,不拆分成单片 |
| packaging | 塑料袋、泡沫盒 | 100×150 px | 被风吹动的塑料袋按当前帧形态标 |
| construction_waste | 砖块、碎石 | 200×300 px | 通常是大目标,可以直接学 |
| other | 其他杂物 | 不定 | 用于兜底,不追求精度 |
这个表的关键在cigarette和leaf这两行——它们是路面垃圾里最容易漏检和误检的类别,标注框的松紧直接影响后面损失函数的收敛行为。标注工具用 LabelImg 或 Roboflow 均可,输出 YOLO 格式的 txt 文件,内容形如类别ID x_center y_center width height(归一化坐标),这个格式在后续训练时不需要任何转换。
2.3 增强策略:针对"小目标+复杂背景"的albumentations配置
路面垃圾识别的增强不能无脑套用检测任务的通用配置。RandomBrightnessContrast这类全局增强对路面场景是有害的——它会同时改变目标区域和背景区域的亮度关系,导致模型学到"亮度差"这个假特征,而不是"烟头形状"这个真特征。更好的做法是只做局部遮挡、尺度扰动和色调扰动的组合。
下面这段是基于 albumentations 的增强管线,我实测过比 ultralytics 内置的默认增强在路面场景上 mAP@0.5 大约高 3~5 个点:
import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform = A.Compose([ A.RandomResizedCrop(height=640, width=640, scale=(0.5, 1.0), p=0.8), A.HorizontalFlip(p=0.5), A.RandomScale(scale_limit=0.3, p=0.5), # 模拟垃圾在不同拍摄距离下的尺度变化 A.RandomSunFlare(src_radius=200, angle_range=(0.3, 0.5), p=0.1), # 模拟逆光眩光 A.RandomShadow(shadow_roi=(0.1, 0.4, 0.9, 0.9), p=0.2), # 模拟树荫遮挡 A.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.02, p=0.5), A.OneOf([ A.GaussNoise(var_limit=(10.0, 30.0), p=0.5), A.MotionBlur(blur_limit=3, p=0.5), # 模拟车辆行驶时的运动模糊 ], p=0.3), ToTensorV2(), ])参数说明:RandomResizedCrop的scale设为 0.5 到 1.0,是因为路面垃圾的尺度分布本来就集中在图像面积的 2% 以下,过强的裁剪缩放会把本就小的目标直接裁丢;RandomShadow的shadow_roi限定在图像中下部,是因为路面区域基本在下半屏,天空区域的阴影增强只会浪费算力。需要特别提醒的是,RandomSunFlare这类带光源模拟的增强 p 值不要超过 0.1,不然模型会学着用"高光区域"来定位垃圾,夜间部署直接翻车。
提示:数据增强的目的是模拟拍摄条件变化,不是增加样本数量。路面垃圾识别的增强强度应该比通用检测小一档,原因在于垃圾目标的纹理信息本来就稀少,过度增强会让模型学到"模糊块"而不是"垃圾特征"。
3. YOLOv8训练路面垃圾模型:从 yaml 配置到损失函数曲线判读
3.1 环境配置:GTX 1660 Ti 这类 6GB 显存卡的推荐搭配
路面垃圾识别的训练环境不需要顶配。GTX 1660 Ti(6GB 显存)实测可以训练 YOLOv8s,只要不开大 batch、不开多尺度训练。推荐的软件栈是:Python 3.10、PyTorch 2.1.0、CUDA 11.8、ultralytics 8.1.x。这个组合的兼容性最稳,CUDA 12.x 虽然在 40 系显卡上更快,但在 1660 Ti 这类图灵架构上偶发算子兼容问题,没必要追新。
安装命令按顺序执行:
conda create -n yolo_garbage python=3.10 -y conda activate yolo_garbage pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics==8.1.47 albumentations验证安装的方式不是跑 demo,而是直接打印模型结构确认 C2f 模块被正确加载:python -c "from ultralytics import YOLO; model = YOLO('yolov8s.yaml'); print(model.model[-2])"。能看到C2f字样说明环境正常。C2f是 YOLOv8 的骨干核心模块,它的设计思路是跨层特征重用——把上一层的输出同时喂给多个分支再拼接,相比 YOLOv5 的 C3 模块,梯度回传路径更丰富,对微小目标(烟头)的浅层纹理特征提取更有优势。这也是选 YOLOv8 而不是 YOLOv5 做路面垃圾项目的主要理由。
3.2 数据集配置和训练命令:小目标版本的参数改动
在项目目录下建data.yaml,内容是数据集路径和类别映射。这里有一个容易被忽略的点:names列表的顺序必须和标注 txt 里的类别 ID 完全一致,顺序错一位,整个模型就全乱了。训练命令如下:
yolo detect train \ data=data.yaml \ model=yolov8s.yaml \ pretrained=yolov8s.pt \ epochs=120 \ batch=8 \ imgsz=640 \ patience=20 \ mosaic=0.5 \ close_mosaic=10 \ fl_gamma=1.5 \ box=8.0 \ cls=0.8 \ dfl=1.2 \ device=0参数说明:
mosaic=0.5:默认值是 1.0,路面垃圾场景必须降。因为 mosaic 拼接会极大缩小每个子图的占比,烟头本来就小,再被拼到一张图的四分之一区域里,几乎等于直接抹除。close_mosaic=10表示最后 10 个 epoch 关闭 mosaic,这个参数组合是保证小目标在后期精调阶段能被充分学习的关键。fl_gamma=1.5:焦点损失(Focal Loss)的 gamma 参数。路面数据里cigarette类别的样本数量可能只有leaf的十分之一,调高 gamma 让模型把注意力集中在难分类的少数类别上。如果类别不平衡不严重,可以保持默认的 0.0 不动。box=8.0:边框回归损失的权重。YOLOv8 默认是 7.5,这里加一点是因为路面垃圾的框普遍偏小,同等 IoU 误差下小框的绝对像素偏差更致命,加大 box 权重等于告诉模型"框不准要重罚"。
训练完成后,runs/detect/train/weights/下会生成best.pt和last.pt。判断模型是否收敛,看results.png里的train/box_loss和val/box_loss曲线——如果 val 曲线在close_mosaic开始的那个 epoch 后没有明显下降,说明前面 mosaic 阶段的特征学偏了,需要回到第 2 章检查数据增强配置。
3.3 损失函数曲线判读:甘特图还是过山车
用tensorboard或直接看 ultralytics 生成的results.csv可以画出损失曲线。路面垃圾识别项目里最常见的曲线问题是box_loss 下降很快但 cls_loss 震荡——这说明模型已经能框出垃圾候选区域,但分类置信度忽高忽低。我一般会先用下面的脚本做一个快速诊断:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") # 取 val 的 cls_loss 和 box_loss 列 plt.plot(df["epoch"], df["val/cls_loss"], label="val_cls") plt.plot(df["epoch"], df["val/box_loss"], label="val_box") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png", dpi=150)如果画出来val/cls_loss呈周期性波动而不是单调下降,大概率是数据集的类别样本不平衡超出了合理范围。此时不要盲目加训练轮数,而是回到 2.2 节的类别表,检查最差的那几个类别(通常是cigarette和packaging)的标注框是否偏松——框大了会把背景特征学进类别特征里。另一个常见问题是val/dfl_loss在训练后期仍然偏高,这表示框的分布预测不确定,小目标的定位一致性差,补救方案是微调dfl=1.2到1.5并配合imgsz=800做一次短训,通常 30 个 epoch 就能验证是否有效。
提示:不要追求训练集损失降到 0。路面垃圾的特征本身就有噪声(同一片落叶在不同光照下差异巨大),训练集损失过早收敛到接近 0 反而说明模型过拟合了图像的背景纹理,val 损失会在过拟合开始的 epoch 后拐头向上。
4. 路面垃圾识别的推理优化和误检抑制:从 SAHI 切图到 NMS 调参
4.1 小目标漏检的真凶不是模型,而是下采样
YOLOv8s 在 640×640 输入下,检测头在 P3、P4、P5 三个尺度输出特征图,分别对应 80×80、40×40、20×20。一个 10×20 像素的烟头,经过 8 倍下采样后在 P3 特征图上只剩 1×2 到 2×3 个像素点——这么少的特征点,检测头很难稳定输出高置信度的框。这就是为什么路面垃圾项目在 640 分辨率下对烟头类别的召回率普遍只有 50% 上下。
解决办法有两个方向,第一个是训练时直接上imgsz=960,让烟头在特征图上多占几个像素点,代价是显存占用和推理耗时同步上升;第二个是我更推荐的做法——推理时做SAHI(Slicing Aided Hyper Inference)切图推理。SAHI 的思路是把原始大图切成若干有重叠的小块,每个小块独立推理后再把结果拼接回去。对于路侧摄像头拍摄的 1080p 图像,切成 640×640、重叠率 20% 的小块,烟头在切块后的有效像素面积直接变成原来的 2 倍以上。
4.2 基于 ultralytics + SAHI 的推理代码
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model = AutoDetectionModel.from_pretrained( model_type="ultralytics", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.25, image_size=640, device="cuda:0", ) result = get_sliced_prediction( "test_images/road_01.jpg", detection_model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2, postprocess_type="NMM", # 用 NMM 而不是 NMS postprocess_match_threshold=0.5, postprocess_class_agnostic=False, ) result.export_visuals(export_dir="output/")参数说明:postprocess_type="NMM"(Non-Maximum Merging)是关键——切图推理会产生大量重叠区域的重复框,NMS 只抑制低置信度的框,但跨切块的同一目标可能两边置信度都差不多,NMS 会保留两个框;NMM 则是把重叠度高、类别一致的框合并成一个,输出框的位置更稳定。postprocess_match_threshold=0.5表示两个框的 IoU 超过 0.5 且类别相同时才合并,阈值调低会导致相邻的不同垃圾被错误合并。
运行这段代码后,如果output/里的可视化结果仍显示烟头漏检,检查目标在原始图像中的像素尺寸:如果小于 15×15 像素,切图尺寸应从 640 降到 512 再试。这里有一条经验换算法则——切图尺寸每降 1/4,小目标召回率约提升 8% 到 12%,但推理时间近似线性增加,需要在吞吐量和召回率之间做平衡。
4.3 误检抑制的三个后处理技巧
路面垃圾误检的主要来源是路面的地缝、树影和轮胎痕迹,这些纹理在特征层面和垃圾的高频纹理有相似性。切图推理解决了漏检,但也会放大误检,因为每个切块独立推理意味着原有的全局上下文("这里是大片柏油路,不应该有垃圾")被切断了。我的处理方式是规则后处理:
第一,利用先验位置过滤。路面垃圾只出现在画面下半部分的道路区域,如果模型在图像上半部分的天空或楼体上检测出paper或plastic_bottle,大概率是误检。按照这个先验对检测框做区域掩码,删除所有 bbox 中心点位于图像垂直方向前 40% 的框,可以干掉大约 30% 的误检。
第二,利用目标宽高比过滤。路面垃圾在常规拍摄角度下,除了落叶有较随机的宽高比,烟头和瓶子的宽高比都有统计边界。比如plastic_bottle的宽高比通常落在 0.2 到 0.6 之间,如果出现一个宽高比接近 1.0 的"瓶子",很可能是圆形井盖被误检。写一个简单的过滤函数就行,不用引入额外模型。
第三,利用类别共现过滤。路面垃圾识别里cigarette经常被误检为paper——这是因为烟头和揉成团的白纸在纹理上确实接近。对这种混淆对,可以通过降低paper的置信度阈值、提高cigarette的阈值来压制,具体数值需要根据验证集的混淆矩阵来定。
4.4 部署到边缘设备:香橙派5上的优化配置
香橙派5 这类 ARM 设备的 NPU 算力有限,直接跑 640×640 的 fp32 模型通常只有 2~3 FPS,必须做转换和量化。常见且可靠的部署链路是:best.pt -> ONNX -> RKNN。第一步先导出 ONNX:
yolo export model=runs/detect/train/weights/best.pt format=onnx opset=12 imgsz=640 simplify=True导出时opset=12是为了兼容 RKNN-Toolkit 的算子支持范围,更高版本的 opset 会导致部分算子无法映射到 NPU 上。转到 RKNN 模型时常用 int8 量化,但路面垃圾的小目标在 int8 量化下精度损失比大目标更严重——因为量化步长是全局统一的,小目标的特征值范围小,量化误差占比大。所以部署到香橙派时,建议先用 int8 量化跑一遍验证集,如果cigarette类别的 AP 下降了超过 15%,就退回 fp16 或者只在第 6 层之后做量化混合精度。
提示:边缘部署的帧率优化,优先改预处理而不是改模型。将
letterbox的填充色从灰色改为路面平均灰度(约 110),可以减少填充区域对检测头的干扰,这个改动能在几乎不影响精度的前提下减少 5% 左右的推理耗时。
如果要压榨最后一点性能,可以把输入分辨率从 640 降低到 480,并用 4.1 节的 SAHI 切图在服务端做二次精检:边缘设备用低分辨率模型做粗筛,只有出现检出的帧才传回服务端用小模型精检。这个"边端粗筛 + 云端精检"的架构是路面垃圾项目里投入产出比最高的部署方案,烟头漏检率能压到 5% 以下,同时边缘设备的单路视频处理可以做到 15 FPS 左右。使用这种方案时,别忘了粗筛阶段的置信度阈值不要设太高,0.15 到 0.2 是比较合适的范围——粗筛的目的是不漏,误检留给云端去过滤。
本文还有配套的精品资源,点击获取