☰
基于Python的垃圾分类数据系统:Flask+SQLite+MobileNetV3全链路实现
2026/10/3 3:01:09 网站建设 项目流程

简介:这份资源是面向计算机、通信、人工智能、自动化等相关专业学生与教师的Python毕业设计垃圾分类数据系统完整源码包,适合作为毕业设计、期末课程设计或课程大作业的参考方案,也可供初学者学习进阶。压缩包共20个文件,约35.12MB,包含7个py源码文件、6个zip数据集压缩包、3个ui界面文件以及cpp、jpg、md、png等辅助文件,涵盖垃圾分类识别核心逻辑、界面设计与数据集资源。项目代码均经过调试测试,可正常运行,答辩评审分达到98分,具有较高的学习借鉴价值。目前已有296人学习下载。读者可从中获取完整的项目目录结构、垃圾分类算法实现思路、UI界面搭建方式与数据集组织方案,基础较好的同学还能在此基础上修改调整,实现不同功能扩展,是毕业设计选题与实战练手的实用参考。

1. 垃圾分类数据系统:从一张图片到一条可查的投放记录

你拍一张外卖餐盒的照片,系统要能告诉你这是“其他垃圾”还是“可回收物”,并且把这次识别结果、时间、设备编号写进数据库,后台还能按小区、按日期统计各类垃圾的投放量——这就是“基于 Python 的垃圾分类数据系统”要干的事。它不是一个单纯的图像分类 demo,而是把识别模型、后端接口、数据存储和统计查询串成一条完整链路。适合正在做计算机毕业设计、需要一套能跑通、能演示、能写进论文的完整项目的同学,也适合想用 Python 快速搭一个“AI + 数据管理”原型的开发者。热搜里“python 毕业设计”“垃圾分类”“数据系统”“源码”这几个词,恰好对应了这套系统的四个核心模块:Python 技术栈、分类识别、数据管理、可交付代码。下面我按实际落地顺序,把选型、建库、写接口、接模型、排错和进阶技巧拆开讲。

2. 技术选型与数据建模:为什么用 Flask + SQLite + MobileNetV3

2.1 框架选型:Flask 够轻,SQLite 够用,MobileNetV3 够快

毕业设计类项目最怕两件事:环境装不上、演示时卡死。所以选型的第一原则是“依赖少、启动快、不挑机器”。后端我一般用 Flask,不是因为它比 Django 强,而是因为它没有复杂的 ORM 和中间件配置,一个app.py加几个路由就能跑起来,查 bug 时不用翻框架源码。数据库用 SQLite,单文件存储,不需要额外安装数据库服务,拷贝整个项目文件夹就能在另一台电脑上恢复数据,这对答辩现场换电脑的场景非常友好。模型侧选 MobileNetV3-Small,在 CPU 上单张图片推理大约 30 到 60 毫秒,准确率在公开垃圾分类数据集上能做到 90% 以上,比 ResNet50 轻量得多,不会出现“演示时风扇狂转、页面转圈”的尴尬。

热搜里“python 安装教程”“vscode python 环境配置”出现频率很高,说明很多同学卡在环境这一步。我的建议是:不要用最新版 Python,用 3.9 或 3.10,因为 PyTorch 和 TensorFlow 对这两个版本的支持最稳定。安装时勾选“Add Python to PATH”,然后在 VS Code 里选解释器时直接选那个带python.exe的路径,不要用 Microsoft Store 版本,否则后面装torch容易出玄学问题。

2.2 数据表设计:三张表撑起整个系统

数据系统不是把识别结果往文件里一写了事,要能按条件查、按维度统计。我一般设计三张核心表:users存管理员和普通用户,devices存投放设备信息,records存每次识别的完整记录。records表是关键,字段包括:id、device_id、image_path、predict_label、confidence、create_time。其中predict_label存分类结果(如“可回收物”),confidence存置信度,方便后续筛选低置信度样本做人工复核。

建表语句如下,直接放在init_db.py里执行一次即可:

import sqlite3 conn = sqlite3.connect('garbage.db') cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, image_path TEXT NOT NULL, predict_label TEXT NOT NULL, confidence REAL NOT NULL, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') cursor.execute(''' CREATE TABLE IF NOT EXISTS devices ( device_id TEXT PRIMARY KEY, location TEXT NOT NULL, status INTEGER DEFAULT 1 ) ''') conn.commit() conn.close()

这段代码的逻辑很直接:连接数据库文件,创建两张表,records表用自增主键,create_time默认取当前时间,省去手动传时间戳的麻烦。参数上注意confidence用 REAL 类型,不要用 TEXT,否则后面按置信度排序时会变成字符串比较,0.9会排在0.85前面,这是血泪教训。device_id用 TEXT 而不是 INTEGER,因为实际设备编号可能带字母前缀,比如BJ-001。

2.3 目录结构:让源码可交付、可复现

毕业设计源码最怕“只有作者自己能跑”。我一般固定这样的目录结构:

garbage_system/ ├── app.py # Flask 主入口 ├── init_db.py # 建库脚本 ├── model/ │ ├── train.py # 训练脚本 │ └── best_model.pth # 训练好的权重 ├── static/ │ └── uploads/ # 上传图片存储 ├── templates/ │ └── index.html # 前端页面 └── requirements.txt # 依赖清单

requirements.txt里只写实际用到的包,版本号固定,比如Flask==2.3.2、torch==2.0.1、Pillow==9.5.0。不要写torch>=2.0,否则别人装的时候可能拉到不兼容的新版本,直接翻车。这个结构的好处是:模型训练、后端服务、前端页面、数据库初始化各自独立,答辩时老师问“模型在哪训练”你直接指model/train.py,问“数据怎么存”你指init_db.py,清晰。

3. 从图片上传到分类落库:Flask 接口与模型推理的完整链路

3.1 上传接口:处理文件、调用模型、写入数据库

核心逻辑集中在一个/predict路由里。用户上传图片后,后端先保存文件,再调用模型推理,最后把结果写进records表并返回 JSON。代码如下:

import os import uuid import torch from flask import Flask, request, jsonify from PIL import Image from torchvision import transforms from model.net import MobileNetV3Small # 假设模型定义在 model/net.py app = Flask(__name__) UPLOAD_FOLDER = 'static/uploads' os.makedirs(UPLOAD_FOLDER, exist_ok=True) # 加载模型(只加载一次,不要每次请求都加载) device = torch.device('cpu') model = MobileNetV3Small(num_classes=4) model.load_state_dict(torch.load('model/best_model.pth', map_location=device)) model.eval() # 预处理:与训练时保持一致 transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) LABELS = ['可回收物', '有害垃圾', '厨余垃圾', '其他垃圾'] @app.route('/predict', methods=['POST']) def predict(): file = request.files.get('image') if not file: return jsonify({'code': 400, 'msg': '未上传图片'}), 400 # 用 uuid 重命名,避免中文文件名和重复覆盖 filename = f"{uuid.uuid4().hex}.jpg" filepath = os.path.join(UPLOAD_FOLDER, filename) file.save(filepath) img = Image.open(filepath).convert('RGB') tensor = transform(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): output = model(tensor) prob = torch.softmax(output, dim=1) confidence, pred = torch.max(prob, dim=1) label = LABELS[pred.item()] conf = round(confidence.item(), 4) # 写入数据库 conn = sqlite3.connect('garbage.db') cursor = conn.cursor() cursor.execute( 'INSERT INTO records (device_id, image_path, predict_label, confidence) VALUES (?, ?, ?, ?)', ('WEB-001', filepath, label, conf) ) conn.commit() conn.close() return jsonify({ 'code': 200, 'label': label, 'confidence': conf, 'image_path': filepath })

这段代码有几个关键点。第一,模型加载放在路由外面,全局只加载一次。如果写在predict函数里面,每次请求都会重新读权重文件,响应时间从几十毫秒变成几秒,演示时直接卡死。第二,文件名用uuid重命名,避免用户上传中文名或同名文件导致覆盖。第三,torch.no_grad()关闭梯度计算,推理时不需要反向传播,能省内存和计算量。第四,unsqueeze(0)增加 batch 维度,因为模型输入要求是[N, C, H, W],单张图片是[C, H, W],不加这一步会报维度错误。

参数上注意transform必须和训练时完全一致。训练时用的Resize((224, 224)),推理时也要用 224,不要改成 256,否则模型看到的特征分布变了,准确率会掉。Normalize的均值和标准差是 ImageNet 的固定值,如果你训练时用了别的值,这里也要对应改。

3.2 查询与统计接口:按小区、按日期聚合

数据系统不能只有写入,还要能查。我一般加两个接口:/records按条件分页查记录,/stats按日期和分类聚合统计。统计接口的 SQL 如下:

@app.route('/stats') def stats(): date = request.args.get('date', '') conn = sqlite3.connect('garbage.db') cursor = conn.cursor() if date: cursor.execute(''' SELECT predict_label, COUNT(*) as cnt FROM records WHERE DATE(create_time) = ? GROUP BY predict_label ''', (date,)) else: cursor.execute(''' SELECT predict_label, COUNT(*) as cnt FROM records GROUP BY predict_label ''') rows = cursor.fetchall() conn.close() result = [{'label': row[0], 'count': row[1]} for row in rows] return jsonify({'code': 200, 'data': result})

这里用DATE(create_time)把时间戳截断到日期,方便按天统计。GROUP BY predict_label按分类聚合,返回每个分类的数量。如果前端要画饼图,直接拿这个 JSON 去渲染就行。注意 SQLite 的DATE()函数要求create_time是标准格式,如果你手动插入的时间格式不对,统计结果会是空,所以建表时用DEFAULT CURRENT_TIMESTAMP最省心。

3.3 前端页面:一个上传框加一个结果展示区

前端不需要太复杂,一个index.html搞定。核心是一个<input type="file">和一个<button>,点击后通过fetch把图片发给/predict,拿到结果后显示分类和置信度。代码片段如下:

async function uploadImage() { const fileInput = document.getElementById('imageInput'); const file = fileInput.files[0]; if (!file) { alert('请先选择图片'); return; } const formData = new FormData(); formData.append('image', file); const response = await fetch('/predict', { method: 'POST', body: formData }); const result = await response.json(); if (result.code === 200) { document.getElementById('result').innerText = `分类:${result.label},置信度:${result.confidence}`; } else { document.getElementById('result').innerText = '识别失败'; } }

这段 JS 的逻辑是:取文件、包装成FormData、发 POST 请求、解析 JSON、更新页面。注意FormData的 key 必须和 Flask 里request.files.get('image')的'image'一致,否则后端拿不到文件。置信度显示时保留四位小数就够了,太多位数反而显得不专业。

4. 避坑与排查:模型加载、数据库锁、路径问题

4.1 模型加载报错Missing key(s) in state_dict

现象:启动 Flask 时抛出RuntimeError: Error(s) in loading state_dict for MobileNetV3Small: Missing key(s)...。原因通常是训练时用了DataParallel或多卡训练,保存的权重 key 前面多了module.前缀,而推理时用的是单卡模型。解决办法是在load_state_dict前处理一下 key:

state_dict = torch.load('model/best_model.pth', map_location='cpu') new_state_dict = {k.replace('module.', ''): v for k, v in state_dict.items()} model.load_state_dict(new_state_dict)

如果反过来,推理时用了DataParallel而权重没有前缀,就手动加上。核心是让保存和加载时的模型结构一致。

4.2 SQLite 报database is locked

现象:多人同时上传图片时,偶尔返回 500,日志显示sqlite3.OperationalError: database is locked。原因是 SQLite 默认只允许一个写操作,并发写入时会锁库。毕业设计演示通常只有一个人操作,但如果你开了多个线程或用了debug=True的热重载,可能同时有两个进程在写。解决办法有两个:一是把debug设为False再演示;二是每次写操作后立即conn.close(),不要保持长连接。如果确实需要并发,换 PostgreSQL,但毕业设计没必要。

4.3 上传图片后路径找不到

现象:数据库里image_path存的是static/uploads/xxx.jpg,但前端访问http://localhost:5000/static/uploads/xxx.jpg返回 404。原因是 Flask 的静态文件目录默认是static,你存的路径如果带了static/前缀,实际文件在static/uploads/下,访问时应该用/static/uploads/xxx.jpg。检查UPLOAD_FOLDER和app.static_folder是否一致。我一般把UPLOAD_FOLDER设为os.path.join(app.static_folder, 'uploads'),这样路径自动对齐,不会出错。

4.4 置信度全是 0.25 左右

现象:模型对任何图片都输出四个分类概率差不多,置信度在 0.25 上下。原因通常是预处理不一致,比如训练时用了Normalize,推理时忘了加;或者图片通道顺序搞错,PIL 读出来是 RGB,但训练时用的是 BGR。检查transform是否和训练脚本里完全一样,特别是Resize的尺寸和Normalize的参数。另一个可能是模型没有eval(),Dropout 和 BatchNorm 还在训练模式,输出会随机。加上model.eval()和torch.no_grad()再试。

4.5 中文标签显示乱码

现象:前端页面显示å¯åç©这样的乱码。原因是 Flask 返回 JSON 时默认用 ASCII 编码,中文被转义了。解决办法是在app.config里设置JSON_AS_ASCII = False,或者用jsonify时指定ensure_ascii=False。Flask 2.3 之后推荐用app.json.ensure_ascii = False。改完之后中文正常显示,答辩时不会尴尬。

5. 进阶技巧:用置信度阈值做人工复核队列

系统跑通之后,真正体现“数据系统”价值的不是分类准确率有多高,而是对低置信度样本的处理能力。我一般会加一个“复核队列”功能:把所有confidence < 0.7的记录标记为待复核,后台可以筛选出来人工确认,确认后更新predict_label并标记reviewed = 1。这样系统就有了闭环——模型不确定的交给人工,人工确认的结果可以回流到训练集,下次重新训练时模型会更强。

具体做法是在records表加两个字段:reviewed INTEGER DEFAULT 0和review_label TEXT。查询待复核记录:

SELECT id, image_path, predict_label, confidence FROM records WHERE confidence < 0.7 AND reviewed = 0 ORDER BY confidence ASC

按置信度升序排列,最不确定的排最前面,优先处理。人工确认后执行:

UPDATE records SET review_label = '可回收物', reviewed = 1 WHERE id = 123

这个技巧在答辩时很加分,因为它说明你考虑到了模型的不确定性,而不是盲目相信输出。另外,统计接口可以加一个维度:按reviewed分组,展示“自动确认”和“人工复核”的比例,这个指标能直接写进论文的“系统评估”章节。

还有一个实用习惯:每次重新训练模型后,不要直接覆盖best_model.pth,而是按日期备份,比如best_model_20250101.pth。这样如果新模型效果变差,可以快速回滚。我吃过这个亏,有一次调参后准确率掉了 5 个点,旧权重被覆盖,只能重新训练,浪费了一整天。现在固定保留最近三个版本,心里踏实。

希望帮到你。

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

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

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

立即咨询