城市垃圾检测-简介
基于深度学习的城市垃圾与杂物目标检测系统是一套围绕城市环卫巡检需求构建的计算机视觉毕设项目,整体由模型训练与网站应用两部分组成。模型部分以公开 Kaggle 城市垃圾数据集为基础,数据总量 7325 张图片且一一对应标注,按 70%、20%、10% 切分为训练、验证与测试集,标注采用 YOLO 归一化格式,所有目标统一归入单类 garbage,中文展示名为垃圾。在数据就绪前,项目先做了轻量与深度画像:单类实例约 1.7 万个,每图平均目标数约 2.4 个,小目标占比不到 8%,标注异常率极低,错标可疑率约 7% 未过清洗线故照常训练并如实披露。训练选用 yolo26m 骨干,借助基准探测、过拟合烟测、学习率扫描等前置脚本锁定配置,正式训练设置输入边长 512、批次 12、学习率 0.001、优化器 MuSGD,在第 133 轮取得验证集 mAP50-95 约 0.683,随后按早停耐心自动终止于第 153 轮。导出阶段把权重转成 ONNX,并基于验证集按 95% 通过率标定能量分数阈值,使纯色图、随机噪声等域外输入能被拒识而不被强行判为垃圾。网站后端采用 Django 框架,核心接口包括模型列表、识别推理与统计大屏;前端基于 Vue 提供图片、视频、摄像头三类识别页,以及识别历史列表和统计大屏页。识别时后端先做 letterbox 缩放填充,再经 ONNX 推理与后处理把检测框还原回原图坐标,返回中文类别与置信度;成功识别会写入行为计数表与历史明细表,前者只增不改用于统计,后者支持按用户隔离与软删除。统计大屏一次拉取六类聚合:类别分布、近 14 天趋势、置信度直方图、星期乘小时热力、任务类型占比与关键指标,帮助从多个角度观察识别行为。整套系统在功能上贯通了从数据到模型再到可交互应用的过程,便于在答辩中讲清每一处代码与产物的对应关系。
城市垃圾检测-背景
在城市日常运转里,垃圾与杂物散落街角、路边的现象几乎天天都能见到,环卫巡查往往靠人沿着街道一遍遍走、用眼睛看,既费人力又很难把每一处都覆盖到位。这几年深度学习在图像识别上变得真正可用,像 YOLO 这类目标检测算法已经能把图片里的特定物体框出来并标上类别,这给自动发现路面垃圾提供了一条比较实在的思路。对于一个想做计算机视觉方向毕设的本科生来说,城市垃圾检测这类题目离生活近、数据能找到、算法又有成熟框架可依托,做起来既有可落地的画面,也不会因为门槛太高而卡住。把公开的城市垃圾数据集拿来训练,再搭一个能上传图片、视频或调用摄像头做识别的小系统,刚好能把课堂上学过的图像处理与模型推理串成一条完整的线。正因如此,这个题目适合作为毕业设计来练手和展示,既不空泛也不过度复杂。
从个人学习角度看,这个题目把数据集处理、模型训练、接口开发与前端展示串在了一起,做完一遍对深度学习和 Web 应用怎么配合会有比较具体的体会,比单独看课本要扎实一些。从实际应用角度看,系统能帮巡检人员快速从图片或视频里找出垃圾位置,算是一种减轻人工负担的辅助手段,当然它只是个毕设级别的演示,离真正上线部署还有不少工程细节要补,不能把它说得太满。从教学展示角度看,项目从数据画像、训练曲线到推理拒识都有可追溯的产物,答辩时容易把每一步讲清楚,也方便后来同学照着复现。说到底它更像一个把课堂知识落到地上的练习,价值在于把流程走通、把原理弄懂,而不是宣称解决了什么重大的行业难题。保持这个分寸,做出来的东西反而更经得起追问。
城市垃圾检测-核心功能
图片识别:在图片识别页上传本地图片,后端调用模型推理,返回带中文类别名和置信度的检测框,并在结果区画出位置,适合处理单张街景照片。 视频识别:上传视频文件,系统按帧抽取画面逐帧识别,结果随播放进度叠加显示,适合处理已有的巡检录像或监控片段。 摄像头识别:打开设备摄像头采集实时画面,点击开始识别后连续检测,适合现场演示和交互展示,能直观看到模型在真实画面里的表现。 识别历史:每次成功识别都会在历史表留下记录,支持按任务类型、检出类别检索,详情页展示类别、平均置信度、耗时与原图尺寸,并支持软删除,方便回看与整理。 统计大屏:独立的大屏页面以图表形式呈现聚合结果,类别分布用环图、趋势用折线、置信度用柱图、时段用热力图,直观反映识别行为的整体样貌。 登录与权限:提供注册、登录与个人信息维护,管理员与普通用户看到的功能范围不同,保证普通用户只能看到自己的识别数据,账号之间不串台。
城市垃圾检测-视频展示
[video(video-sfa5dVpr-1792763259913)(type-csdn)(url-live.csdn.net/v/embed/549…)]
城市垃圾检测-图片展示
城市垃圾检测-代码展示
from pyspark.sql import SparkSession
import numpy as np, onnxruntime as ort, json
from django.db.models import Count
from django.db.models.functions import TruncDate
from main.models import recognitionevent
from datetime import datetime, timedelta
spark = SparkSession.builder.appName("UrbanGarbageBatch").master("local[*]").getOrCreate()
def predict_core(pil_img, model_pkg, conf_th=0.4):
rgb = np.asarray(pil_img.convert("RGB"))
if rgb.std() < 5:
return {"code": 3, "reason": "pure_color"}
arr, ratio, pad, ow, oh = preprocess_image(pil_img, model_pkg["preprocess"])
inp = model_pkg["session"].get_inputs()[0].name
out = model_pkg["session"].run(None, {inp: arr})[0]
e = energy_score(out, model_pkg["ood"], model_pkg["meta"])
if e is not None and e < float(model_pkg["ood"]["energy_thr"]):
return {"code": 3, "reason": "out_of_distribution"}
results = postprocess_detect(out, model_pkg["meta"], ratio, pad, (ow, oh), conf_th, model_pkg["id"])
if not results:
return {"code": 2, "msg": "未检出有效目标,请更换清晰图片后重试"}
for r in results:
r["class_name"] = display_name(model_pkg["id"], r["class"])
results.sort(key=lambda x: x["conf"], reverse=True)
return {"code": 0, "data": results, "model": model_pkg["id"]}
def dashboard_stats(request):
qs = recognitionevent.objects.filter(is_success=True)
if not _is_admin(request):
qs = qs.filter(user=_uid(request))
class_rows = list(qs.exclude(top1_class="").values("top1_class").annotate(c=Count("id")).order_by("-c")[:20])
class_dist = [{"name": r["top1_class"] or "未知", "count": r["c"]} for r in class_rows]
since = datetime.now() - timedelta(days=14)
trend_rows = list(qs.filter(created_at__gte=since).annotate(d=TruncDate("created_at")).values("d").annotate(c=Count("id")).order_by("d"))
trend = [{"date": (r["d"].strftime("%Y-%m-%d") if r["d"] else ""), "count": r["c"]} for r in trend_rows]
buckets = [0] * 10
for conf in qs.values_list("confidence", flat=True):
try:
c = float(conf or 0.0)
except Exception:
c = 0.0
buckets[min(9, max(0, int(c * 10)))] += 1
conf_hist = [{"name": "%.1f-%.1f" % (i / 10.0, (i + 1) / 10.0), "count": buckets[i]} for i in range(10)]
heat = {}
week_ago = datetime.now() - timedelta(days=7)
for t in qs.filter(created_at__gte=week_ago).values_list("created_at", flat=True):
wd = t.weekday()
h = t.hour
heat[(wd, h)] = heat.get((wd, h), 0) + 1
return {"code": 0, "data": {"class_dist": class_dist, "trend": trend, "conf_hist": conf_hist, "heatmap": heat}}
def batch_event_analysis_with_spark():
rows = list(recognitionevent.objects.filter(is_success=True).values("created_at", "top1_class", "task_type", "confidence"))
if not rows:
return {"total": 0, "high_conf": 0, "daily": [], "by_type": [], "by_class": [], "by_weekday": []}
df = spark.createDataFrame(rows)
df.createOrReplaceTempView("events")
daily = spark.sql("SELECT date(created_at) AS day, COUNT(*) AS cnt FROM events GROUP BY date(created_at) ORDER BY day")
by_type = spark.sql("SELECT task_type, COUNT(*) AS cnt FROM events GROUP BY task_type")
by_class = spark.sql("SELECT top1_class, COUNT(*) AS cnt FROM events GROUP BY top1_class ORDER BY cnt DESC")
weekday = spark.sql("SELECT dayofweek(created_at) AS wd, COUNT(*) AS cnt FROM events GROUP BY dayofweek(created_at) ORDER BY wd")
total = spark.sql("SELECT COUNT(*) AS t FROM events").collect()[0]["t"]
high = spark.sql("SELECT COUNT(*) AS h FROM events WHERE confidence >= 0.7").collect()[0]["h"]
ratio = round(high / total, 3) if total else 0.0
daily_pdf = daily.toPandas()
type_pdf = by_type.toPandas()
class_pdf = by_class.toPandas()
weekday_pdf = weekday.toPandas()
return {"total": int(total), "high_conf_count": int(high), "high_conf_ratio": ratio, "daily": daily_pdf.to_dict("records"), "by_type": type_pdf.to_dict("records"), "by_class": class_pdf.to_dict("records"), "by_weekday": weekday_pdf.to_dict("records")}
城市垃圾检测-结语
做计算机毕设的路上一个人琢磨很容易卡壳,要是你在城市垃圾检测这个题目上遇到环境配置、模型训练或者网站对接的问题,可以来我的主页翻翻,里面留了技术员的联系方式,有空可以互相交流下经验。如果这篇内容对你有点用,顺手点个赞、收藏一下就很感谢了,也欢迎关注我,或者到评论区说说你现在的毕设进度和困惑,大家一起给彼此打打气。