
选毕设题目那段日子我基本把网页上那些推荐题目翻了个遍最后定了基于Django的农业害虫识别系统。原因很直接这个题目同时压中了Web开发和深度学习两块内容既有工程量又有算法亮点不会让答辩变成简单的系统演示。真正动手以后才发现从数据集整理、模型训练、Django工程集成到后期把源码和文档整理成一套能交付的东西每一环都有不少值得记录的细节。这篇文章就把这套农业害虫识别系统的设计与实现完整复盘一遍从技术选型到模型训练从核心模块编码到远程调试和部署验收把我踩过的坑和总结的经验都写出来。准备做类似毕设、或者想复现一个图片识别加Web管理系统的开发者可以直接把这篇当参考。1. 为什么选农业害虫识别当毕设需求与选题逻辑1.1 农业植保场景里的真实需求农田害虫防治讲究治早治小虫子刚起来那几天是防控窗口期。但实际情况是基层农户识别害虫主要靠肉眼和经验碰到不常见的虫子很容易认错把棉铃虫当玉米螟、把蚜虫危害当病害最后打错药、误了时机。植保技术人员数量有限也不可能天天跑田里。所以拍照识虫这个需求是真实存在的不是拍脑袋想的。图像识别技术这几年已经很成熟把手机拍一张害虫照片传到服务器跑一遍深度学习模型返回害虫种类和防治建议这条链路在技术上有成熟路径在业务上有落地价值。做这种题目答辩的时候你有东西可讲需求来自实际场景不是凭空造一个增删改查系统。1.2 作为毕业设计这个题目的难度评估我选题目时主要评估了三点。第一复杂度适中。如果一个题目里只有Django写出来就是一个普通的管理系统算法上没什么亮点如果一个题目只有模型训练又不像一个完整的软件工程。农业害虫识别系统把两者结合了Django负责业务逻辑、数据管理、页面展示深度学习模型负责核心识别能力。两边都有但每一块的深度都可以自己控制。第二知识覆盖面够全。这个系统完整走下来会涉及数据库设计ORM模型、后端视图编写、模板渲染、文件上传处理、深度学习模型训练与推理、环境部署和远程调试。一套流程做完等于把大学里Web开发和机器学习两门课的知识串起来了老师问你什么你都能展开说。第三演示效果好。答辩现场最怕的就是假大空的项目页面点来点去只有表格。这个系统不一样你现场上传一张稻飞虱的照片几秒钟返回识别结果和防治建议效果直观老师一眼就能看懂系统在做什么。1.3 我最后划定的功能范围为了防止需求发散、最后做不完我把功能边界划得很清楚害虫识别上传图片返回害虫类别、置信度、形态特征和防治建议害虫百科按类别维护每种害虫的详细信息供用户检索学习识别记录保存每次识别历史用户可以复查管理员可以在后台看到使用情况后台管理用Django自带的Admin维护害虫数据、管理用户这个范围下系统核心链路就是图片上传到模型推理再到结果返回围绕这条链路把Django的请求处理、文件上传、数据库写入、模板渲染这些关键点全部串起来了。2. 系统整体架构与关键技术选型Django为什么够用2.1 系统的整体架构拆解整个系统是典型的B/S结构浏览器作为客户端Django作为服务端。浏览器端用Bootstrap模板渲染页面用户上传害虫图片后Django的视图函数接收图片文件做格式校验然后把图片保存到服务器的media目录接着视图层调用独立的识别模块识别模块负责加载深度学习模型、对图片做预处理、执行推理返回结构化的识别结果视图拿到结果后一方面传给模板渲染展示给用户另一方面写入数据库保存识别记录。我特意把识别模块从视图函数里拆出来单独放到一个prediction目录下。这样做的好处是视图层代码只关心HTTP请求和响应具体的图像预处理、模型推理逻辑都封闭在独立模块里以后换模型、换框架、加缓存都不会动到业务代码。2.2 为什么是Django而不是Flask或者SpringBoot很多同学做这种题目会纠结框架我的经验是你先把技术栈定下来再想其他。这里给一个我当时的对比思路对比维度DjangoFlaskSpringBoot开发效率高ORM、Admin、认证都是内置的高但很多功能要自己集成中配置多上手慢深度学习生态Python生态TensorFlow/PyTorch直接调用同样可以但工程结构要靠自己搭Java生态访问Python模型要额外做服务答辩讲解MVT结构清晰好讲自由度高但代码组织容易散适合Java方向但如果模型是Python要绕一层学习成本中文档和中文资料极多低高我最终选Django核心原因有三个。第一项目需要的用户认证、后台管理、ORM、表单处理Django全都有不需要从零搭建毕设周期短时间要花在刀刃上。第二深度学习模型的训练和推理都在Python这边Django用Python写两者在同一个进程里直接互相调用省去跨语言通信的麻烦。第三MVT模式规整模型、视图、模板分开答辩时按着这个结构讲逻辑非常通顺。2.3 数据库设计三张核心表就够了数据库我用Django自带的关系型数据库SQLite起步方便开发和打包演示如果要上线改配置切到MySQL也很容易Django的ORM把这层屏蔽掉了。核心数据表设计如下表名主要字段用途user用户名、密码、邮箱、创建时间用户认证pest_info名称、学名、形态特征、危害作物、防治方法、图片害虫百科数据recognition_record用户、上传图片路径、识别结果、置信度、识别时间识别历史记录从这张表能看出来这个系统的数据结构并不复杂但每一张表都有实际用途。pest_info是百科数据recognition_record记录用户的每一次识别行为这两个表直接支撑了系统的主要业务。用户表我直接用了Django的AbstractUser做扩展加了一个手机号字段方便后面做用户电话绑定。2.4 工程目录与代码分层一个典型的Django工程我建议把代码分层清楚不要全堆在views.py里。我的工程结构大概是这样pest_project/ ├── manage.py ├── pest_app/ # 核心业务代码 │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── admin.py │ └── ... ├── prediction/ # 识别模块独立封装 │ ├── __init__.py │ ├── model.py # 模型加载和预测 │ └── preprocess.py # 图片预处理 ├── media/ # 上传的害虫图片 ├── static/ # CSS、JS、静态资源 └── requirements.txt # 依赖清单分层清楚带来的直接好处是远程调试的时候定位问题非常快前端报错找模板和静态文件后端报错找views和prediction一眼就能锁定范围。3. 害虫识别模型从数据集到训练再到接入Django3.1 数据集怎么选公开数据集加筛选训练害虫识别模型第一个绕不开的问题就是数据。市面上有公开的农业害虫数据集比如IP102里面包含一百多种害虫图片数量也有几万张。但直接拿来训练会有两个问题一是类别太多很多类别之间形态差异很小识别难度大二是原始标注噪声比较多直接用会影响模型效果。我的做法是筛选出十种具有代表性的常见农田害虫保证每种至少500到800张图片覆盖不同生长阶段、不同拍摄角度、不同光照条件。类别不用多十类左右对毕设来说完全够演示效果也稳定。数据准备好之后按目录划分每个类别一个文件夹这是最简单的标注方式对后续加载和训练都很友好。如果自己拍图或者从搜索引擎收集注意版权问题尽量用公开数据集或自己拍摄。数据质量比数据量更重要模糊的、重复的、标注错误的图片宁可删掉留着只会拉低准确率。3.2 模型选型ResNet50还是MobileNet模型选择上很多教程一上来就让你从零训练一个卷积神经网络我强烈不建议这么做。设备不够、数据量不够、训练时间不够最后准确率上不去整个项目就废了。正确做法是迁移学习用在大规模数据集上预训练好的模型作为特征提取器只需要微调最后的全连接层。我最终对比了两个主流模型模型优点缺点适用场景ResNet50准确率高结构经典答辩好讲模型文件大约90MB推理稍慢对性能要求不高的Web服务毕设首选MobileNetV3模型小约10MB推理快准确率略低部署到低配服务器或移动端坦白说在几百到一千张图的小数据集上ResNet50和MobileNet的差距没有想象中大关键还是数据质量和训练策略。我最后选了ResNet50理由是答辩时可以讲残差网络的结构、为什么能解决深层网络退化问题这些都是老师爱听的加分内容。3.3 训练细节一组能复现的参数配置训练脚本我整理成了独立的Python文件数据加载用ImageFolder方式直接从目录读取。代码核心部分大概是这样的import torch import torch.nn as nn from torchvision import datasets, transforms, models from torch.utils.data import DataLoader # 数据增强随机旋转、翻转、亮度调整 transform_train transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 加载预训练ResNet50替换最后一层 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) num_features model.fc.in_features model.fc nn.Linear(num_features, 10) # 10类害虫 # 冻结除最后一层外的参数先训练fc层再逐步解冻 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3)这里有一个很重要的训练策略先用较小的学习率只训练新加的全连接层等loss下降后再解冻部分卷积层用更小的学习率微调。如果一开始就把所有层都放开训练小数据集很容易过拟合训练集准确率99%验证集只有70%。我当时用7:2:1划分训练集、验证集和测试集输入图片缩放到224x224batch size设32先训练20个epoch再解冻后几层微调10个epoch最终测试集Top-1准确率在92%左右。这个结果用于演示已经相当能打了。3.4 模型接入Django时最容易踩的三个坑模型训练完成后把模型文件放进Django工程里才是真正开始踩坑的地方。第一个坑是图像预处理不一致。训练的时候图片做了resize、归一化推理的时候也必须完全一样地做一遍。如果训练时用的是Resize((224, 224))推理时却用了别的尺寸模型很可能给出错误的预测结果。这个错误非常隐蔽因为程序不会报错只是结果不准。我的解决办法是把预处理步骤单独封装成函数训练和推理共用一份代码。第二个坑是模型加载慢。ResNet50模型文件90多MB如果每次用户请求都重新加载一次首次请求可能要等好几秒。我的做法是在模型加载函数上加了一个单例缓存只在第一次请求时加载模型后面的请求复用同一个模型实例。_model None def get_model(): global _model if _model is None: _model load_model(pest_model.pth) return _model第三个坑是类别映射顺序混乱。训练时类别是按文件夹名排序的推理时如果搞不清楚每个索引对应哪个类别返回的结果就会张冠李戴。我训练完就保存了一份类别索引和中文名称的映射文件推理的时候直接加载避免重新拼。4. Django核心模块实现图片上传、识别、展示一条龙4.1 图片上传别只写功能一定要做校验图片上传是识别链路的第一步也是很多初学者翻车的地方。我的视图函数里接收request.FILES拿到上传的文件之后先做两件事校验文件扩展名只允许jpg、jpeg、png限制文件大小超过5MB直接拒绝防止大文件拖垮服务器。import os from django.conf import settings from django.shortcuts import render from django.utils import timezone from .models import RecognitionRecord from prediction import predict_image def upload_and_recognize(request): if request.method POST: image_file request.FILES.get(image) if not image_file: return render(request, pest/error.html, {error: 请选择图片}) ext os.path.splitext(image_file.name)[1].lower() if ext not in [.jpg, .jpeg, .png]: return render(request, pest/error.html, {error: 仅支持jpg和png格式}) if image_file.size 5 * 1024 * 1024: return render(request, pest/error.html, {error: 图片大小不能超过5MB}) # 保存图片 image_path os.path.join(settings.MEDIA_ROOT, uploads, f{timezone.now().strftime(%Y%m%d%H%M%S)}_{image_file.name}) os.makedirs(os.path.dirname(image_path), exist_okTrue) with open(image_path, wb) as f: for chunk in image_file.chunks(): f.write(chunk) # 调用识别模块 result predict_image(image_path) ...这里我特别想说一句文件名的生成一定要用时间戳加随机串不要直接用用户上传的原始文件名。原因有两点一是避免文件名中有特殊字符导致路径问题二是避免不同用户上传同名文件时互相覆盖。4.2 核心视图函数串联整个识别逻辑识别模块的predict_image函数封装了模型加载、预处理、推理和结果映射的全部逻辑。视图层拿到返回结果之后把结果连同图片路径一起存入RecognitionRecord表最后将数据传入模板。# prediction/predict_image.py import torch from PIL import Image from .model import get_model from .preprocess import preprocess_image def predict_image(image_path): model get_model() tensor preprocess_image(image_path) # 与训练时保持一致 model.eval() with torch.no_grad(): output model(tensor.unsqueeze(0)) probabilities torch.softmax(output, dim1) confidence, index torch.max(probabilities, dim1) return { class_name: class_names[index.item()], confidence: round(confidence.item(), 4), suggestion: pest_info[class_names[index.item()]] }视图函数接收这个结果后查询PestInfo表补充形态特征和防治方法然后渲染页面。整个链路里prediction模块只负责识别Django视图只负责业务流程互不干扰这个设计后面做远程调试时帮了我大忙哪一段出问题直接看哪一段。4.3 页面展示结果卡片加历史记录页面展示我用的Bootstrap栅格布局。识别结果页展示四样东西上传的图片、识别出的害虫名称、置信度百分比、防治建议。置信度我保留两位小数显示低于60%的结果页面会提示识别置信度较低建议咨询当地植保站这个是考虑到模型永远不可能100%准确要引导用户理性看待结果。历史记录页面单独做了分页展示每页十条按照识别时间倒序排列。每条记录显示缩略图、害虫名称、置信度、识别时间点击可以跳转到详细结果页。这个模块虽然简单但体现了系统对用户数据的管理能力答辩时也算是一个加分点。4.4 Django Admin维护百科数据的利器PestInfo的增删改查我完全没有自己写页面直接注册进了Django Admin后台。from django.contrib import admin from .models import PestInfo, RecognitionRecord admin.register(PestInfo) class PestInfoAdmin(admin.ModelAdmin): list_display (name, scientific_name, crop_host, created_at) search_fields (name, scientific_name, crop_host) list_filter (crop_host,)Admin后台的好处是免费自带一套完整的管理界面我只需要在admin.py里配置列表展示字段和搜索字段。害虫百科的维护、防治方法的更新全部通过后台完成不用写一行前端代码。这个思路建议做类似系统时直接沿用用户功能自己做管理功能优先考虑Admin。5. 远程调试与部署交付环境、工具和容易翻车的地方5.1 远程调试到底怎么搞标题里带了远程调试这个词很多同学可能以为是什么特殊技能其实就是把开发环境搬到云端或者让另一个人连接到你的开发环境共同排查问题。我实践下来最顺手的方案是VS Code Remote-SSH本地装好VS Code安装Remote-SSH扩展在远程服务器上开一个SSH账号配上公钥免密登录VS Code左侧选择Remote Explorer连接到服务器的IP地址连接后直接打开服务器上的Django工程目录本地写代码服务器上运行这样做的最大好处是本地和远程环境完全一致不存在我本地能跑你服务器跑不了的问题。调试Django应用时我把DEBUG True打开在views.py里打上断点浏览器发一次请求代码就能停在断点上变量值、调用栈一目了然。如果对方没有服务器也有一个替代方案Django的报错页面本身就是最好的调试工具。只要DEBUG True程序出错时页面会显示完整的调用堆栈、异常信息、出错文件和行号直接把这些信息截图或复制给帮你远程定位的人基本上能解决80%的问题。5.2 环境一致性虚拟环境和依赖锁版本远程调试和交付过程中我遇到的最常见问题就是环境不一致。本机Python 3.10开发得好好的换到另外一台机器上装依赖就报错。解决这个问题没有捷径就是老老实实用虚拟环境加依赖清单。python -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt里面必须把关键依赖的版本号锁死尤其是Django、torch、pillow、numpy这几个。我实际遇到过一个问题pillow版本过高导致图片处理时报错降级一个版本就好了。像这类跟深度学习框架强相关的依赖版本差一点就容易出问题所以锁版本是必须的。说到模型文件还有个细节要注意ResNet50模型文件接近100MB放Git仓库既不合适也容易超限。交付时我把模型文件单独放在一个目录并在README里写明下载链接和解压放置的路径这样既保证了工程代码的干净也不影响别人复现。5.3 静态文件和媒体文件配置404重灾区Django项目里静态文件和上传图片404是新人踩坑最多的点。在调试环境下的配置是这样的# settings.py MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media) STATIC_URL /static/ STATICFILES_DIRS [os.path.join(BASE_DIR, static)]然后要在项目的根urls.py里把媒体文件的路由补上from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ...你的路由 ] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)不加这一行用户上传的图片在页面上永远显示不出来只有DEBUG模式能访问。网上很多帖子提过这个问题我这次也在这里卡了半小时查来查去最后发现只是忘了加媒体路由。5.4 局域网演示与公网部署毕设答辩前经常要在现场演示实验室的网络环境和笔记本可能隔着一堵墙。如果只在本机跑python manage.py runserver别人的浏览器根本访问不到。这时候要这样启动服务python manage.py runserver 0.0.0.0:8000同时记得去settings.py里改ALLOWED_HOSTS [*]否则访问时会出现DisallowedHost错误。这样的话同一局域网内的设备通过你的局域IP加8000端口就能访问系统了。手机现场演示拍一张虫子图片直接页面上传效果非常炸场。如果要在云服务器上长期运行我建议还是用ginicorn加nginx的组合比直接用runserver稳。Django本身不擅长处理高并发静态文件生产环境下让nginx挡在前面静态文件直接交给nginx服务。不过对毕设来说runserver方式演示已经够用服务器部署只是一个加分项。5.5 交付源码时的目录和文档规范既然涉及源码交付我多说一句目录规范。交付包里的README至少要写清几件事项目简介和功能列表、环境要求Python版本、依赖安装命令、数据库初始化步骤、模型文件的获取方式、启动命令、默认管理员账号。文档不要写流水账要让一个从没运行过项目的人按步骤10分钟内跑起来。我这次整理交付内容时把requirements.txt、模型说明、部署笔记、答辩演示脚本都分类放好后面重新复现这个项目时省了非常多时间。6. 这套系统还能怎么升级从毕设到真正可用的产品6.1 提升识别精度的两个方向92%的准确率做演示够了但要往深了做可以从两个方向改进。第一是细粒度识别很多害虫不同发育阶段的形态差别很大比如幼虫和成虫可以训练一个先判断虫态再识别种类的两级模型。第二是多模型融合对预测置信度不高的样本同时跑MobileNet和ResNet取投票结果能有效降低误判率。6.2 加入用户反馈闭环一个好用的识别系统应该有用户反馈机制。用户对识别结果不满意时可以点识别错误按钮选择正确的类别这些反馈数据会进入数据库定期汇成新的训练集让模型越用越准。这个功能技术上不复杂但把系统从单向识别变成了有数据闭环的产品答辩讲到这里基本就是高分点。6.3 从Web端扩展到视频与移动端当前系统是单张图片识别如果想扩展可以做视频帧检测用OpenCV从视频里抽帧对每一帧执行识别遇到置信度高于阈值的结果就截帧保存并告警。这是植物保护站做虫情监测的常见模式。移动端方面Django可以写一套DRF接口把识别逻辑做成API小程序和App直接调用后端代码几乎不用动。6.4 结合地域与天气数据做精准防治建议害虫的发生和地域、季节、天气关系极大。如果系统能拿到用户的地理位置和当前天气数据比如温湿度、降雨量就可以在防治建议里加上当前温度适合该害虫繁殖建议尽快处理这类针对性提示。这个功能会让系统从能识别升级成能指导实用价值完全不一样。我个人做完这套农业害虫识别系统最大的体会是毕设不是把代码写完就完了把工程分层做好、把环境整理干净、把文档写清楚这些看不见的工作反而决定了项目能不能顺利交付和演示。远程调试时省下的时间、答辩现场演示的流畅度都来自这些前期准备。如果你正在做类似的题目建议不要在模型训练上死磕刷分把Django的集成、部署和文档做好整个项目会稳得多。