☰
用Django和CNN搭建准确率90%的智能垃圾分类系统
2026/10/9 3:49:55 网站建设 项目流程

垃圾分类这件事,很多人觉得不就是分个可回收、厨余、有害吗?真到了实操层面,纯靠人眼分辨极易出错,尤其遇到“这是干垃圾还是湿垃圾”的瞬间,整个人都会僵住。我做这个“基于django图像识别的智能垃圾分类系统”,就是想用卷积神经网络替代人眼判断,再通过Django把模型服务化,做成一个能上传照片、返回分类结果、记录历史的Web应用。整个项目跑通后,从拍照到得出“可回收垃圾”的结论,大约一秒出头,识别准确率在常见生活垃圾上能稳定在90%上下,足够应付日常生活中绝大多数场景了。

这篇文章写给两类人:一是正在做课程设计、毕业设计的学生,二是想入门Python Web + 深度学习的开发者。我会把从环境搭建、模型训练到Django集成的完整思路都拆开讲,包括踩过的坑。你跟着做,不需要买显卡也能跑,数据量小可以CPU训练,核心是把链路打通。

1. 项目概述与需求拆解

1.1 智能垃圾分类到底要解决什么问题

社区里的垃圾桶虽然分了颜色,但实际投放时大家还是靠直觉:饮料瓶扔可回收,电池扔有害,剩菜扔厨余,这些好判断。麻烦的是那些“边缘垃圾”:脏了的塑料袋、沾了油的包装盒、用过的纸巾,网上说法五花八门,普通用户根本没耐心去查。这个系统要解决的就是“识别不确定性”:用户拍一张照片,模型判断它属于哪一类,然后返回类别、置信度和对应的投放建议。

从技术上讲,这是一个典型的单标签图像分类问题。输入是一张RGB图片,输出是若干类别中的一个。难点不在模型本身,而在怎么把模型“塞进”一个Web应用里,让普通人能用浏览器操作,同时还要考虑上传图片的尺寸、模型推理的耗时、分类结果的展示形式。这背后涉及三部分技术栈:图像处理、深度学习模型、Web后端框架。三者单独拎出来都有现成方案,但要拼成一个稳定可用的系统,就需要认真设计接口和数据结构。

1.2 技术选型:为什么是Django + 图像识别

图像识别这边选择面很宽,可以用TensorFlow、PyTorch,也可以调云厂商的API。但既然是自建系统,本地模型是首选,因为不依赖网络、没有按次调用费用,而且能自己控制模型细节。模型本身我选了Keras写好的预训练模型做迁移学习,既能保证准确率,又不用从零训练几千个epoch。

后端选Django,看中的是它“全家桶”式的配套:ORM管数据库、自带Admin后台、内置开发服务器、模板系统还能顺便渲染页面。做这类管理系统,Django能省掉大量重复造轮子的时间。有人觉得FastAPI更轻量,但如果你要管用户、管历史记录、做管理后台,Django的生态优势很明显。换句话说,FastAPI像工具箱,Django像精装修房,后者更契合“系统设计与实现”这类项目。

1.3 系统功能模块划分

整个系统按职责可以拆成四大块:

  • 图像识别模块:负责加载模型、预处理图片、执行推理、返回类别和置信度。
  • 业务逻辑模块:处理上传请求、调用识别模块、保存识别记录、生成响应。
  • 数据管理模块:用Django ORM管理用户、垃圾分类记录,可能还有反馈表。
  • 展示模块:前端页面负责上传、展示识别结果、历史记录查询,Admin后台负责数据维护。

这四个模块之间通过Django的MTV模式自然解耦。模型加载可以在应用启动时完成,避免每个请求都重新载入权重文件;业务逻辑放在View里,较重的预处理可以抽成服务类;数据管理完全交给ORM,不需要手写SQL。这样设计下来,后期加功能很顺手。

2. 环境准备与项目初始化

2.1 开发环境搭建:Python、Django、依赖

我用的Python 3.9,Django 4.x,Keras带TensorFlow 2.x。安装顺序有讲究:先把虚拟环境建好,再装依赖,避免把系统Python搞乱。第一步创建虚拟环境并激活:

python -m venv venv source venv/bin/activate # Windows是 venv\Scripts\activate

然后升级pip,安装核心依赖。用requirements.txt比较省事:

pip install django==4.2.7 tensorflow==2.13.0 pillow numpy

这里有个经验:TensorFlow的安装包很大,国内网络环境建议用镜像源,比如清华源的pypi。如果你机器没有GPU,装CPU版本就够了,推理一张图大概一到两秒,对于非高并发的个人项目完全能接受。GPU版本反而要额外装CUDA和cuDNN,环境坑多。

2.2 创建Django项目和应用

搭过Django的人都知道,第一步是django-admin startproject,但真正做系统时建议把“项目”和“应用”分开。项目叫waste_classification,应用单独建一个recognition,职责单一。命令如下:

django-admin startproject waste_classification cd waste_classification python manage.py startapp recognition

创建完成后记得在waste_classification/settings.py的INSTALLED_APPS里加上recognition。这一步很多人会忘,导致后面ORM模型不生效,我自己也栽过一次。接着执行迁移,把Django自带的后台应用建好:

python manage.py migrate python manage.py createsuperuser

迁移成功说明数据库连接没有问题。这里默认用的SQLite,开发阶段足够;如果要做正式部署,再切PostgreSQL。

2.3 项目结构设计

一个可维护的Django项目,目录不要全堆在一起。我用的是这种结构:

waste_classification/ ├── manage.py ├── waste_classification/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── recognition/ # 核心应用 ├── models.py ├── views.py ├── urls.py ├── services/ │ ├── model_loader.py │ └── preprocessing.py └── ml_models/ # 放训练好的模型文件 └── waste_model.h5

services目录不是Django自动生成的,而是我手动加的。把它当成模型加载和预处理的服务层,View只负责接收请求和返回响应,具体的图像处理逻辑往下拆。这样如果以后换模型,只需要改model_loader.py,View层不需要动。良好的分层,省的是维护时挠头的次数。

3. 垃圾分类图像识别模型设计与实现

3.1 分类体系与数据集准备

垃圾分类标准各地有差异,我按国内通用口径划分成四类:可回收物、厨余垃圾、有害垃圾、其他垃圾。这里有一个容易踩的坑:分类太粗会导致模型混淆,比如“塑料瓶”和“纸箱”都是可回收物,但外观差了十万八千里。所以训练阶段不要只标四个类,而是先按具体物品细分类别,比如瓶子、纸张、电池、果皮等,然后映射到四个大类。识别时模型先输出细类,再映射到大类,准确率会高很多。

数据集方面,我第一版用了网上的垃圾分类图片集,大概1万多张,但清洗后发现很多图片重复、角度单一,训练出来泛化很差。后来自己补充了一部分实拍图,包括不同光照、不同背景下的垃圾桶周围真实场景。用迁移学习的话,数据量在每类500张以上就能达到可用效果。如果实在没数据,可以用ImageDataGenerator做数据增强:旋转、平移、翻转、缩放,既能扩充数据集,也能增强模型的鲁棒性。

3.2 模型选型与训练细节

我用的模型是MobileNetV2,预训练在ImageNet上。选它的原因很务实:模型体积小、推理速度快,CPU也能跑。相比之下ResNet50虽然准确率略高,但模型文件大、推理慢,不适合做Web应用。用迁移学习时,我拿掉MobileNetV2顶层的分类层,加了一个全局平均池化层、一个Dropout层、一个Dense层,最后接一个细分类别数的Softmax输出。

训练代码的关键部分大致是这样:

from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.models import Model from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout base_model = MobileNetV2(weights='imagenet', include_top=False) base_model.trainable = False x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(128, activation='relu')(x) x = Dropout(0.2)(x) predictions = Dense(num_classes, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=predictions)

训练时base_model.trainable = False很重要。先把新加的层训练到收敛,再解冻部分卷积层做微调。如果一上来就全量训练,预训练权重会被破坏,准确率反而上不去。我用Adam优化器,初始学习率0.001,batch_size 32,训练20个epoch后看验证集准确率趋于平稳,再解冻最后几层,用更小的学习率0.0001继续训练5个epoch。

输出模型后,记得用测试集验证。我最终细分类别12个,映射到四大类后准确率大约93%。这个数字看着还行,但真实场景里用户会用各种角度拍照,所以后面我特意在预处理里加了尺寸标准化。

3.3 将模型集成到Django中

模型训练好只是第一步,集成到Django才是重头戏。最直观的做法是在每一个请求到来时加载模型,但这样性能极差,因为模型加载就要好几秒。正确的姿势是:在Django应用启动时加载一次,把模型放在全局变量里,后续请求全部复用。

我在recognition/services/model_loader.py里写了一个模块级变量:

import tensorflow as tf from tensorflow.keras.models import load_model _model = None def get_model(): global _model if _model is None: _model = load_model('recognition/ml_models/waste_model.h5') print('模型加载完成') return _model

然后在apps.py里重写ready()方法,让Django在启动应用时主动调用一次get_model(),把模型提前加载进内存。这样第一个用户请求就不用等模型加载了。需要强调一点:TensorFlow的全局图可能和Django的多线程开发服务器有兼容性问题,所以我建议在VIEWS中把模型推理封装成函数,并且确保模型推理是线程安全的。实际验证下来,用tf.function装饰推理函数能减少不少接口延迟。

图像预处理也要放一块:读取上传图片、转换为RGB、resize到224x224、归一化、扩展batch维度。很多人做预处理时直接PIL.Image.open,但忘记处理EXIF旋转,导致手机上传的竖图被模型旋转了90度,识别自然不准。我在预处理里用ImageOps.exif_transpose修正,这个细节很值得注意。

4. Django后端核心业务逻辑开发

4.1 用户与分类记录的数据模型设计

数据库设计直接影响系统能不能长期用下去。我设计了两个核心模型:一个用来记录每次识别请求,另一个用来维护物品细类到四大类的映射。Django的ORM让这个流程非常清晰。

models.py里我大致这样写的:

from django.db import models class CategoryMapping(models.Model): item_name = models.CharField(max_length=50, unique=True) category = models.CharField(max_length=10) suggestion = models.TextField() class RecognitionRecord(models.Model): image = models.ImageField(upload_to='uploads/%Y%m%d/') item_name = models.CharField(max_length=50) category = models.CharField(max_length=10) confidence = models.FloatField() created_at = models.DateTimeField(auto_now_add=True)

CategoryMapping解决从“细类”到“大类”的映射关系,也顺便存投放建议。比如“塑料瓶”映射到“可回收物”,建议文案是“请清洗后投入可回收垃圾桶”。用户通过接口上传图片,返回结果的同时,把识别记录写入数据库。这样后台可以统计哪些物品被识别得多,为后续优化提供数据。

4.2 图像上传与识别接口实现

Django处理文件上传有现成的机制。我写了一个接口,接收POST请求中的图片字段,校验格式,然后交给服务层处理。注意request.FILES拿到的是UploadedFile对象,需要先转成PIL.Image再预处理。

核心View大致是这样的:

from django.views import View from django.http import JsonResponse from django.core.files.base import ContentFile from io import BytesIO from PIL import Image from .services.model_loader import get_model from .services.preprocessing import preprocess_image from .models import RecognitionRecord, CategoryMapping class RecognizeView(View): def post(self, request): upload_file = request.FILES.get('image') if not upload_file: return JsonResponse({'error': '缺少图片'}, status=400) try: img = Image.open(upload_file) img = ImageOps.exif_transpose(img) processed = preprocess_image(img) model = get_model() prediction = model.predict(processed)[0] idx = int(prediction.argmax()) confidence = float(prediction[idx]) item_name = label_list[idx] mapping = CategoryMapping.objects.get(item_name=item_name) record = RecognitionRecord.objects.create( image=ContentFile(upload_file.read()), item_name=item_name, category=mapping.category, confidence=confidence, ) return JsonResponse({ 'item_name': item_name, 'category': mapping.category, 'confidence': round(confidence, 4), 'suggestion': mapping.suggestion, 'record_id': record.id, }) except Exception as e: return JsonResponse({'error': str(e)}, status=500)

这里有几个容易出问题的点。ContentFile保存时要传文件内容,不能直接传upload_file对象,否则会报AttributeError。如果不打算保存原图,完全可以在内存里处理完直接返回结果,不落库。但我保存原图是为了以后模型迭代用,所以特意写上了。

label_list是从模型训练时保存的类名列表,可以单独存成JSON文件,也可以放在配置里。千万不要硬编码在View里,那样换模型时容易漏改。

4.3 分类结果返回与前端交互

接口设计成JSON格式,前端无论是用模板渲染还是做SPA都很方便。除了类别和置信度,我还会返回一个suggestion字段,告诉用户怎么处置。这个设计在用户体验上很加分——不是冷冰冰地告诉他“这是可回收”,而是告诉他“纸箱请压扁后投放,保持清洁”。

前端我采用的是简单模板加原生JavaScript,页面包含上传区域、预览图、结果展示三个部分。用户点击上传图片后,前端用FormData把图片POST到接口,拿到JSON结果后渲染到页面上。这里有一个安全细节:Django默认开启了CSRF校验,如果在模板中写fetch请求,必须带上CSRF token。可以用Django提供的{% csrf_token %}在页面中渲染,然后在JS里读取cookie中的csrftoken。

如果你做的是纯API项目,可以在settings.py里加@csrf_exempt装饰器,但我建议保留CSRF,因为系统要配合登录用户使用,安全性更完整。

5. 系统部署与常见问题排查

5.1 本地运行与调试

开发阶段直接跑python manage.py runserver是最省事的。启动后先访问Admin后台,确认数据模型有没有注册好。如果CategoryMapping是空表,记得通过Admin或者shell脚本初始化几条映射数据,否则接口查到DoesNotExist会直接500。

日常调试中我习惯用Postman先测接口,再跑去调前端。上传图片的接口,Postman里要用form-data格式,字段名必须和View里request.FILES.get('image')的image一致。这里最容易犯低级错误,字段名写成img、file,返回一直是“缺少图片”。

启动时如果发现预测输出不对,建议先单独写一个测试脚本,直接加载模型跑一张本地图片,看输出形态是否正常。因为Django环境下报错堆栈有时候会混合模板和模型层的错误,拆开排查会更快。

5.2 常见报错与解决方案

我在开发这套系统时收集了几个出现频率极高的报错,整理成速查表,供你用:

报错现象常见原因解决办法
FileNotFoundError: Model not found模型路径写错或者没放到目录检查model_loader.py里的路径,建议用BASE_DIR拼接绝对路径
TypeError: cannot serialize session模型权重未在启动时加载,重复加载用apps.py里ready()方式确保只加载一次
OperationalError: no such table忘记执行migrate先python manage.py makemigrations再migrate
bad operand type for unary +: 'str'在前端模板拼接字符串时出错检查模板变量类型,必要时用stringformat过滤器
AttributeError: 'UploadedFile' object has no attribute 'read'把UploadedFile当成普通文件读使用upload_file.read()或者upload_file.open()后读
CPU推理速度慢模型过大或未做归一化换MobileNetV2,图片resize到224x224,确保推理时batch_size=1

除了表格里的,还有一个奇葩问题:Django开发服务器默认是threaded的,如果模型推理函数内部用了tf.function,第一次调用可能触发图构建,导致第一个请求特别慢,甚至卡住。我把推理函数用tf.function装饰后,第一次请求从原来的3秒降到了1秒多,但图构建仍然有延迟。解决办法是在启动时“预热”一次模型,也就是在apps.py里加载模型后,用一张纯黑色图片先跑一次推理。这样真实用户访问时就不用经历图构建的冷启动。

5.3 性能优化与扩展思路

这个系统跑通后,如果要上线服务更多人,有几个优化方向非常明确。

第一,模型优化。如果觉得MobileNetV2还不够快,可以转成TensorFlow Lite或者ONNX格式,进一步压缩体积和推理延迟。我试过把模型转成TFLite后,推理时间在CPU上几乎砍半,但需要重新验证准确率是否下降。

第二,并发请求。Django内置的开发服务器不适合多用户高并发,部署时用Gunicorn做WSGI服务器,前面再挂Nginx处理静态文件和反向代理。静态文件如上传的图片、CSS、JS,都交给Nginx,Django只处理动态请求。

第三,数据库。SQLite在并发写入高时会锁库,正式部署最好迁到PostgreSQL。Django的ORM代码基本不用改,配置文件切换数据库连接即可。

第四,前端体验。当前是上传后等结果,体验尚可。以后可以改成前端先用Canvas压缩图片再上传,减少传输时间;甚至在手机端接入摄像头直拍,更加贴近真实使用。整个系统的延展性很好,因为我当初把模型加载、预处理器、业务View分层了,换模型、换数据库都不需要伤筋动骨。

提示:以上扩展中提到的具体配置方法与代码实现,建议查阅Django官方部署文档和对应服务的使用说明,按自己的服务器环境灵活调整。

6. 实操中的核心体会与最后提醒

做这个项目给我最大的体会是:深度学习应用放到Web后端里,难点往往不是模型精度,而是如何管理模型的生命周期。刚开始我傻傻地在每个请求里加载模型,结果接口耗时惨不忍睹,后来改成启动时加载,又遇到并发下模型被多个线程调用的问题,折腾了好一阵。把这些底层细节处理好,系统才谈得上稳定可用。

还有一点想强调的是数据重要性。模型训练阶段我花了大量时间清洗图片,最终效果和网上直接下载的数据集有天壤之别。如果只是想在项目里“跑通”,用现成模型权重也行,但要做认真展示,还是应该自己收集一批实地图片,哪怕是手机拍的,都能让模型更接地气。

如果你准备复刻这个项目,建议先别急着写代码,花半天把分类映射表设计好:里有哪些物品细类、对应的投放建议是什么。这块看起来不起眼,但直接决定用户体验。系统运行后,再根据后台数据持续补充新物品类别,模型也可以定期重训,整个系统就会越用越聪明。垃圾分类这件事,光靠罚或者号召是不够的,工具能给一部分人提供“即时答疑”,已经很有价值。

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

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

立即咨询