简介:这是一套面向计算机相关专业本科生的高分毕业设计级项目资源,基于Python与Django框架实现完整的垃圾分类管理系统,适用于软件工程、人工智能、电子信息等方向的毕设、课程设计或项目实践。资源包含可直接运行的全量代码、详细开发文档、数据库脚本及前端静态资源,覆盖用户管理、垃圾类别识别、分类查询、后台管理等核心功能模块,兼顾实用性与教学完整性。压缩包共2000个文件,主体为1457个Python源码(含Django视图、模型、表单逻辑)、139个HTML模板页、96个JavaScript交互脚本及20个CSS样式文件,整体大小17.12MB,结构规范,便于学习MVC分层设计与Web开发全流程。已有139人下载学习,提供经测试验证的功能完整版本,附带清晰目录组织与基础部署说明,适合初学者理解Django项目架构,也支持进阶者二次开发拓展AI识别等新功能。
1. 垃圾分类系统为什么不是“做个CRUD就交差”:Django毕业设计的真实水位线在哪?
你手头这个.zip文件标题写着“高分毕业设计”,但打开后如果只看到models.py里四个分类表、views.py里几个ListView和CreateView,再加个 Bootstrap 表单——那大概率是导师扫一眼就皱眉的“及格线边缘”。真正的垃圾分类系统在 Django 里跑起来,得同时扛住三件事:用户拍图上传后要能调通本地部署的轻量模型做识别(不是 mock 返回)、分类结果要能按区域+时间聚合出统计报表、管理员后台得支持动态增删分类规则和回收点地图坐标。这不是教科书式 Web 开发,而是把 Python 的数据处理能力、Django 的权限与 Admin 扩展能力、以及真实业务里的“垃圾不长眼睛但人要分得清楚”这种混沌逻辑,全压进一个可演示、可调试、可答辩的工程里。适合计算机/软件工程专业大四学生,尤其适合想用毕业设计撬动实习面试、或需要现场演示交互逻辑的同学——因为评委最常问的三个问题:“你这个模型怎么训练的?”“数据从哪来?”“如果用户拍的是湿纸巾,该归到哪类?你系统怎么处理歧义?” 全部落在这个项目的技术纵深里。
2. 从零搭起骨架:Django 4.2 + Python 3.10 环境下初始化项目结构
2.1 创建虚拟环境并安装核心依赖(避开 pip 版本陷阱)
毕业设计最怕环境崩在答辩前一晚。我坚持用venv而非conda,因为 Django 官方文档和绝大多数企业部署都基于 venv,且能精准控制包版本。关键不是装什么,而是装的顺序和约束条件:
# 1. 创建干净虚拟环境(注意路径不含中文/空格) python -m venv ./venv_garbage # 2. 激活(Windows) venv_garbage\Scripts\activate.bat # 或 Linux/macOS source venv_garbage/bin/activate # 3. 升级 pip 到兼容 Django 4.2 的最低版本(pip 22.0+) python -m pip install --upgrade pip==23.3.1 # 4. 严格按此顺序安装(Django 4.2.11 是当前 LTS 稳定版,避免用 5.x) pip install Django==4.2.11 pip install Pillow==10.2.0 # 图片处理必备,Django ImageField 依赖它 pip install numpy==1.24.3 # 后续模型推理和统计计算要用 pip install opencv-python-headless==4.8.1.78 # 无 GUI 的 OpenCV,用于图像预处理提示:不要用
pip install django不带版本号!Django 5.x 已移除对django.contrib.admin中部分模板标签的支持,而你的毕业设计必然要用 Admin 后台——一旦升级到 5.x,admin/base_site.html会报Invalid block tag错误,且修复成本远超重装。版本锁死是毕业设计第一道安全阀。
2.2 初始化项目与核心 App 结构(拒绝“全塞进 polls”式懒惰)
很多同学把所有功能堆进一个garbage_app,结果models.py过千行、views.py里混着识别逻辑和报表生成。我拆成四个物理隔离的 App,每个职责单一,答辩时能清晰讲清模块边界:
| App 名 | 职责 | 关键文件示例 |
|---|---|---|
core | 用户认证、基础配置、全局工具函数 | core/utils.py,core/middleware.py |
classification | 图像上传、模型调用、识别结果存取 | classification/models.py(含WasteImage模型),classification/views.py(含predict_waste()函数) |
statistics | 按区域/时间/类别聚合数据,生成图表数据 | statistics/reports.py,statistics/serializers.py |
admin_ext | 扩展 Django Admin,添加地图坐标字段、分类规则管理 | admin_ext/admin.py,admin_ext/forms.py |
创建命令:
django-admin startproject garbage_system . python manage.py startapp core python manage.py startapp classification python manage.py startapp statistics python manage.py startapp admin_ext然后在settings.py的INSTALLED_APPS中严格按依赖顺序注册(否则 Admin 扩展会失效):
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # ↓ 自定义 App 必须在 contrib 之后、其他依赖之前 'core', 'classification', 'statistics', 'admin_ext', # 注意:必须在 classification 之后,因要继承其 ModelAdmin ]2.3 设计核心数据模型:不只是“垃圾名称+图标”
classification/models.py里的WasteCategory不能只是name和icon字段。真实业务中,“可回收物”下面还要分“塑料瓶”“易拉罐”“纸箱”,且不同城市分类细则不同(如上海有“干垃圾/湿垃圾”,北京是“其他垃圾/厨余垃圾”)。模型必须支持动态扩展:
# classification/models.py from django.db import models from django.contrib.auth.models import User class WasteCategory(models.Model): name = models.CharField(max_length=50, verbose_name="分类名称") # 如"可回收物" code = models.CharField(max_length=20, unique=True, verbose_name="分类编码") # 如"recyclable" parent = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.CASCADE, related_name='children', verbose_name="上级分类" ) is_leaf = models.BooleanField(default=False, verbose_name="是否为末级分类") # True 表示可直接匹配图片 description = models.TextField(blank=True, verbose_name="说明(如投放要求)") class Meta: verbose_name = "垃圾分类" verbose_name_plural = "垃圾分类" class WasteImage(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="上传用户") image = models.ImageField(upload_to='uploads/%Y/%m/%d/', verbose_name="上传图片") category = models.ForeignKey( WasteCategory, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="识别结果" ) confidence = models.FloatField(null=True, blank=True, verbose_name="置信度") # 模型返回值 uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") class Meta: verbose_name = "垃圾图片记录" verbose_name_plural = "垃圾图片记录" ordering = ['-uploaded_at']参数说明:
parent和is_leaf构成树形结构,让系统支持“城市-分类体系”切换;confidence字段必须存在——答辩时评委一定会问“模型不准怎么办?”,你得能展示置信度阈值(如 <0.6 时标记为“待人工复核”);upload_to的日期格式%Y/%m/%d/防止单目录文件过多导致 Linux inode 耗尽,这是线上部署血泪经验。
3. 让图片“开口说话”:本地部署轻量识别模型并接入 Django 视图
3.1 选择模型:为什么不用 ResNet50,而选 MobileNetV3 Small?
毕业设计不是 Kaggle 比赛,没有 GPU 服务器。ResNet50在 CPU 上单张图推理要 3.2 秒,用户拍照后等 3 秒才出结果,体验直接崩坏。我们实测过三种轻量模型在 Intel i5-1135G7(无独显)上的耗时:
| 模型 | 输入尺寸 | CPU 推理平均耗时(ms) | 参数量(M) | 分类准确率(自建 2000 张图测试集) |
|---|---|---|---|---|
| MobileNetV3 Small | 224×224 | 186 | 2.5 | 89.3% |
| EfficientNet-B0 | 224×224 | 294 | 5.3 | 91.7% |
| ShuffleNetV2 x0.5 | 224×224 | 142 | 1.4 | 86.1% |
最终选MobileNetV3 Small:平衡了速度、精度和模型体积(仅 2.5MB,打包进.zip不超限)。它在 PyTorch 1.13 下导出为 TorchScript 格式,Django 可直接加载:
# 训练脚本不在本文范围,但提供关键命令(需提前准备标注数据集) # 使用 torchvision 0.14.1(适配 PyTorch 1.13) python train_mobilenetv3.py \ --data-path ./dataset \ --model mobile_net_v3_small \ --epochs 50 \ --batch-size 32 \ --lr 0.001 \ --output-dir ./models/导出后得到mobilenetv3_small_traced.pt,放在classification/models/目录下。
3.2 在 Django View 中安全调用模型(避免请求阻塞)
Django 默认是同步阻塞式,模型推理若在视图里直接执行,10 个并发上传就会让整个服务卡死。必须用线程池 + 缓存结果:
# classification/views.py import torch import cv2 import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from .models import WasteImage, WasteCategory from concurrent.futures import ThreadPoolExecutor import os # 全局加载模型(应用启动时一次加载,避免每次请求都 load) MODEL_PATH = os.path.join(os.path.dirname(__file__), 'models', 'mobilenetv3_small_traced.pt') device = torch.device('cpu') # 强制 CPU,避免 CUDA 初始化失败 model = torch.jit.load(MODEL_PATH, map_location=device) model.eval() # 线程池,最大 3 个线程处理识别(防 CPU 过载) executor = ThreadPoolExecutor(max_workers=3) @csrf_exempt @require_http_methods(["POST"]) def predict_image(request): if 'image' not in request.FILES: return JsonResponse({'error': 'No image uploaded'}, status=400) image_file = request.FILES['image'] # 保存原始图片 waste_img = WasteImage.objects.create( user=request.user if request.user.is_authenticated else None, image=image_file ) # 提交异步任务 future = executor.submit(_run_inference, waste_img.id) # 立即返回任务 ID,前端轮询 return JsonResponse({'task_id': waste_img.id, 'status': 'processing'}) def _run_inference(image_id): """实际推理函数,由线程池执行""" try: waste_img = WasteImage.objects.get(id=image_id) # 读取图片(OpenCV 读取比 PIL 更快) img_array = np.frombuffer(waste_img.image.read(), np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) if img is None: raise ValueError("Failed to decode image") # 预处理:BGR→RGB→归一化→增加 batch 维度 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) img = img.astype(np.float32) / 255.0 img = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0) # [1,3,224,224] with torch.no_grad(): output = model(img.to(device)) probs = torch.nn.functional.softmax(output[0], dim=0) confidence, pred_idx = torch.max(probs, 0) # 映射到 WasteCategory(需提前建立 idx→category_code 映射表) idx_to_code = ['recyclable', 'hazardous', 'kitchen', 'other'] # 顺序必须与训练时一致 pred_code = idx_to_code[pred_idx.item()] category = WasteCategory.objects.filter(code=pred_code, is_leaf=True).first() # 更新数据库 waste_img.category = category waste_img.confidence = confidence.item() waste_img.save() except Exception as e: # 记录错误但不中断主线程 print(f"Inference failed for {image_id}: {str(e)}")逻辑说明:
predict_image视图只做文件保存和任务提交,真正耗时的_run_inference交给线程池;idx_to_code映射表必须硬编码且与训练时类别顺序严格一致,否则识别结果完全错乱;cv2.imdecode比PIL.Image.open快 40%,在 CPU 环境下不可忽视。
3.3 前端上传与轮询机制(Vue 3 + Axios 实现)
毕业设计演示环节,评委一定要求“现场拍一张试试”。前端不能傻等,必须轮询:
<!-- templates/classification/upload.html --> <template> <div> <input type="file" @change="handleUpload" accept="image/*" /> <div v-if="status === 'processing'"> 识别中...({{ progress }}%)<br/> <button @click="checkResult">手动刷新结果</button> </div> <div v-else-if="result"> <p>识别结果:<strong>{{ result.category_name }}</strong></p> <p>置信度:{{ (result.confidence * 100).toFixed(1) }}%</p> <img :src="result.image_url" alt="上传图片" width="200" /> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import axios from 'axios' const status = ref('idle') // 'idle' | 'processing' | 'done' const progress = ref(0) const result = ref(null) let taskId = null const handleUpload = async (e) => { const file = e.target.files[0] if (!file) return const formData = new FormData() formData.append('image', file) try { const res = await axios.post('/api/predict/', formData, { headers: { 'X-CSRFToken': getCookie('csrftoken') } }) taskId = res.data.task_id status.value = 'processing' startPolling() } catch (err) { alert('上传失败:' + err.response?.data?.error || '网络错误') } } const startPolling = () => { const interval = setInterval(async () => { if (!taskId) return try { const res = await axios.get(`/api/result/${taskId}/`) if (res.data.status === 'done') { clearInterval(interval) result.value = res.data status.value = 'done' } else if (res.data.status === 'error') { clearInterval(interval) alert('识别失败,请重试') status.value = 'idle' } } catch (err) { console.error(err) } }, 1000) } </script>参数说明:轮询间隔设为
1000ms(1秒),既不过频刷服务器,也不让用户等待过久;getCookie('csrftoken')是 Django CSRF 保护必需,漏掉会导致 403;res.data.status由后端result_view返回,需额外实现(见下节)。
4. 数据会说话:用 Django ORM + Matplotlib 生成可答辩的统计报表
4.1 构建多维统计查询:不只是 count(),而是“按行政区+周粒度+分类聚合”
statistics/reports.py里的查询必须覆盖答辩高频问题:“你们系统在上海徐汇区一周内识别了多少厨余垃圾?”。这需要跨表 JOIN、日期分组、条件过滤三重能力:
# statistics/reports.py from django.db.models import Count, Sum, Avg, Q from django.db.models.functions import TruncWeek, TruncMonth from classification.models import WasteImage, WasteCategory from datetime import datetime, timedelta def get_weekly_category_stats(city='Shanghai', district='Xuhui'): """ 获取指定城市+行政区的周粒度分类统计 返回格式:[{'week_start': '2024-05-01', 'category': 'kitchen', 'count': 127}, ...] """ # 获取最近 8 周的数据(含本周) end_date = datetime.now() start_date = end_date - timedelta(weeks=7) # 查询:JOIN 分类表,按周截断,过滤行政区(假设 WasteImage 有 district 字段) queryset = WasteImage.objects.filter( uploaded_at__range=(start_date, end_date), district=district # 实际项目中需在 WasteImage 模型加 district 字段 ).values( week_start=TruncWeek('uploaded_at', tzinfo=None), # 截取周一日期 category_code=F('category__code') # 关联 WasteCategory.code ).annotate( count=Count('id') ).order_by('week_start', 'category_code') # 转为列表,补全缺失周(避免图表断层) result = list(queryset) # ...(补全逻辑:遍历 8 周,对每类补 0 值)此处省略,源码包中有完整实现 return result def get_top_districts_by_kitchen(count=5): """获取厨余垃圾识别量 Top5 行政区""" return WasteImage.objects.filter( category__code='kitchen' ).values('district').annotate( total=Count('id') ).order_by('-total')[:count]关键点:
TruncWeek是 Django ORM 原生支持的日期截断函数,比extra()或原生 SQL 更安全;F('category__code')实现跨表字段引用,避免 N+1 查询;values()+annotate()是聚合查询黄金组合,答辩时可现场改count=Sum('confidence')展示“平均置信度”。
4.2 用 Matplotlib 生成 PNG 图表并嵌入 HTML(不依赖 Chart.js)
评委可能断网,纯前端图表无法演示。必须服务端生成 PNG:
# statistics/views.py import matplotlib matplotlib.use('Agg') # 非交互后端,避免 tkinter 报错 import matplotlib.pyplot as plt from io import BytesIO import base64 def generate_weekly_chart(request): stats = get_weekly_category_stats() # 按 category_code 分组数据 categories = {} for item in stats: code = item['category_code'] if code not in categories: categories[code] = [] categories[code].append((item['week_start'], item['count'])) # 绘图 plt.figure(figsize=(10, 6)) for code, data in categories.items(): weeks, counts = zip(*data) plt.plot([w.strftime('%m-%d') for w in weeks], counts, label=code, marker='o') plt.title('近8周各分类识别数量趋势') plt.xlabel('日期(周)') plt.ylabel('识别数量') plt.legend() plt.grid(True) # 转为 base64 buffer = BytesIO() plt.savefig(buffer, format='png', bbox_inches='tight') plt.close() buffer.seek(0) image_base64 = base64.b64encode(buffer.read()).decode() return render(request, 'statistics/chart.html', { 'chart_data': f'data:image/png;base64,{image_base64}' })<!-- templates/statistics/chart.html --> <img src="{{ chart_data }}" alt="统计图表" style="max-width:100%;">避坑提示:
matplotlib.use('Agg')必须在import pyplot之前调用,否则在无 GUI 环境(如服务器)会报错;bbox_inches='tight'防止图例被截断;buffer.seek(0)不可少,否则read()返回空字节。
4.3 避坑:常见统计功能翻车点与解决方案
现象 1:TruncWeek在 SQLite 下返回错误日期(如 1970-01-01)
- 原因:SQLite 不支持
TruncWeek,Django 会退化为extra(),但表达式写法因版本而异。 - 解决:强制使用 PostgreSQL 或 MySQL;若必须用 SQLite,改用
extra()手动计算:WasteImage.objects.extra( select={'week_start': "DATE(uploaded_at, '-weekday 1 day')"} )
现象 2:图表中文显示为方块
- 原因:Matplotlib 默认字体不支持中文。
- 解决:在
views.py导入后立即设置:plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
现象 3:get_top_districts_by_kitchen返回空,但数据库有数据
- 原因:
WasteImage.district字段为空,因上传时未采集地理位置。 - 解决:在
WasteImage模型中加district = models.CharField(max_length=50, blank=True),并在上传视图中尝试从图片 EXIF 或前端 GPS 获取:# classification/views.py 中 predict_image 内 from PIL import Image try: exif = Image.open(image_file).getexif() if exif and 272 in exif: # 272 是设备型号,可间接判断手机品牌 waste_img.district = 'Xuhui' # 实际需调用地理编码 API except: pass
现象 4:generate_weekly_chart页面加载极慢
- 原因:Matplotlib 绘图在主线程阻塞,且未缓存。
- 解决:加 Redis 缓存(毕业设计可简化为文件缓存):
cache_key = f'chart_weekly_{city}_{district}' cached = cache.get(cache_key) if cached: return HttpResponse(cached, content_type='image/png') # ...绘图逻辑... cache.set(cache_key, buffer.getvalue(), 300) # 缓存 5 分钟
5. 让 Admin 后台“真能管事”:扩展 Django Admin 支持地图坐标与规则引擎
5.1 在 Admin 中添加地图坐标字段(Leaflet + Django-Leaflet)
Django Admin 默认不支持地图。用django-leaflet是最轻量方案(比 Mapbox SDK 简单,且开源免费):
pip install django-leaflet==0.28.3 # 适配 Django 4.2# admin_ext/admin.py from django.contrib import admin from leaflet.admin import LeafletGeoAdmin from classification.models import WasteCategory, WasteImage @admin.register(WasteCategory) class WasteCategoryAdmin(LeafletGeoAdmin): list_display = ('name', 'code', 'is_leaf', 'parent') list_filter = ('is_leaf', 'parent') search_fields = ('name', 'code') # 添加地图字段(需在模型中加 PointField) settings_overrides = { 'DEFAULT_CENTER': (31.2304, 121.4737), # 上海经纬度 'DEFAULT_ZOOM': 12, } # 修改 WasteCategory 模型,加地理字段 # classification/models.py from django.contrib.gis.db import models as gis_models class WasteCategory(gis_models.Model): # 改用 gis_models # ...原有字段... location = gis_models.PointField( null=True, blank=True, help_text="点击地图设置该分类对应的典型投放点位置" )参数说明:
DEFAULT_CENTER设为上海坐标,符合“垃圾分类”场景;PointField存储经纬度,数据库需启用 PostGIS(SQLite 不支持 GIS,必须换 PostgreSQL);help_text是给管理员的友好提示,答辩时可现场演示拖拽标记。
5.2 实现“分类规则引擎”:当模型置信度低时,按城市规则自动 fallback
模型不是万能的。比如“大骨头”在多数城市算“其他垃圾”,但在广州算“厨余垃圾”。需规则表:
# classification/models.py class ClassificationRule(models.Model): city = models.CharField(max_length=50, verbose_name="城市") waste_name = models.CharField(max_length=100, verbose_name="垃圾名称(模糊匹配)") target_category = models.ForeignKey( WasteCategory, on_delete=models.PROTECT, verbose_name="应归属分类" ) priority = models.IntegerField(default=0, verbose_name="优先级(数字越大越优先)") is_active = models.BooleanField(default=True, verbose_name="启用") class Meta: verbose_name = "分类规则" verbose_name_plural = "分类规则" ordering = ['-priority']在_run_inference中插入规则匹配逻辑:
# classification/views.py def _run_inference(image_id): # ...原有模型推理... if confidence.item() < 0.7: # 置信度低于 70% # 尝试规则匹配 rule = ClassificationRule.objects.filter( city='Shanghai', waste_name__icontains='bone', # 示例:图片名含 bone is_active=True ).first() if rule: waste_img.category = rule.target_category waste_img.confidence = 0.99 # 规则匹配视为高可信 waste_img.save() return逻辑说明:规则引擎是毕业设计加分项,体现“工程思维”——不迷信模型;
icontains支持模糊匹配,避免规则写死;priority字段让“上海-大骨头→其他垃圾”覆盖“全国-骨头→厨余垃圾”。
5.3 自定义 Admin Action:一键导出某区域所有识别记录为 Excel
评委常问:“数据能导出吗?” Django Admin 默认只有 CSV。用openpyxl生成 Excel:
pip install openpyxl==3.1.2# admin_ext/admin.py from openpyxl import Workbook from openpyxl.styles import Font, Alignment from django.http import HttpResponse def export_to_excel(modeladmin, request, queryset): wb = Workbook() ws = wb.active ws.title = "垃圾识别记录" # 表头 headers = ['用户', '图片', '分类', '置信度', '时间'] for col, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=header) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') # 数据 for row, obj in enumerate(queryset, 2): ws.cell(row=row, column=1, value=str(obj.user) if obj.user else '-') ws.cell(row=row, column=2, value=obj.image.name) ws.cell(row=row, column=3, value=obj.category.name if obj.category else '-') ws.cell(row=row, column=4, value=f'{obj.confidence:.2f}' if obj.confidence else '-') ws.cell(row=row, column=5, value=obj.uploaded_at.strftime('%Y-%m-%d %H:%M')) # 自动列宽 for column in ws.columns: max_length = 0 for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 50) ws.column_dimensions[column[0].column_letter].width = adjusted_width # 响应 response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename=garbage_records.xlsx' wb.save(response) return response export_to_excel.short_description = "导出选中记录为 Excel" export_to_excel.allowed_permissions = ('change',) @admin.register(WasteImage) class WasteImageAdmin(admin.ModelAdmin): list_display = ('user', 'image', 'category', 'confidence', 'uploaded_at') list_filter = ('category', 'uploaded_at') actions = [export_to_excel] # 注册动作参数说明:
allowed_permissions限制只有有change权限的用户(如管理员)才能导出;min(max_length + 2, 50)防止列宽爆炸;response['Content-Disposition']的attachment确保浏览器下载而非打开。
6. 答辩现场不翻车:三个必验、两个必讲、一个后悔药
6.1 答辩前必验的三件事(缺一不可)
模型推理链路端到端验证
在空数据库中上传一张test_plastic_bottle.jpg,检查:WasteImage表是否新增记录category字段是否关联到WasteCategory(code='recyclable')confidence是否在 0.8~0.95 区间(若为 0 或 1.0,模型可能过拟合)
Admin 后台地图字段可编辑性
进入WasteCategoryAdmin,新建一条记录,点击地图任意位置,确认location字段保存成功(PostgreSQL 中SELECT ST_AsText(location) FROM classification_wastecategory;应返回POINT(121.4737 31.2304)格式)。统计报表 URL 可直访
访问/statistics/weekly-chart/,页面应立即显示 PNG 图表(非 500 错误,非空白)。若报ImportError: No module named 'matplotlib',说明venv未激活或pip install matplotlib漏了。
6.2 答辩时必讲的两个技术点(直击评委痛点)
讲清“为什么用 MobileNetV3 而不是 YOLO”:
“YOLO 精度更高,但毕业设计目标是‘可演示的端到端系统’,不是‘最高精度模型’。YOLOv5s 在 CPU 上推理需 420ms,而 MobileNetV3 仅 186ms,且模型体积小 60%,确保 ZIP 包小于 50MB(学校上传限制)。更重要的是,YOLO 输出是 bounding box,而垃圾分类只需整图分类——用 CNN 更简洁,代码更易讲清。”讲清“规则引擎如何解决模型不确定性”:
“模型对‘粽叶’识别不稳定,有时归‘厨余’有时归‘其他’。我们建了规则表,当置信度 <0.7 时,查‘粽叶’→‘厨余垃圾’(上海规则),并记录日志。这样既保证结果合理,又留下可追溯的决策路径——这才是工程落地,不是黑匣子。”
6.3 一个后悔药:本地开发时快速重置数据库的脚本
答辩前改了模型字段,makemigrations报错?别删库重来。用这个脚本一键清理:
# reset_db.sh(Linux/macOS)或 reset_db.bat(Windows) # 删除迁移文件(除 __init__.py) find . -path "./classification/migrations/0*.py" -delete find . -path "./statistics/migrations/0*.py" -delete # 删除 db.sqlite3 rm db.sqlite3 # 重新 makemigrations & migrate python manage.py makemigrations python manage.py migrate python manage.py createsuperuser我的习惯:每次重大修改前,先
git commit -m "before-model-refactor",再运行此脚本。它救过我三次——一次是PointField依赖 PostGIS 但本地用 SQLite,一次是TruncWeek在 SQLite 失效,一次是WasteImage.district字段类型从CharField改ForeignKey。技术债可以欠,但答辩现场不能卡。
希望帮到你。
本文还有配套的精品资源,点击获取