ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

基于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(weightsimagenet, include_topFalse, 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( rescale1./255, rotation_range40, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue, validation_split0.2 ) train_generator train_datagen.flow_from_directory( data/train, target_size(224, 224), batch_size32, class_modecategorical, subsettraining )注意rescale1./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, axis0) / 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( userrequest.user if request.user.is_authenticated else None, imageimg, result_labellabel, confidenceconfidence ) 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( totalCount(id) ).order_by(-total)这类统计其实很适合加缓存。我当时分了一层简单的缓存统计5分钟过期用django.core.cache里的cache.set和cache.get包了一层。平时不用太重的缓存方案项目规模没到Redis非上不可的程度本地内存缓存完全够用。数据库索引也是容易忽视的点。result_label和create_time这两个字段是高频查询条件我给它们都加了db_indexTrue。加索引是个动态过程一开始表里几百条数据感觉不到差别等数据量大了才发现加不加索引的查询耗时能差好几倍。5. 前端交互与页面流程实现5.1 上传页面的交互设计前端这块我做得比较克制没有引入任何前端框架三张主要页面是首页、识别页和历史记录页。识别页的交互是核心场景用户选择图片后先预览然后点击识别按钮页面通过AJAX方式调用后端接口等待结果返回。预览功能用JavaScript的FileReader实现关键在读取图片前判断文件类型只允许jpg、jpeg和png三种格式。识别按钮在请求发出后要立即禁用并且显示加载状态否则用户以为没点上连续点好几次结果后端跑了好几个推理任务服务器CPU占满后谁都识别不了了。这是实战中非常典型的一个体验细节。AJAX请求要注意带上CSRF TokenDjango的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项目时把图像预处理函数单独抽出来训练时用一份代码预测时用同一份代码避免两边各写一套逻辑导致预处理方式不一致。这个小习惯救了我不少次希望你用的时候也能体会到它的好处。
返回列表