基于 YOLO11 的 18 类户外生活垃圾智能检测系统:从模型到 Streamlit 全流程实战
关键词:YOLO11、生活垃圾检测、城市环卫、Streamlit、目标检测、Ultralytics
一、背景
1.1 行业痛点
街道、公园、社区、景区——这些户外公共空间每天产生的生活垃圾种类繁多:塑料瓶、易拉罐、纸箱、衣物、口罩、零食包装袋……传统环卫巡检依赖人工绕场、肉眼判断,存在三个老大难问题:
- 人力成本高:一个中等城市仅环卫一线工人就以万计,夜班 / 节假日排班尤其紧张;
- 响应不及时:垃圾从产生到被清理往往要等下一班巡检,节假日峰值期容易堆积;
- 数据无法沉淀:哪类垃圾在哪个区域出现频次最高?人工记录难以量化,更难指导环卫资源调度。
随着各地"智慧城市""无废城市"政策落地,把 摄像头 + AI 视觉感知接入环卫调度系统,已经成为城市环卫数字化转型的标准动作。本项目就是这条路线下的一个具体落点:用 YOLO11 完成 18 类常见户外生活垃圾的目标检测,并配套一个开箱即用的 Streamlit 可视化界面,便于在巡检车、固定点位摄像头甚至手机端快速接入。
视频演示与源码 下载
包含:
📦 完整项目源码 📦 预训练 模型权重
🗂️ 数据集数据集
1.2 项目运行效果
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 段解析:
@st.cache_resource把YOLO(model_path)的耗时(首次约 2~3 s)锁在第一次访问,之后所有用户共享同一份模型实例。result.plot()直接返回带颜色框 + 类别 + 置信度的BGR ndarray,省去自己画 cv2.rectangle 的心智负担。- 把每个
box的cls / conf / xyxy拍平成dict,最终拼成DataFrame—— 后续无论是表格展示、CSV 下载还是统计分析,都共用这一份结构。 run_image_inference走临时文件路径,是为了让Ultralytics内部的 EXIF / orientation 处理逻辑生效,比直接喂 numpy 数组更稳;finally删除临时文件避免磁盘泄漏。- Streamlit 的
st.cache_resource+st.file_uploader+st.dataframe组合,让一个产品级页面只需 11 行代码——这是 Streamlit 在 AI 应用里能快速出活的核心原因。
四、训练效果
模型在自建的 18 类 数据集上训练 200 epoch(YOLO11n、640 输入、batch 16),最优一轮指标如下:
| 指标 | 数值 |
|---|---|
| Precision | 0.7820 |
| Recall | 0.8532 |
| mAP@0.5 | 0.8983 |
| mAP@0.5:0.95 | 0.8210 |
从训练曲线看,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 落地的样板。