资讯详情

资讯详情

ResNet50/VGG16/DenseNet农作物病害识别系统(含Flask界面与Grad-CAM可解释性)

简介本资源是一套完整的农作物病虫害智能识别检测系统毕设项目面向计算机、人工智能及农林信息化相关专业本科生解决农业场景中病害图像分类与目标检测的工程落地问题。资源包含56个文件涵盖9个Jupyter Notebook含ResNet50、VGG16/19、DenseNet121、Fastai、PyTorch、TensorFlow等多框架实现、2个Python主程序、2个Docker配置文件、部署指南AWS/GCP/本地Flask、模型权重pkl、静态资源CSS/JS/HTML及完整README与贡献规范包体88.27MB结构清晰、模块解耦便于学习迁移与二次开发。已有349人学习下载项目经导师指导并获98分高分评价所有代码均本地实测可运行附详细环境配置requirements.txt与多平台部署说明特别适合毕设选题、课程设计及深度学习实战训练者快速上手与深入理解CNN在农业视觉任务中的应用范式。1. 这不是又一个“猫狗分类”Demo一套跑通ResNet50/VGG16/DenseNet121三模型、带Flask Web界面、含真实田间拍摄病叶图像的农作物病虫害识别系统毕设级可交付你手头正赶着计算机/农林信息化/智慧农业方向的毕业设计导师说“得有模型、有数据、有界面、能跑通”但搜到的90%所谓“病虫害识别代码”要么是Kaggle上改个路径就打包卖的猫狗分类套壳要么是TensorFlow 1.x老古董无标注图报错堆栈没人修。而这份资源——它真在田埂边拍过37类病斑叶片用ResNet50在本地RTX 3060上训出94.2%验证准确率Flask服务启动后输入一张玉米锈病叶片照片3秒内返回带置信度的病名防治建议文本更关键的是它把PyTorch/TensorFlow/Keras/Fastai四套主流框架的训练脚本全摊开给你不是让你选一个而是让你看清为什么VGG16在小样本下容易过拟合、DenseNet121为何对早期褐斑更敏感、ResNet50微调时layer4要不要冻结——这些不是论文里的结论是作者在Plant_Disease_RESNET50.ipynb里用lr1e-4和lr5e-5各跑3轮后画出的loss曲线告诉你的。它专为毕设场景打磨requirements.txt里没一行冗余包Dockerfile适配Ubuntu 20.04Python 3.8连deployment_guide/aws_deployment.md都写了怎么用EC2 t2.medium部署而不超学生免费额度。如果你需要的不是一个玩具而是一份能答辩、能演示、能写进论文方法论章节的可复现、可解释、可扩展的农作物病虫害识别基线系统这就是目前能找到最接近“开箱即用”的那一份。2. 从数据集结构到模型选型为什么这37类病害图像必须按train/val/test三级划分且VGG16要配batch_size16而DenseNet121必须用322.1 数据集的真实构成与预处理逻辑不是ImageNet子集而是田间实拍专家标注的37类病害项目根目录下的images/文件夹并非简单堆放图片而是严格遵循深度学习工业实践的三级结构images/ ├── train/ │ ├── apple_scab/ # 苹果黑星病127张 │ ├── corn_rust/ # 玉米锈病98张 │ ├── tomato_blight/ # 番茄晚疫病142张 │ └── ... # 共37个子目录每类平均85~156张 ├── val/ │ ├── apple_scab/ # 验证集每类取训练集20%向下取整 │ └── ... └── test/ ├── apple_scab/ # 测试集独立采集的未参与训练的田间图每类30张 └── ...注意test/目录下的图像从未出现在任何训练或验证阶段且由农科院植保站提供原始拍摄记录EXIF中含GPS坐标与拍摄时间这是区别于普通公开数据集的核心价值——它模拟了真实部署时模型面对新地块、新光照条件的泛化能力。你在notebook/Plant_Disease_Detection_Keras.ipynb中会看到ImageDataGenerator的validation_split0.2仅作用于train/目录而test/目录通过flow_from_directory(directoryimages/test)单独加载确保评估不污染训练过程。2.2 四大模型脚本的差异化设计为什么Fastai版自动调参却不如手动调ResNet50稳定项目提供6个Jupyter Notebook覆盖主流框架但它们绝非简单复制粘贴Notebook文件名核心框架关键设计点适用场景Plant_Disease_RESNET50.ipynbPyTorch手动冻结layer1-layer3仅微调layer4fc学习率分段衰减1e-4→1e-5→1e-6毕设答辩需展示调参逻辑强调模型可解释性Plant_Disease_VGG16.ipynbKeras使用imagenet权重初始化batch_size16显存限制添加Dropout(0.5)防过拟合小样本100张/类场景突出基础模型鲁棒性Plant_Disease_DenseNet121.ipynbTensorFlow启用tf.data流水线prefetch(2)cache()提升IO效率特征图可视化显示早期卷积层对叶脉纹理的响应需分析模型关注区域支撑论文“注意力机制”章节Plant_Disease_Fastai.ipynbFastailearner.fine_tune(epochs15, base_lr3e-3)自动LR查找但cbs[ShowGraphCallback(), SaveModelCallback()]强制保存最佳epoch快速验证想法但需人工检查learn.recorder.plot_loss()确认收敛关键参数说明Plant_Disease_RESNET50.ipynb中torchvision.models.resnet50(pretrainedTrue)加载的是PyTorch官方torchvision0.13.1对应的ImageNet权重而非自训权重。model.fc nn.Sequential(nn.Dropout(0.3), nn.Linear(2048, 37))重置分类头其中0.3是作者在val集上交叉验证得出的最优Dropout率——低于0.2则过拟合高于0.4则欠拟合。这个数字不是玄学是notebook/Plant_Disease_RESNET50.ipynb第12 cell里for p in model.layer4.parameters(): p.requires_grad True解冻后用torch.optim.AdamW(model.parameters(), lr1e-4)训出来的。2.3 模型性能对比的硬指标不是准确率数字而是test/集上的混淆矩阵与F1-score所有Notebook最终都会生成results/目录下的.csv报告但真正决定毕设分数的是test/集结果。以ResNet50为例其test_results.csv包含class_nameprecisionrecallf1-scoresupportapple_scab0.960.920.9430corn_rust0.890.950.9230tomato_blight0.930.880.9030macro avg0.920.910.911110为什么看macro avg而非weighted avg37类病害样本量不均衡最少类仅67张最多类156张macro avg对每一类平等加权更能反映模型对罕见病害如grape_esca的识别能力——这正是农技推广中最关键的指标。你在app/models/plant_classifier.py里会发现预测函数predict_image()返回的不仅是class_name还有confidence和recommendation基于models/recommendations.json映射这才是毕设里“智能决策支持”的落地点。3. Flask Web服务部署实战从server.py启动到Nginx反向代理绕过端口冲突与静态资源4043.1app/server.py的轻量级架构为什么不用FastAPI而坚持Flask项目选择Flask而非更现代的FastAPI是出于毕设场景的务实考量依赖极简requirements.txt中flask2.2.5无额外异步依赖pip install -r requirements.txt在校园网环境下1分钟内完成调试友好if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)开启热重载修改view/templates/index.html后刷新即生效部署平滑gunicorn -w 2 -b 127.0.0.1:8000 app.server:app可直接替换app.run()无需重构路由逻辑。核心路由逻辑在app/view/routes.py中定义from flask import render_template, request, jsonify, send_from_directory import os from app.models.plant_classifier import predict_image app.route(/) def index(): return render_template(index.html) # 加载前端页面 app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: No file uploaded}), 400 file request.files[file] if file.filename : return jsonify({error: No selected file}), 400 # 保存上传文件到临时目录 upload_path os.path.join(app.config[UPLOAD_FOLDER], file.filename) file.save(upload_path) # 调用模型预测 result predict_image(upload_path) # 清理临时文件 os.remove(upload_path) return jsonify(result)参数说明app.config[UPLOAD_FOLDER]在app/__init__.py中定义为os.path.join(os.path.dirname(__file__), static, uploads)确保文件存入app/static/uploads/而非项目根目录避免Git误提交。predict_image()函数内部会自动将图像resize为224x224ResNet50输入尺寸并应用与训练时完全一致的transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(...)])这是保证线上推理与离线训练结果一致的关键。3.2 Docker部署全流程Dockerfile如何解决CUDA版本与PyTorch兼容性问题Dockerfile采用多阶段构建精准控制环境# 构建阶段使用nvidia/cuda:11.3-cudnn8-runtime-ubuntu20.04 FROM nvidia/cuda:11.3-cudnn8-runtime-ubuntu20.04 # 安装Python3.8及系统依赖 RUN apt-get update apt-get install -y \ python3.8 \ python3.8-venv \ python3.8-dev \ rm -rf /var/lib/apt/lists/* # 创建虚拟环境 RUN python3.8 -m venv /opt/venv ENV PATH/opt/venv/bin:$PATH # 复制requirements并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . /app WORKDIR /app # 暴露端口 EXPOSE 5000 # 启动命令 CMD [gunicorn, --bind, 0.0.0.0:5000, --workers, 2, app.server:app]关键避坑点nvidia/cuda:11.3-cudnn8-runtime-ubuntu20.04镜像与torch1.12.1cu113完全匹配见requirements.txt若强行升级PyTorch到2.x会导致torch.cuda.is_available()返回False。作者在Dockerfile第1行就锁死CUDA版本而非用FROM python:3.8-slim再装CUDA——后者在校园网内下载cudnn包常超时失败。执行docker build -t plant-detect . docker run -p 5000:5000 plant-detect后访问http://localhost:5000即可看到Web界面所有静态资源CSS/JS/图片均通过send_from_directory()正确路由。3.3 Nginx反向代理配置解决/static/路径404与HTTPS强制跳转当部署到云服务器时需用Nginx代理Flask默认5000端口并处理静态资源。nginx.conf关键段落upstream plant_backend { server 127.0.0.1:5000; } server { listen 80; server_name your-domain.com; # 强制HTTPS跳转 return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # 静态资源直接由Nginx服务不经过Flask location /static/ { alias /app/app/static/; expires 1h; } # API请求转发给Flask location / { proxy_pass http://plant_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }为什么location /static/必须用alias而非rootroot会拼接路径为/app/app/static//static/xxx.css而alias将/static/直接映射到/app/app/static/确保link href/static/css/style.css正确加载。此配置使Nginx承担静态文件服务压力Flask专注模型推理QPS从单进程3.2提升至12.7实测ab -n 1000 -c 50 http://localhost:5000/predict。4. 避坑指南毕设调试中最常踩的5个坑以及血泪换来的解决方案4.1 现象Plant_Disease_RESNET50.ipynb运行到model.train()时报CUDA out of memory原因默认batch_size32在RTX 306012GB显存上超出容量尤其DenseNet121因特征图通道数多显存占用比ResNet50高40%。解决在Notebook第7 cell中将DataLoader的batch_size从32改为16并添加pin_memoryTrue加速数据传输train_loader DataLoader(train_dataset, batch_size16, shuffleTrue, num_workers4, pin_memoryTrue)验证方法运行nvidia-smi观察Memory-Usage是否稳定在8.2GB以下。若仍OOM进一步降至8并启用torch.cuda.empty_cache()。4.2 现象Flask启动后上传图片返回{error: No file uploaded}但前端确已选择文件原因HTML表单缺少enctypemultipart/form-data属性导致浏览器未以二进制流发送文件。解决检查app/view/templates/index.html中form标签form iduploadForm enctypemultipart/form-data !-- 必须存在 -- input typefile namefile acceptimage/* required button typesubmit识别病害/button /form血泪经验曾因复制某博客代码漏掉enctype调试2小时才发现request.files为空。建议在routes.py的predict()函数开头加日志app.logger.info(fFiles received: {list(request.files.keys())})。4.3 现象Docker容器内gunicorn启动后立即退出docker logs显示Address already in use原因app/server.py中if __name__ __main__: app.run(...)未被注释导致Flask与Gunicorn双重启动端口5000被抢占。解决打开app/server.py将if __name__ __main__:块整段注释掉只保留app create_app()。Docker CMD中gunicorn才是唯一入口。验证docker exec -it container_id bash进入容器执行netstat -tuln | grep :5000应只显示gunicorn进程。4.4 现象test/集上某类病害如pepper_spotF1-score仅0.32远低于其他类原因该类图像在images/test/pepper_spot/中存在大量强反光叶片拍摄时未打柔光而训练集images/train/pepper_spot/均为均匀光照模型未学习到反光鲁棒性。解决在notebook/Plant_Disease_RESNET50.ipynb的数据增强部分为train_transform添加transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3, hue0.1)并在val_transform中禁用该增强确保验证公平。技巧用torchvision.utils.make_grid可视化增强效果确认反光区域被有效扰动。4.5 现象AWS EC2部署后Web界面CSS失效按钮样式错乱原因Nginx配置中location /static/的alias路径错误指向/app/static/而非/app/app/static/因应用代码在/app目录app/是子目录。解决修改nginx.conflocation /static/ { alias /app/app/static/; # 注意双层app/ expires 1h; }排查命令在服务器执行curl -I http://localhost/static/css/style.css返回200 OK才正确若为404检查Nginx error logtail -f /var/log/nginx/error.log。5. 模型可解释性进阶用Grad-CAM可视化ResNet50的决策依据让答辩老师看到“AI为什么认为这是稻瘟病”5.1 Grad-CAM原理极简版不是画热力图而是定位模型“看哪里”Grad-CAMGradient-weighted Class Activation Mapping的核心思想是用目标类别对最后一层卷积特征图的梯度加权求和该特征图生成热力图。它不依赖模型结构ResNet/VGG/DenseNet通用且能精确定位病斑区域——这比单纯说“准确率94%”更有说服力。项目在notebook/Plant_Disease_RESNET50.ipynb的“Explainability”章节已集成只需3步加载预训练模型并提取目标层ResNet50中model.layer4[2].conv3是最后一个卷积层输出2048通道注册梯度钩子hook在前向传播时捕获该层输出反向传播时捕获梯度计算热力图对梯度全局平均池化加权求和特征图再插值到原图尺寸。import torch import torch.nn.functional as F from PIL import Image import numpy as np def grad_cam(model, img_tensor, target_class, layer): 生成Grad-CAM热力图 model.eval() features None gradients None def forward_hook(module, input, output): nonlocal features features output def backward_hook(module, grad_input, grad_output): nonlocal gradients gradients grad_output[0] hook_f layer.register_forward_hook(forward_hook) hook_b layer.register_backward_hook(backward_hook) output model(img_tensor.unsqueeze(0)) model.zero_grad() output[0, target_class].backward() # 反向传播目标类别 # 计算权重对梯度全局平均池化 weights torch.mean(gradients, dim(2, 3), keepdimTrue) # 加权求和特征图 cam torch.sum(weights * features, dim1, keepdimTrue) # ReLU激活插值到原图尺寸 cam F.relu(cam) cam F.interpolate(cam, size(224, 224), modebilinear, align_cornersFalse) hook_f.remove() hook_b.remove() return cam.squeeze().cpu().numpy() # 使用示例 img_pil Image.open(images/test/rice_blast/IMG_001.jpg).convert(RGB) img_tensor transform(img_pil) # transform同训练时 target_class 12 # rice_blast在classes.txt中的索引 cam_map grad_cam(model, img_tensor, target_class, model.layer4[2].conv3) # 可视化叠加 import matplotlib.pyplot as plt plt.figure(figsize(10, 5)) plt.subplot(1, 2, 1) plt.imshow(img_pil) plt.title(Original Image) plt.axis(off) plt.subplot(1, 2, 2) plt.imshow(img_pil) plt.imshow(cam_map, cmapjet, alpha0.5) plt.title(Grad-CAM Heatmap) plt.axis(off) plt.show()参数说明layermodel.layer4[2].conv3是ResNet50的精确目标层layer4是第四残差块[2]是第三个Bottleneckconv3是1x1卷积输出层。若用VGG16则目标层为model.features[-2]倒数第二个卷积层。cam_map数值范围0~1值越高表示模型越关注该区域——你会清晰看到热力图高亮在稻叶上的典型梭形病斑而非叶脉或背景。5.2 将Grad-CAM集成到Web服务让老师上传一张图立刻看到AI的“思考过程”修改app/models/plant_classifier.py的predict_image()函数使其返回热力图base64编码def predict_image(image_path): # ... 原有预测逻辑 ... class_idx pred.argmax().item() confidence pred.max().item() # 生成Grad-CAM热力图 cam_map grad_cam(model, img_tensor, class_idx, model.layer4[2].conv3) # 归一化到0-255并转为uint8 cam_normalized (cam_map - cam_map.min()) / (cam_map.max() - cam_map.min() 1e-8) * 255 cam_uint8 np.uint8(cam_normalized) # 叠加到原图 img_pil Image.open(image_path).convert(RGB).resize((224, 224)) overlay Image.fromarray(cam_uint8).convert(RGB) blended Image.blend(img_pil, overlay, alpha0.4) # 转base64 import io, base64 buffered io.BytesIO() blended.save(buffered, formatPNG) cam_base64 base64.b64encode(buffered.getvalue()).decode() return { class_name: classes[class_idx], confidence: float(confidence), recommendation: recommendations.get(classes[class_idx], 请咨询农技专家), heatmap: fdata:image/png;base64,{cam_base64} }然后在app/view/templates/index.html中添加显示区域div idheatmap-container styledisplay:none; h3AI决策依据/h3 img idheatmap-img src altGrad-CAM Heatmap stylemax-width:100%; /div script document.getElementById(uploadForm).addEventListener(submit, function(e) { e.preventDefault(); // ... 原有AJAX逻辑 ... fetch(/predict, {method:POST, body: formData}) .then(r r.json()) .then(data { document.getElementById(result).innerHTML pstrong识别结果/strong${data.class_name}/p pstrong置信度/strong${(data.confidence*100).toFixed(1)}%/p pstrong防治建议/strong${data.recommendation}/p ; // 显示热力图 document.getElementById(heatmap-img).src data.heatmap; document.getElementById(heatmap-container).style.display block; }); }); /script答辩技巧当老师问“你怎么知道模型没瞎猜”直接点击上传一张稻瘟病图3秒后热力图高亮病斑区域——这比10页公式推导更直观。我当年答辩时导师盯着热力图看了半分钟说“这个能进论文附录。”5.3 用混淆矩阵指导模型迭代不是追求最高准确率而是降低“误诊成本”农作物病害识别中“把健康作物判为病害”假阳性成本远低于“把严重病害判为健康”假阴性。因此毕设论文的模型优化目标应是最大化召回率Recall而非准确率Accuracy。notebook/Plant_Disease_RESNET50.ipynb末尾的plot_confusion_matrix()函数生成的矩阵需重点分析两类错误真实\预测rice_blasthealthy...rice_blast282...healthy525...假阴性FN22张稻瘟病被误判为其他病害需检查rice_blast类图像是否混入非典型样本如早期症状不明显假阳性FP55张健康稻叶被误判为稻瘟病应增加healthy类的负样本如不同光照下的健康叶并调整损失函数权重。实操方案在train.py中修改WeightedRandomSampler为rice_blast类设置更高采样权重weights[rice_blast_idx] 1.5或在CrossEntropyLoss中传入weighttorch.tensor([1.0, 1.0, ..., 1.5, ...])。从那以后我每次调参都强制先画混淆矩阵再看准确率——因为准确率会掩盖致命的假阴性。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →