
简介这份资源是面向计算机、人工智能、自动化等专业学生与开发者的水稻病虫害自动识别系统完整项目基于Python与机器学习技术实现可作为毕业设计、期末课程大作业或进阶练手案例。项目为个人毕设成果答辩评审分达98分代码均经过调试测试确保可正常运行基础较好的学习者还能在此基础上修改调整扩展出不同功能。压缩包共311个文件约2.56MB涵盖xml配置、java与class后端代码、vue前端页面、js脚本、css样式以及jpg、png图像样本和md说明文档等前后端与数据资源配套齐全目录结构清晰便于按模块检索学习。目前已有293人学习关注适合希望快速获取可运行项目、理解病虫害识别流程与前后端协作方式的学习者参考借鉴。1. 水稻病虫害识别从数据集到推理一套能跑通的 Python 机器学习方案长什么样水稻病虫害识别这件事真正卡住大多数人的从来不是模型结构而是数据。稻瘟病、纹枯病、稻飞虱、二化螟这几类田间拍出来的照片背景杂乱、光照不均、病斑尺度差异极大同一片叶子早期和晚期能差出两个数量级。我见过太多人拿着网上凑的几百张图直接上 ResNet训练集准确率 99%换一批新图就崩到 60% 以下这就是典型的过拟合加数据泄漏。这套「基于 Python 机器学习的水稻病虫害自动识别系统」要解决的就是把数据采集、清洗、增强、训练、部署这条链路完整串起来让你拿到源码和数据后能真正复现出一个可用的识别系统而不是一个只在验证集上好看的玩具。它适合有 Python 基础、想做一个完整计算机视觉落地项目的学生和初级工程师也适合需要快速搭出病虫害识别原型的农业信息化从业者。下面我按实际做项目的顺序把每个环节的参数、坑和判断标准讲清楚。2. 数据准备与增强水稻病虫害数据集怎么清洗才不翻车2.1 先搞清楚你手里的是什么数据拿到一份水稻病虫害数据集第一件事不是写模型是统计。常见做法是先把目录结构打印出来看每个类别的图片数量和分辨率分布。水稻病虫害公开数据集一般包含稻瘟病Rice Blast、纹枯病Sheath Blight、稻曲病False Smut、白叶枯病Bacterial Leaf Blight以及健康叶片这几类每类几百到上千张不等。数量不均衡是常态健康样本往往最多稀有病害可能只有一两百张。import os from collections import Counter from PIL import Image data_root data/rice_disease class_counts Counter() size_stats [] for cls in os.listdir(data_root): cls_dir os.path.join(data_root, cls) if not os.path.isdir(cls_dir): continue imgs [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .png, .jpeg))] class_counts[cls] len(imgs) # 抽样统计分辨率避免全量读取拖慢速度 for f in imgs[:20]: with Image.open(os.path.join(cls_dir, f)) as im: size_stats.append(im.size) print(类别分布:, class_counts) print(分辨率样本:, size_stats[:10])这段代码做两件事统计类别分布抽样看分辨率。逻辑很直白但参数上有个细节——抽样数量别设太大20 张足够看出分辨率范围全量读取几千张图会浪费好几分钟。如果发现某类少于 200 张后面增强策略就要针对性加码如果分辨率跨度从 200px 到 4000px 都有统一 resize 之前得先决定是裁剪病斑区域还是整图缩放。2.2 数据清洗的三个硬标准清洗不是删几张模糊图那么简单。我一般按三个标准过一遍第一剔除纯背景图或叶片占比过低的图这类图会让模型学到错误的背景关联第二剔除重复图同一张图换个文件名重复出现是数据泄漏的头号来源用感知哈希pHash去重最稳第三标注错误的图必须人工过一遍尤其是稻瘟病和纹枯病早期症状相似标错会直接拉低上限。import imagehash from PIL import Image import os def dedup_by_phash(folder, threshold5): hashes {} removed [] for f in os.listdir(folder): path os.path.join(folder, f) try: with Image.open(path) as im: h imagehash.phash(im) except Exception: continue dup False for exist_h, exist_f in hashes.items(): if h - exist_h threshold: # 汉明距离小于阈值判为重复 dup True removed.append((f, exist_f)) break if not dup: hashes[h] f return removed removed dedup_by_phash(data/rice_disease/rice_blast) print(f删除重复图 {len(removed)} 张)pHash 的 threshold 设 5 是个经验值设太小漏删设太大误删。汉明距离小于等于 5 基本可以判定为同一张图的变体。这段代码只处理单个类别目录实际用的时候对每个类跑一遍。去重之后你会发现有些类可能直接少掉两三成这是好事说明原来数据里的水分被挤掉了。2.3 增强策略要按病害特点来定通用增强随机翻转、旋转、亮度抖动对水稻病害是基础操作但不够。水稻病斑的关键特征是形状和纹理所以我会额外加两类增强一是随机裁剪加缩放模拟不同拍摄距离二是颜色抖动控制在小范围因为病斑颜色是重要判别特征抖太狠反而破坏信号。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.7, 1.0)), # 模拟不同拍摄距离 transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.02), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])参数说明RandomResizedCrop的 scale 下限设 0.7意思是至少保留 70% 的画面再小就可能把病斑裁没了。ColorJitter的 hue 只给 0.02因为色相变化对病斑颜色影响太大给多了模型学不到稳定特征。验证集只做 resize 和归一化不做任何随机增强这是评估可靠性的底线。归一化用的 ImageNet 均值方差如果你从零训练可以换成数据集自身统计值但用预训练权重就必须保持一致。3. 模型选型与训练从 ResNet 到轻量网络的取舍3.1 为什么我不建议一上来就自己搭网络水稻病虫害识别本质是细粒度图像分类病斑差异体现在局部纹理上。自己从零搭一个 CNN在几千张图的规模下几乎不可能超过预训练模型微调的效果。常见做法是拿 ResNet50 或 EfficientNet-B0 做 backbone替换最后的全连接层冻结前面几层先训分类头再解冻整体微调。EfficientNet-B0 参数量只有 ResNet50 的五分之一左右精度在多数农业数据集上差距不到 2 个百分点如果最终要部署到边缘设备优先选它。import torch import torch.nn as nn from torchvision import models def build_model(num_classes, backboneefficientnet_b0, pretrainedTrue): if backbone resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT if pretrained else None) in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) elif backbone efficientnet_b0: model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.DEFAULT if pretrained else None) in_features model.classifier[1].in_features model.classifier nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model model build_model(num_classes5, backboneefficientnet_b0)Dropout 设 0.3 是防止分类头过拟合数据量小于 5000 张时这个值可以提到 0.4 到 0.5。替换分类层的时候注意 EfficientNet 的 classifier 是个 Sequential索引 1 才是 Linear 层直接改model.classifier整个替换更省事。3.2 两阶段训练的参数怎么设冻结 backbone 阶段学习率可以给大一点1e-3 配 Adam 就行训 5 到 8 个 epoch。解冻微调阶段学习率必须降下来我一般用 1e-4 配余弦退火训 20 到 30 个 epoch。batch size 看显存8GB 显存跑 EfficientNet-B0 加 224 输入batch 32 没问题。from torch.optim import Adam from torch.optim.lr_scheduler import CosineAnnealingLR # 阶段一只训分类头 for p in model.parameters(): p.requires_grad False for p in model.classifier.parameters(): p.requires_grad True optimizer Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) criterion nn.CrossEntropyLoss(label_smoothing0.1) # 阶段二解冻全部小学习率微调 # for p in model.parameters(): # p.requires_grad True # optimizer Adam(model.parameters(), lr1e-4) # scheduler CosineAnnealingLR(optimizer, T_max30)label_smoothing0.1这个参数值得加水稻病害有些样本标注本身有歧义硬标签会让模型过度自信。类别不均衡的话给 CrossEntropyLoss 传 weight 参数按类别频率的倒数来设或者直接用 WeightedRandomSampler 在采样层面解决。3.3 训练过程中该盯哪些指标不要只看 loss。我一般同时记录训练集和验证集的准确率、F1以及混淆矩阵。水稻病害里稻瘟病和纹枯病最容易混如果这两个类在混淆矩阵上互相误判超过 15%说明模型没学到区分性特征要么加数据要么在增强上做针对性处理。验证集 F1 连续 5 个 epoch 不涨就可以停了再训就是过拟合。4. 推理部署与系统集成把模型变成能用的识别接口4.1 导出与推理脚本训练完的模型要能脱离训练代码独立跑。我习惯导出 state_dict 加一个类别映射 json推理脚本只依赖 torch 和 PIL不依赖训练时的任何自定义模块。import json import torch from PIL import Image from torchvision import transforms def load_inference_model(weight_path, num_classes5): model build_model(num_classesnum_classes, backboneefficientnet_b0, pretrainedFalse) model.load_state_dict(torch.load(weight_path, map_locationcpu)) model.eval() return model def predict(image_path, model, class_names): tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) img Image.open(image_path).convert(RGB) tensor tf(img).unsqueeze(0) with torch.no_grad(): logits model(tensor) probs torch.softmax(logits, dim1)[0] idx probs.argmax().item() return class_names[idx], probs[idx].item() class_names [health, rice_blast, sheath_blight, false_smut, bacterial_blight] model load_inference_model(weights/best.pth) label, conf predict(test.jpg, model, class_names) print(f识别结果: {label}, 置信度: {conf:.3f})model.eval()必须调否则 Dropout 和 BatchNorm 行为不对推理结果会飘。torch.no_grad()省显存也提速。置信度低于 0.6 的时候建议返回「不确定」而不是硬给一个类别农业场景里误判的代价比拒判高。4.2 用 Flask 包一个最小可用接口系统集成最轻的方式是 Flask一个 POST 接口接收图片返回类别和置信度。前端可以是网页上传也可以是移动端调用。from flask import Flask, request, jsonify import io from PIL import Image app Flask(__name__) model load_inference_model(weights/best.pth) class_names [health, rice_blast, sheath_blight, false_smut, bacterial_blight] app.route(/predict, methods[POST]) def api_predict(): file request.files.get(image) if not file: return jsonify({error: no image}), 400 img Image.open(io.BytesIO(file.read())).convert(RGB) tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) tensor tf(img).unsqueeze(0) with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] idx probs.argmax().item() return jsonify({label: class_names[idx], confidence: round(probs[idx].item(), 4)}) if __name__ __main__: app.run(host0.0.0.0, port5000)接口层面注意两点一是图片读取用io.BytesIO包一层避免临时文件写磁盘二是返回结构固定成label加confidence前端好处理。生产环境别用 Flask 自带 server换 gunicorn 起多 worker模型加载放在全局只做一次。5. 避坑与排查水稻病虫害识别项目里最容易踩的五个坑5.1 验证集准确率虚高换图就崩现象训练时验证集准确率 95% 以上拿手机拍的新图测试错得离谱。原因数据泄漏同一片叶子的不同角度照片被分到了训练集和验证集模型记住了叶子而不是病斑。解决按「拍摄批次」或「叶片个体」划分数据集同一片叶子的所有图只能出现在一个集合里。如果数据里没有批次信息用 pHash 聚类后再划分。5.2 损失不下降准确率卡在随机水平现象训练几个 epoch 后 loss 几乎不动准确率在类别数倒数附近徘徊。原因最常见的是归一化参数和预训练权重不匹配或者标签映射错了位。解决先检查transforms.Normalize用的均值和预训练权重是否一致再打印几个 batch 的标签和图片对应关系确认没有错位。学习率过大也会导致这个现象把 1e-3 降到 1e-4 试试。5.3 某类识别率极低混淆矩阵集中在一两类现象健康叶片识别很准但稻瘟病和纹枯病互相误判严重。原因这两类早期症状确实相似且训练数据里可能有一批标注边界模糊的样本。解决把这两类的混淆样本单独捞出来人工复核标注同时针对病斑区域做裁剪增强让模型聚焦局部纹理而不是整叶形状。5.4 推理速度慢单张图要好几秒现象接口响应时间超过 2 秒批量测试时更慢。原因没用torch.no_grad()或者模型还在 train 模式或者每次请求都重新加载模型。解决确认model.eval()和torch.no_grad()都加了模型加载放在应用启动时只做一次。如果还慢考虑用 ONNX Runtime 或 TorchScript 导出加速EfficientNet-B0 在 CPU 上单张推理可以压到 100ms 以内。5.5 部署后置信度普遍偏低现象所有预测的置信度都在 0.4 到 0.6 之间没有明显的高置信结果。原因训练时的预处理和推理时的预处理不一致比如训练用了 RandomResizedCrop 但推理只做了 Resize尺度分布对不上。解决推理预处理必须和验证集预处理完全一致验证集怎么做的推理就怎么做一个参数都不能差。6. 把识别准确率再往上推一档两个我实际用过的技巧第一个技巧是测试时增强TTA。推理时对同一张图做多次变换水平翻转、不同尺度裁剪把多次预测的概率平均。这个操作不增加训练成本只增加推理时间通常能把准确率拉高 1 到 3 个百分点。实现上就是把推理循环跑几遍每遍用不同的 transform最后 softmax 概率求平均。def predict_with_tta(image_path, model, class_names, n_crops5): img Image.open(image_path).convert(RGB) tta_transforms [ transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])]), transforms.Compose([transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])]), transforms.Compose([transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p1.0), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])]), ] probs_sum torch.zeros(len(class_names)) with torch.no_grad(): for tf in tta_transforms: tensor tf(img).unsqueeze(0) probs_sum torch.softmax(model(tensor), dim1)[0] probs_avg probs_sum / len(tta_transforms) idx probs_avg.argmax().item() return class_names[idx], probs_avg[idx].item()第二个技巧是错误样本回流。系统上线后把置信度低于 0.6 的样本自动存下来人工标注后加入训练集重新微调。这个闭环跑两三轮模型在真实场景下的表现会有明显提升。我自己的习惯是每积累 200 张新标注样本就重训一次分类头backbone 不动学习率 1e-4 跑 10 个 epoch成本很低但收益稳定。做这类农业识别项目数据迭代的优先级永远高于换模型结构这是我踩了无数坑之后最深的体会。希望帮到你。本文还有配套的精品资源点击获取