news 2026/10/6 9:03:17

基于Django的图像识别垃圾分类系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django的图像识别垃圾分类系统设计与实现

最近把垃圾分类这个老话题搬进了Web项目里,做了一个基于Django的图像识别垃圾分类系统。从训练模型到后端集成,再到上线部署,前前后后踩了不少坑。这篇文章就把整套设计与实现过程拆开讲讲,从模型选型到数据库设计,从识别接口到常见报错,尽量把关键决策背后的原因说清楚。如果你正准备做毕业设计、课程设计,或者刚学完Django想找个练手项目,这套内容应该能帮你少走很多弯路。

1. 项目背景与核心设计思路

1.1 为什么选Django加图像识别这套组合

垃圾分类本身是个偏传统的业务场景,但加上图像识别之后就变成了典型的人工智能应用落地。选技术栈的时候我认真对比过几个方向:Flask轻量但生态不够全,Node.js做后端也可以但Python的AI生态优势浪费了。最终锁定Django,理由很实际:自带ORM、Admin后台、用户认证,开发速度比Flask快一截,而且MVT结构天然适合把模型推理、业务逻辑、前端页面分层管理。

图像识别部分选了TensorFlow/Keras加载预训练的卷积神经网络。之所以不从头训练,是因为垃圾分类数据集规模普遍不大,从头训一个ResNet级别的网络,几万张图片跑一天也不一定收敛,而迁移学习直接用ImageNet上预训练好的权重,只微调最后的全连接层,训练时间能缩短一个量级,精度还很能打。这也是现在做图像分类项目最主流的做法。

这个系统能解决的核心问题很简单:用户上传一张垃圾图片,系统自动识别出它属于哪一类(比如可回收、厨余、有害、其他),同时给出具体的垃圾名称和投放提示。后续还加了历史记录、统计报表和用户反馈功能,保证整个系统不只是“识别一下”那么单薄。

1.2 系统功能模块与整体架构

我设计的系统分四个大模块:

用户模块:注册、登录、个人中心,其实Django自带的auth就能覆盖七八成需求,没必要重复造轮子。

图像识别模块:这是核心。用户通过网页上传图片,后端接收后先做预处理(尺寸统一、归一化),再交给训练好的模型推理,返回识别结果和置信度。

数据记录模块:识别记录存MySQL,包括图片路径、识别类别、置信度、识别时间、用户ID。后面做统计报表都靠这些数据。

反馈模块:用户如果觉得识别得不对,可以手动纠正,这些纠偏数据积累起来以后就是增量训练的数据集。

架构上采用前后端不分离的方式,Django直接渲染模板,识别接口用AJAX异步调用。为什么不拆前后端?因为这个场景页面交互不复杂,模板渲染加少量JavaScript完全够用,拆成Vue/React反而增加部署成本和开发负担。记住一个原则:技术选型不是越新越好,是越适合越好。

2. 图像识别核心:模型选型与训练实战

2.1 模型选型对比:DenseNet121与MobileNetV3

当时在DenseNet121和MobileNetV3之间犹豫了一阵。MobileNetV3主打轻量化,适合部署到嵌入式设备,但精度相比DenseNet稍微逊色。考虑到这个系统运行在云服务器上,不太需要纠结模型体积,最终选了DenseNet121作为骨干网络。

DenseNet的特点在于每一层都跟前面所有层有连接,梯度流更顺畅,参数效率高。用Keras加载预训练权重非常方便:

from tensorflow.keras.applications import DenseNet121 base_model = DenseNet121(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) base_model.trainable = False

这里有个关键点:include_top设为False,去掉原来的全连接层。因为ImageNet的1000类跟垃圾分类的类别完全不对应,必须自己加上新的分类头。trainable设为False的意思是冻结基础网络的权重,只训练后面的全连接层,这样计算量大大降低,而且能有效防止在小数据集上过拟合。

2.2 数据集准备与数据增强

数据集是这类项目最花时间的部分。我参考了开源的垃圾分类数据集,又自己去拍摄和爬取了一部分,把类别统一成四分类:可回收垃圾、厨余垃圾、有害垃圾、其他垃圾,每类大约2000张图片。

数据量仍然不够,所以做了数据增强。Keras里用ImageDataGenerator随机旋转、平移、缩放、水平翻转,相当于让一张图片变成好几张不同形态的样本:

from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=40, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, validation_split=0.2 ) train_generator = train_datagen.flow_from_directory( 'data/train', target_size=(224, 224), batch_size=32, class_mode='categorical', subset='training' )

注意rescale=1./255这行,所有像素值归一化到0-1之间,这个操作能大幅加速收敛。不管用的是VGG还是ResNet还是DenseNet,预处理步骤都是必需的。

2.3 训练参数设计与评估

训练过程分为两个阶段。第一阶段只训练新加的全连接层,优化器用Adam,学习率设1e-3;第二阶段解冻基础网络的后几层,学习率降到1e-5,做一遍微调。这种两阶段训练比直接端到端训练效果好很多,因为先让分类头适应任务,再去微调底层特征,不容易把预训练权重破坏掉。

最终模型的验证准确率在93%左右,单张图片推理时间在150毫秒左右。识别结果保存成HDF5格式的model文件,后面要加载到Django项目里用。评估的时候除了准确率,还重点看了每个类别的召回率和精准率,特别是“其他垃圾”这个类,因为它包含各种杂七杂八的东西,最容易混淆。遇到类似的分类问题,一定要多看混淆矩阵,别只盯着Accuracy一个数字。

3. Django后端与识别流程集成

3.1 创建Django项目与应用结构

项目结构是我顺手整理出来的,每个部分职责单一:

garbage_project/ ├── manage.py ├── garbage_project/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── classification/ │ ├── views.py │ ├── urls.py │ ├── models.py │ ├── utils/ │ │ └── recognizer.py │ └── tensorflow_models/ ├── user/ │ ├── views.py │ └── models.py └── templates/

用django-admin startproject garbage_project创建项目后,分别创建classification和user两个App。可能有人图省事把所有逻辑堆在一个App里,新手阶段我强烈建议别这么干。项目小的时候看不出来,等你要改识别逻辑或者加新功能时,拆开的优势立刻体现出来——只动对应App,不会牵连其他地方。

3.2 模型加载与识别接口实现

模型加载这块有个常见的坑:如果每次请求都去加载模型,服务器会直接被拖垮。因为加载HDF5模型就要好几秒,更别说还有推理计算。正确做法是把模型加载做成单例,模块加载时初始化一次,后续请求直接复用。

我把这部分封装成独立的工具模块recognizer.py:

# classification/utils/recognizer.py import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import img_to_array, load_img MODEL_PATH = 'classification/tensorflow_models/garbage_model.h5' CLASS_LABELS = ['可回收', '厨余', '有害', '其他'] _recognizer = None def get_recognizer(): global _recognizer if _recognizer is None: _recognizer = load_model(MODEL_PATH) print("模型加载完成") return _recognizer def predict_garbage(image): model = get_recognizer() img = load_img(image, target_size=(224, 224)) img_array = img_to_array(img) img_array = np.expand_dims(img_array, axis=0) / 255.0 pred = model.predict(img_array) class_idx = np.argmax(pred[0]) confidence = float(np.max(pred[0])) return CLASS_LABELS[class_idx], confidence

注意predict之前那张图片要跟训练时的预处理保持一致——同样的尺寸、同样的归一化方式。最容易犯的错误就是在训练时做了rescale而预测时忘了,或者反过来,导致结果差得离谱。

视图层用函数视图加AJAX处理上传请求。考虑到前端需要无刷新获取结果,我封装了一个简单的识别接口:

# classification/views.py import json from django.http import JsonResponse from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_POST from .utils.recognizer import predict_garbage from .models import RecognitionRecord @require_POST @ensure_csrf_cookie def recognize_api(request): img = request.FILES.get('image') if not img: return JsonResponse({'code': 1, 'msg': '未上传图片'}) if img.size > 5 * 1024 * 1024: return JsonResponse({'code': 1, 'msg': '图片不能超过5MB'}) try: label, confidence = predict_garbage(img) except Exception as e: return JsonResponse({'code': 2, 'msg': str(e)}) # 保存记录 record = RecognitionRecord.objects.create( user=request.user if request.user.is_authenticated else None, image=img, result_label=label, confidence=confidence ) return JsonResponse({'code': 0, 'data': {'label': label, 'confidence': confidence, 'record_id': record.id}})

这个接口同时兼顾了校验、识别、记录落库三个环节。图片上传前限制大小是很有必要的,不然用户传一张几十MB的RAW格式图片,迟迟不响应不说,还浪费服务器资源。

4. 数据库设计与ORM优化技巧

4.1 核心表结构设计

项目里需要三张核心表,他们都继承Django自带Models.Model。设计时我遵循一个原则:尽可能复用Django内置的User模型,而不是重新建一张用户表,这样认证逻辑零成本获得。

用户反馈表比较特殊,它有一个标签字段用于记录用户的纠偏操作,这就像给模型准备“人工标注”,以后要迭代模型的话,这张表的数据就是增量训练数据集的核心来源。在写ORM查询的时候,新人容易踩的一个坑是删除对象。用Model.objects.filter(条件).delete()就能批量删除,但注意它返回的是一个元组,第一个值是删除的总条数。如果要判断某条记录是否存在,推荐用get_object_or_404,而不是自己try/except DoesNotExist,代码会干净很多。

三张表分别是:

图片识别记录表,字段包括id、关联用户、图片文件、识别结果、置信度、识别时间。用户ID允许为空,因为游客也可以试用识别功能。这里用了Django的ForeignKey和CASCADE级联删除,用户注销后历史记录自动清理,避免残留垃圾数据。

用户反馈表的设计更细致一些,包含反馈类型字段用来区分是在纠偏提示还是修改分类错误,还存了用户选的正确标签。这个表跟识别记录表是多对一关系,一条识别记录可以被多次反馈。

4.2 ORM查询优化与倾向缓存

分类统计页面要做按类别分组的计数,如果直接Python层循环统计,数据量小的时候无所谓,但记录过万后就会发现接口越来越慢。用ORM自带的annotate聚合查询是更优雅的方案:

from django.db.models import Count from .models import RecognitionRecord summary = RecognitionRecord.objects.values('result_label').annotate( total=Count('id') ).order_by('-total')

这类统计其实很适合加缓存。我当时分了一层简单的缓存:统计5分钟过期,用django.core.cache里的cache.set和cache.get包了一层。平时不用太重的缓存方案,项目规模没到Redis非上不可的程度,本地内存缓存完全够用。

数据库索引也是容易忽视的点。result_label和create_time这两个字段是高频查询条件,我给它们都加了db_index=True。加索引是个动态过程,一开始表里几百条数据感觉不到差别,等数据量大了才发现加不加索引的查询耗时能差好几倍。

5. 前端交互与页面流程实现

5.1 上传页面的交互设计

前端这块我做得比较克制,没有引入任何前端框架,三张主要页面是首页、识别页和历史记录页。识别页的交互是核心场景:用户选择图片后先预览,然后点击识别按钮,页面通过AJAX方式调用后端接口,等待结果返回。

预览功能用JavaScript的FileReader实现,关键在读取图片前判断文件类型,只允许jpg、jpeg和png三种格式。识别按钮在请求发出后要立即禁用,并且显示加载状态,否则用户以为没点上,连续点好几次,结果后端跑了好几个推理任务,服务器CPU占满后谁都识别不了了。这是实战中非常典型的一个体验细节。

AJAX请求要注意带上CSRF Token,Django的CSRF防护默认是开启的,不加Token的话请求会被403拒绝。我用的是一个比较标准的方式,在模板里获取csrf_token,然后在JavaScript里放到请求头:

fetch('/classify', { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken') }, body: formData })

5.2 识别结果展示与置信度处理

识别结果返回后,页面展示四个信息:垃圾类别、具体置信度、投放建议、对应垃圾桶图标。置信度这个参数很关键,但展示时必须处理一下,尤其是置信度低的预测。如果模型预测某项概率只有60%,直接硬显示“这属于可回收垃圾”很容易误导用户。

做法是设置一个置信度阈值,低于0.7时提示用户“不太确定,请确认是否准确”,同时给出其他候选类别的名称。这套逻辑虽然简单,但对提升用户信任度帮助很大。模型不一定每张图都能确认,系统应该学会说“不知道”,而不是硬着头皮给答案。这条经验用在任何分类系统里都适用——模型不是每问必答,要允许它表达不确定。

6. 部署上线与性能调优记录

6.1 服务器部署的关键步骤

部署环境是阿里云一台2核4G的Ubuntu服务器,这套配置跑Django加图像识别模型基本够用。部署时没有用django自带runserver,因为runserver是单进程单线程的,性能和稳定性都不足以承载真实访问。我用了经典的uWSGI + Nginx组合方案。

Nginx负责静态文件、媒体文件的直接返回和反向代理动态请求,uWSGI负责跑Django应用。当时针对一个上传文件是图片的POST请求,试过两种方式:一种是把图片先存到服务器再传给模型,另一种是直接内存里处理。第一次实装时我采取的是先落盘再读取的流程,这样好处是留档明确,坏处是多一次磁盘IO。后来测试发现对于5MB以内的图片,落盘读取的延迟几乎感知不到,但要留意存储目录的权限和磁盘空间。

Nginx的client_max_body_size参数一定要设置,默认是1MB,上传稍微大点的图片就直接413错误。这个参数很多人会忘记调整,排查的时候容易一头雾水。

6.2 性能瓶颈分析与模型加载优化

监控下来性能瓶颈主要在两个地方。第一个是模型推理时的CPU占用,因为服务器没有GPU,推理依赖CPU,平均每次150毫秒其实还好但并发上来之后就会有排队。解决方法是把uWSGI的进程数从2调到4,让每个进程都加载一份模型,分别处理请求。

第二个是模型加载时间。每次启动服务进程时,模型加载需要5-8秒,期间如果来了请求就直接报错。我的处理方案是在Django的ready方法里做热加载,App启动后立即触发一次模型加载,宁可慢三秒启动也不让第一个用户撞上未加载的窗口。

7. 常见问题与排查方法速查

调试这套系统的时候,确实遇到好几个经典报错,整理出来给后面做类似项目的同学参考:

问题现象触发原因解决方案
上传图片提示403请求头缺少CSRF Token在JS请求头带X-CSRFToken
上传大图时413Nginx默认限制body大小1MB在Nginx配置里调大client_max_body_size
图片识别结果总是同一类修改了模型后没有重启uWSGI重启uWSGI让新模型重新加载
模型首次访问响应极慢模型首次加载耗时长应用启动阶段用ready钩子预加载模型
上传的图片路径访问404Django没配MEDIA_URL和MEDIA_ROOT配置媒体文件路由和Nginx的location规则
数据统计不显示新记录ORM查询结果被缓存住确认缓存策略,设置合理的过期时间或主动失效

还有一个新手会忽略的坑,就是图片路径中包含中文文件名时,可能是文件名里有中文导致存储后打不开。最好上传时统一改名,比如用UUID重命名存储文件,这样既避免中文路径问题,也让文件名不会泄露用户信息。个人建议在模型使用的图片路径处理中,文件保存到本地后立即把临时文件清理掉,避免大量临时垃圾堆积在/tmp目录。

在模型推理报错排查方面,最常见的报错是维度不匹配。这是因为训练时用的输入尺寸是224x224,而预测时传入的图片尺寸不对。一定要在代码里做强制缩放,并检查通道数是否为3。还有一次遇到预测结果全是同一个类别,排查后发现是模型文件路径错误,加载了一个没训练完的中间模型,所有权重还没收敛就保存了。所以训练时要明确区分中间模型和最终模型,保存文件时加上epoch和val_acc信息,比如model_epoch25_val95.h5这种命名习惯能省去很多混乱。

8. 增量更新与实际项目体会

这个项目做完之后,最大的收获并不是“学会训练一个模型”或者“会写Django接口”,而是真正理解了把深度学习模型嵌进Web项目的完整路径。模型训练只是冰山一角,数据准备、前后端联调、部署运维、性能排查,每一块都需要投入大量精力。

如果你现在刚学完Django基础,想上手做这个项目,我给几个直接的建议:

  • 别在一开始就追求识别准确率,先跑通全流程,模型哪怕只有70%的准确率,也比一个只做了页面的漂亮原型有意义。
  • 数据集可以从公开的垃圾分类数据集起步,但一定要亲自检查数据质量,有些公开数据集里同一类别图片差异极大,需要手动清洗。
  • 模型文件不要提交到Git仓库,HDF5文件动辄几百MB,放进代码库会让仓库体积爆炸,用对象存储或网盘单独管理权重文件。
  • 预留用户反馈功能,你这个系统如果真的有真实用户使用,反馈数据是后续模型迭代最宝贵的资源,很多系统的反馈功能就是摆设,实际上接口一定要打通、数据一定要入库。
  • 部署阶段多看一眼uWSGI日志,很多隐蔽问题的线索都藏在日志里,别全靠界面上的错误提示瞎猜。

最后再分享一个小的经验:在做图像识别相关Django项目时,把图像预处理函数单独抽出来,训练时用一份代码,预测时用同一份代码,避免两边各写一套逻辑导致预处理方式不一致。这个小习惯救了我不少次,希望你用的时候也能体会到它的好处。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 9:03:12

MySQL索引底层数据结构全解析:从B+树到联合索引与失效根因

很多后端开发把索引当成“面试题”来背,背完B树、哈希索引之后就觉得懂了,但真到了线上慢查询排查,或者被人追问一句“为什么MySQL不用红黑树做索引”,往往就露馅了。我在优化线上订单表的时候,被这个底层问题卡过整整…

作者头像 李华
网站建设 2026/10/6 9:02:30

Open WebUI 本地模型部署实战:从 Ollama 接入到知识库配置

简介:这是一份围绕Web用户界面(WebUI)构建的前端学习资源包,系统讲解超文本标记语言在页面结构中的基础作用,并结合层叠样式表与脚本语言展示完整的界面开发流程,适合刚开始接触网页制作的学习者、前端初学…

作者头像 李华
网站建设 2026/10/6 9:01:58

Python CI/CD实战:根治环境漂移,从依赖锁到自动部署

说实话,我见过太多Python项目,代码写得很漂亮,但只要换台机器跑就原形毕露——缺包、版本不兼容、编码错乱,最后只能甩出一句"在我电脑上跑得好好的啊"。这句话听多了你会发现,根子不在某个人身上&#xff0…

作者头像 李华
网站建设 2026/10/6 9:01:28

高能物理软件工具链入门:从事件生成到ROOT分析

一提高能物理相关软件,很多人第一反应是门槛高:粒子物理标准模型、费曼图、探测器响应,听起来像另一个次元的东西。但真正上手之后你会发现,这一整套软件生态的核心思路特别朴素——把“理论预言”变成“模拟数据”,再…

作者头像 李华
网站建设 2026/10/6 9:00:42

MFAC无模型自适应控制复现:CFDL、PFDL、FFDL动态线性化与Matlab实现

MFAC无模型自适应控制的复现项目,我以前第一次看到CFDL、PFDL、FFDL这三个缩写时,第一反应是又一套复杂的建模理论。但真正把Matlab代码跑起来,并且在三个非线性系统上分别验证完动态线性化效果后,我才意识到这套方法的妙处&#…

作者头像 李华
网站建设 2026/10/6 9:00:20

MySQL索引优化实战:从B+Tree原理到覆盖索引与慢查询排查

MySQL 索引优化这件事,很多搞后端和数据库的人最终都会走到这一步。一开始可能只是简单地“加了索引就变快了”,但真正到了线上问题排查、SQL 慢查询分析的时候才发现,索引远不是“建一个 BTree”这么简单。这篇内容我会结合自己这些年做数据…

作者头像 李华