基于 YOLO11 的 18 类户外生活垃圾智能检测系统:从模型到 Streamlit 全流程实战

0 阅读7分钟

基于 YOLO11 的 18 类户外生活垃圾智能检测系统:从模型到 Streamlit 全流程实战

关键词:YOLO11、生活垃圾检测、城市环卫、Streamlit、目标检测、Ultralytics

一、背景

1.1 行业痛点

街道、公园、社区、景区——这些户外公共空间每天产生的生活垃圾种类繁多:塑料瓶、易拉罐、纸箱、衣物、口罩、零食包装袋……传统环卫巡检依赖人工绕场、肉眼判断,存在三个老大难问题:

  1. 人力成本高:一个中等城市仅环卫一线工人就以万计,夜班 / 节假日排班尤其紧张;
  2. 响应不及时:垃圾从产生到被清理往往要等下一班巡检,节假日峰值期容易堆积;
  3. 数据无法沉淀:哪类垃圾在哪个区域出现频次最高?人工记录难以量化,更难指导环卫资源调度。

随着各地"智慧城市""无废城市"政策落地,把 摄像头 + AI 视觉感知接入环卫调度系统,已经成为城市环卫数字化转型的标准动作。本项目就是这条路线下的一个具体落点:用 YOLO11 完成 18 类常见户外生活垃圾的目标检测,并配套一个开箱即用的 Streamlit 可视化界面,便于在巡检车、固定点位摄像头甚至手机端快速接入。

视频演示与源码 下载

www.bilibili.com/video/BV1KH…

在这里插入图片描述

包含:

📦 完整项目源码 📦 预训练 模型权重

🗂️ 数据集数据集

1.2 项目运行效果

Web 界面总览

单图检测效果

批量分析图表

1.3 项目介绍

核心创新点:

维度设计
类目覆盖18 类典型户外垃圾,覆盖塑料 / 金属 / 纸制品 / 织物 / 餐厨等高频品类
模型选型YOLO11n,参数量约 2.6 M,CPU 实时、Apple Silicon / NVIDIA GPU 加速可选
部署形态Streamlit 单页应用 + 本地 best.pt,零数据库、零中间件,开箱即用
输入兼容图片 / 批量图片(含 zip)/ 视频 / 摄像头四种输入,全部走同一推理引擎
中文标签标注、UI、配置文件三处统一中文显示,运维人员不需要看英文 class id

功能概览:

  • 🖼️ 单图检测:拖拽上传 → 实时返回带框图 + 类别 / 置信度 / 坐标明细
  • 📚 批量检测:一次最多 200 张,自动出汇总表 + 类别分布柱状图 + 打包下载 zip
  • 🎬 视频检测:支持 mp4 / avi / mov / mkv,逐帧推理后落盘新视频
  • 📷 摄像头实时检测:OpenCV + Streamlit 实时刷新,可作为现场巡检 demo

18 个类别清单:塑料袋、塑料瓶、易拉罐、纸箱、衣物、吸管、泡沫箱、盖子、口罩、牛奶盒、纸巾、小包包装袋、纸张、塑料杯、塑料收纳架、塑料餐具、零食包装袋、垃圾袋。

技术栈:

层选型
模型框架Ultralytics 8.3 + PyTorch
视觉算法YOLO11n(Anchor-Free,C2f Backbone,Decoupled Head + DFL)
Web 前端Streamlit 1.35(自带组件 + 自定义 HTML 样式)
视频 / 图像OpenCV、imageio-ffmpeg
数据可视化Plotly / Streamlit 原生 chart
配置管理单一 configs/project.yaml(行业话术 + UI + 训练 + 推理参数全在这里)

二、设计框架

2.1 系统架构

                ┌──────────────────────────────────────┐
                │     Streamlit Web 前端(单页 SPA)    │
                │  · 单图 / 批量 / 视频 / 摄像头四 Tab │
                │  · 侧栏 · 阈值 · 类别筛选 · 模型缓存  │
                └──────────────┬───────────────────────┘
                               │ Python 调用
                               ▼
                ┌──────────────────────────────────────┐
                │   推理服务层(infer_image_path 等)   │
                │  · 临时文件 + bytes → tensor          │
                │  · 推理结果 → 标注图 + DataFrame      │
                └──────────────┬───────────────────────┘
                               │
                               ▼
                ┌──────────────────────────────────────┐
                │    YOLO11n 推理引擎(best.pt)        │
                │  Backbone (C2f+SPPF) → Neck (FPN/PAN) │
                │   → Decoupled Head(DFL + cls)       │
                └──────────────────────────────────────┘

2.2 YOLO11 网络结构

输入 640×640×3
   │
   ▼
[Backbone]
  Conv → C2f → Conv → C2f → Conv → C2f → Conv → C2f → SPPF
        多尺度卷积 + 跨阶段特征融合,参数量比 v8 进一步压缩
   │
   ▼
[Neck]   FPN 上采样 + PAN 下采样:P3 / P4 / P5 三级金字塔
   │
   ▼
[Head]  Decoupled Head(分类头 / 回归头解耦)
        回归头使用 DFL(Distribution Focal Loss)
        Anchor-Free,输出 (cx, cy, w, h, cls × 18)
   │
   ▼
[NMS]    iou=0.7, conf=0.25  → 最终检测框

2.3 检测流程

图片 / 视频帧 ──▶ Letterbox 640×640 ──▶ YOLO11n forward
                                               │
                                               ▼
                                       Detect Head 输出
                                               │
                                               ▼
                                       conf 过滤 + NMS
                                               │
                                               ▼
                              result.plot() 画框 + DataFrame 列出明细
                                               │
                                               ▼
                              Streamlit 图像组件 + dataframe + 图表渲染

三、代码结构(核心 50 行 + 解析)

项目目录:

户外生活垃圾检测/
├── app/streamlit_app.py        # Web 入口(单文件,1100+ 行)
├── configs/project.yaml         # 行业话术 + UI + 训练 + 推理参数
├── model/best.pt                # 训练好的 YOLO11n 权重
├── scripts/                     # train_yolo / validate / export 等
├── data/dataset/                # 训练 / 验证 / 测试集
└── train/                       # 训练产物(results.csv / 各类曲线)

最核心的 50 行——模型加载 + 单图推理 + Streamlit 单图 Tab:

# === 1. 模型加载(带缓存,避免每次刷新重载)===========================
@st.cache_resource(show_spinner=False)
def load_model(model_path: str) -> YOLO:
    return YOLO(model_path)

# === 2. 单图推理:路径 → 标注图 + 检测明细 DataFrame ===================
def infer_image_path(model: YOLO, image_path: Path, conf: float):
    result = model.predict(str(image_path), conf=conf, verbose=False)[0]
    annotated = result.plot()                           # BGR ndarray,已画框
    rows = []
    if result.boxes is not None:
        for box in result.boxes:
            cls_id = int(box.cls.item())
            x1, y1, x2, y2 = [float(v) for v in box.xyxy[0].tolist()]
            rows.append({
                "class_id": cls_id,
                "class_name": result.names[cls_id],
                "confidence": round(float(box.conf.item()), 4),
                "x1": round(x1, 1), "y1": round(y1, 1),
                "x2": round(x2, 1), "y2": round(y2, 1),
            })
    # BGR → RGB 给 Streamlit;rows 转 DataFrame 给表格组件
    return annotated[:, :, ::-1], pd.DataFrame(rows)

# === 3. bytes → 临时文件 → 复用上面同一函数 ============================
def run_image_inference(model: YOLO, image_bytes: bytes, conf: float, suffix=".jpg"):
    with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as f:
        f.write(image_bytes); tmp = Path(f.name)
    try:
        return infer_image_path(model, tmp, conf)
    finally:
        tmp.unlink(missing_ok=True)

# === 4. Streamlit 单图 Tab:上传 → 推理 → 双列展示 =====================
model = load_model(str(MODEL_PATH))
uploaded = st.file_uploader("上传待检测图片", type=IMAGE_UPLOAD_TYPES)
conf = st.sidebar.slider("置信度阈值", 0.05, 0.95, DEFAULT_CONF, 0.01)

if uploaded is not None:
    annotated, df = run_image_inference(model, uploaded.getvalue(), conf,
                                        suffix=Path(uploaded.name).suffix)
    col1, col2 = st.columns(2)
    col1.image(uploaded, caption="原图")
    col2.image(annotated, caption="检测结果")
    st.dataframe(detections_to_display(df))
    render_detection_analysis(df)

5 段解析:

  1. @st.cache_resource 把 YOLO(model_path) 的耗时(首次约 2~3 s)锁在第一次访问,之后所有用户共享同一份模型实例。
  2. result.plot() 直接返回带颜色框 + 类别 + 置信度的 BGR ndarray,省去自己画 cv2.rectangle 的心智负担。
  3. 把每个 box 的 cls / conf / xyxy 拍平成 dict,最终拼成 DataFrame —— 后续无论是表格展示、CSV 下载还是统计分析,都共用这一份结构。
  4. run_image_inference 走临时文件路径,是为了让 Ultralytics 内部的 EXIF / orientation 处理逻辑生效,比直接喂 numpy 数组更稳;finally 删除临时文件避免磁盘泄漏。
  5. Streamlit 的 st.cache_resource + st.file_uploader + st.dataframe 组合,让一个产品级页面只需 11 行代码——这是 Streamlit 在 AI 应用里能快速出活的核心原因。

四、训练效果

模型在自建的 18 类 数据集上训练 200 epoch(YOLO11n、640 输入、batch 16),最优一轮指标如下:

指标数值
Precision0.7820
Recall0.8532
mAP@0.50.8983
mAP@0.5:0.950.8210

训练结果总览

PR 曲线

归一化混淆矩阵

从训练曲线看,box / cls / dfl 三路 loss 在前 50 epoch 快速收敛;mAP@0.5 在第 100 epoch 后逐步突破 0.85,最终稳定在 0.89~0.90 区间;归一化混淆矩阵中"塑料瓶 / 易拉罐 / 纸箱 / 泡沫箱"等高频类别对角线接近 1.0,仅"纸巾 / 纸张"两类因外观高度相似存在小幅互混,这与人类肉眼判断的混淆模式一致。

五、项目总结

做对了什么:

  • 用一份 project.yaml 把"行业话术 / UI 主题色 / 训练超参 / 推理阈值"全部收口,二次复用到其他垃圾品类(如餐厨、建筑垃圾)只需改这一个文件;
  • 单图、批量、视频、摄像头四种输入复用同一个 infer_image_path,避免了"图像走 numpy、视频走 frame、摄像头走 OpenCV"这种容易翻车的多分支实现;
  • 选 YOLO11n 而不是 v8s/v8m,模型 6 MB、CPU 一张图 80 ms 内完成,方便在巡检车 / 边缘盒子上落地。

还能怎么做:

  • 接入 RTSP 视频流 + Kafka 消息队列,把检测结果(坐标 + 时间戳 + 摄像头 ID)直接写入环卫调度系统;
  • 增加"垃圾热力图"模块,按时间维度聚合同一摄像头的检测结果,反向指导环卫排班;
  • 在数据集层面持续补充夜间 / 雨天 / 远景小目标样本,进一步提升 mAP@0.5:0.95。

一句话总结:YOLO11 的 Anchor-Free + Decoupled Head + DFL 组合,让"小模型 + 多类别 + 实时推理"这件事在 2024 年变成了"一份 yaml + 一份 best.pt + 一份 streamlit_app.py"的工程化标准动作,本文给出了一份可以直接 fork 落地的样板。