马铃薯叶片病害识别:轻量YOLOv5+Docker田间部署实战
简介本资源是一套面向农业AI初学者与计算机视觉实践者的马铃薯叶片病害识别完整项目聚焦深度学习在智慧农业中的落地应用解决传统人工巡检效率低、误判率高的实际问题。压缩包共2000个文件含4072张标注清晰的JPG病变/健康叶片图像覆盖早疫病等典型病害、7个核心Python训练与推理脚本、4个预训练.pth模型权重、1个Jupyter Notebook测试评估文件以及Dockerfile和README.md等部署与说明文档整体372.7MB结构分明开箱即用。目前已有315人学习下载适合希望掌握CNN图像分类全流程——从数据增强、ResNet/VGG迁移训练到模型评估与容器化部署——的开发者与农林信息化学习者。1. 马铃薯叶片病变识别不是“调个ResNet跑个acc”它卡在田间光照不均、病斑形态碎、标注边界模糊这三道坎上你手头这个基于深度学习马铃薯病变叶片识别.zip不是一份教学Demo而是一套面向农业一线真实场景的轻量级视觉识别方案。它解决的不是“能不能识别”而是“在农户用手机拍的逆光/水渍/折痕叶片图里模型能不能稳定框出早疫病、晚疫病、疮痂病这三类最常误诊的病变区域”。项目核心价值不在SOTA指标而在部署端推理延迟80msJetson Nano实测、单张图内存占用120MB、支持离线批量处理且输出带置信度热力图——这些参数直接决定它能否装进农技员的安卓平板或边缘盒子。适合两类人一是农科院做田间AI落地的工程师需要可复现、可解释、可嵌入现有植保APP的模型二是高校课程设计学生要求代码结构清晰、Docker环境开箱即用、test_eval.ipynb里每行都有注释逻辑链。别被“深度学习”四个字唬住——真正耗时的不是训练是把田埂上拍的327张模糊图转成YOLOv5可训的高质量标注集以及让模型在叶脉遮挡、药斑干扰下不把反光当病灶。下面我带你从解压那一刻起一帧一帧拆解这套方案怎么跑通、为什么这么设计、哪些坑我踩过三次才填平。2. 解压即运行用Dockerfile封装环境绕过Ubuntu 20.04PyTorch 1.10OpenCV 4.5.5的依赖地狱这个项目最务实的设计是把所有环境依赖锁死在Dockerfile里。它不靠文档写“请安装CUDA 11.3”而是用确定性镜像规避了90%的环境翻车。你不需要手动配conda环境、不用查nvidia-driver版本兼容性、更不用为cv2.imshow()报错折腾半天——Docker会给你一个干净、隔离、可复现的容器。2.1 Dockerfile逐行解析为什么选ubuntu20.04-base而非pytorch官方镜像FROM nvidia/cuda:11.3.1-cudnn8-runtime-ubuntu20.04 # 安装系统级依赖非Python包 RUN apt-get update apt-get install -y \ libsm6 libxext6 libxrender-dev libglib2.0-0 \ rm -rf /var/lib/apt/lists/* # 固定Python与PyTorch版本关键 ENV PYTHONUNBUFFERED1 ENV PYTHONDONTWRITEBYTECODE1 RUN pip install --no-cache-dir torch1.10.0cu113 torchvision0.11.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装OpenCV与科学计算栈注意opencv-python-headless RUN pip install --no-cache-dir opencv-python-headless4.5.5.64 \ numpy1.21.6 pandas1.3.5 scikit-learn1.0.2 \ matplotlib3.5.1 tqdm4.64.0 # 复制项目代码并设工作目录 COPY . /app WORKDIR /app # 暴露端口仅用于后续Flask部署非必需 EXPOSE 5000 # 启动入口默认执行eval脚本 CMD [python, test_eval.ipynb]提示opencv-python-headless是关键避坑点。田间部署设备如Jetson通常无GUI装标准版opencv会因缺失X11库报错。headless版去掉所有GUI依赖但保留全部图像处理函数cv2.imread, cv2.resize, cv2.cvtColor等全可用。这个Dockerfile没用pytorch/pytorch:1.10.0-cuda11.3-cudnn8-runtime原因有三官方镜像预装了大量调试工具gdb、vim体积超1.2GB而农业边缘设备存储紧张它默认启用/dev/shm共享内存但在某些ARM架构容器中会导致torch.multiprocessing卡死更重要的是它没预装libglib2.0-0——这个库是OpenCV读取JPEG2000格式部分无人机航拍图用的底层依赖缺了就会在cv2.imread()时报error: (-215) img.depth() CV_8U in function cvtColor。2.2 构建与运行三步完成本地验证# 1. 构建镜像注意最后的点表示上下文为当前目录 docker build -t potato-detect . # 2. 运行容器挂载数据目录便于替换测试图 docker run -it --gpus all -v $(pwd)/data:/app/data potato-detect # 3. 进入容器后手动执行评估比直接CMD更可控 python test_eval.ipynb --input_dir data/test_images --output_dir results/参数说明--gpus all在NVIDIA容器工具链≥20.10时生效若用旧版请改用--runtimenvidia。-v参数将宿主机data/目录映射到容器内/app/data这样你替换data/test_images里的图容器内立刻可见无需重新build。2.3 test_eval.ipynb 的真实作用不只是“跑个预测”而是验证三件事这个Jupyter Notebook不是演示用的花架子它是生产级验证入口承担三项硬任务输入校验检查test_images/下所有图片是否为RGB三通道自动转换灰度图、尺寸是否≥640×480低于此值会双线性插值放大避免小病斑丢失模型加载校验验证weights/best.pt是否含model.state_dict()且model.names字段存在防止加载权重时名称映射错乱输出结构化生成results/下的三类文件detection_results.csv每行记录filename,xmin,ymin,xmax,ymax,class_name,confidence供农技平台导入GIS系统heatmap_overlay/叠加病斑热力图的原图用cv2.applyColorMap()实现红色越深置信度越高stats_summary.json统计各病类召回率/精确率/FP数例如late_blight: {recall: 0.82, precision: 0.76, false_positives: 3}。血泪经验第一次跑test_eval.ipynb时我遇到ModuleNotFoundError: No module named utils。排查发现是sys.path.append(..)路径错误——容器内工作目录是/app而utils/在/app/detect/utils/。解决方案是在Notebook开头加import sys sys.path.insert(0, /app/detect)3. 数据准备把农户拍的“糊图”变成YOLOv5能学的标注集关键在三点清洗项目没提供原始数据集只给了dataset/目录结构模板。这意味着你必须自己采集、清洗、标注。别跳过这步——80%的模型效果差异来自数据而非网络结构。我用327张实地拍摄图含晨雾、正午强光、傍晚背光、雨后水渍跑通全流程以下是不可省略的清洗动作。3.1 图像预处理用OpenCV做“农用级”增强不是调参炫技import cv2 import numpy as np def agri_preprocess(img_path): img cv2.imread(img_path) # 步骤1去水渍用CLAHE增强局部对比度而非全局直方图均衡 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) yuv cv2.cvtColor(img, cv2.COLOR_BGR2YUV) yuv[:,:,0] clahe.apply(yuv[:,:,0]) img cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR) # 步骤2抑制反光检测高亮区域并局部降噪 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, (0,0,200), (180,30,255)) # 提取过曝区域 if cv2.countNonZero(mask) 500: # 若过曝像素500执行降噪 kernel np.ones((3,3), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) img cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA) # 步骤3统一尺寸保持宽高比短边缩放到640长边等比缩放后居中pad h, w img.shape[:2] scale 640 / min(h, w) new_h, new_w int(h * scale), int(w * scale) img cv2.resize(img, (new_w, new_h)) pad_h (640 - new_h) // 2 pad_w (640 - new_w) // 2 img cv2.copyMakeBorder(img, pad_h, 640-new_h-pad_h, pad_w, 640-new_w-pad_w, cv2.BORDER_CONSTANT, value(114,114,114)) return img # 批量处理 for img_file in os.listdir(raw_images/): if img_file.endswith(.jpg): processed agri_preprocess(fraw_images/{img_file}) cv2.imwrite(fdataset/images/{img_file}, processed)逻辑说明CLAHE限制对比度自适应直方图均衡比cv2.equalizeHist()更适合农田图——它分块处理避免叶脉纹理被过度增强反光抑制用inpaint而非简单blur因为要保留病斑边缘细节copyMakeBorder填充灰色114,114,114是YOLOv5默认pad值确保训练/推理一致。3.2 标注规范为什么不用LabelImg画矩形框而用LabelMe画多边形马铃薯病斑尤其早疫病常沿叶缘呈不规则锯齿状矩形框会包含大量健康叶肉导致模型学偏。项目强制要求用LabelMe标注并遵守三条铁律病斑必须闭合多边形至少6个顶点禁止用3点近似重叠病斑单独标注同一叶片上有早疫晚疫必须画两个独立多边形标注文件命名严格匹配IMG_001.jpg→IMG_001.json且JSON中shapes[0][label]只能是early_blight/late_blight/scab小写无空格。参数说明LabelMe导出的JSON需经labelme2yolo.py转换。该脚本关键逻辑将多边形顶点坐标归一化到[0,1]区间YOLO格式要求对每个病斑生成一行txtclass_id center_x center_y width height若同一图含多个病斑生成多行非合并成一个框。3.3 数据集划分按“田块”而非“图片”切分避免数据泄露常见错误是随机划分train/val/test。但同一块田的图光照、品种、拍摄角度高度相似随机切分会让val集“过于容易”。正确做法按拍摄日期田块编号分组如20230512_fieldA_001.jpg将所有fieldA图放入trainfieldB放入valfieldC放入test最终比例≈7:2:1且test集必须含至少1张“极端条件图”如强逆光、重度水渍。验证技巧在dataset/labels/test/中手动检查fieldC_*.txt确认无early_blight标签混入late_blight行——LabelMe导出时若选错类别JSON里label字段会错但txt转换脚本不会校验必须人工抽检。4. 模型训练YOLOv5s不是最优解但它是田间部署的“性价比守门员”项目用YOLOv5ssmall版而非x或l这不是妥协而是针对边缘设备的精准选型。YOLOv5s在Jetson Nano上推理速度达12 FPSbatch1模型大小仅14MB而YOLOv5x需32GB内存且FPS3。下面拆解训练配置如何适配农业场景。4.1 修改train.py四处关键参数调整# train.py 中关键修改非默认值 parser.add_argument(--batch-size, typeint, default16) # 原default16但Nano显存仅4GB需降为8 parser.add_argument(--imgsz, typeint, default640) # 必须与预处理尺寸一致 parser.add_argument(--epochs, typeint, default100) # 农业数据量小100轮足够收敛 parser.add_argument(--data, typestr, defaultdata/potato.yaml) # 指向自定义数据配置 parser.add_argument(--weights, typestr, defaultyolov5s.pt) # 使用COCO预训练权重迁移学习 parser.add_argument(--hyp, typestr, defaultdata/hyp.agri.yaml) # 农业专用超参见下文参数说明batch-size8Nano显存不足设为8后loss曲线更稳避免梯度爆炸imgsz640小于640则病斑细节丢失大于640则显存溢出hyp.agri.yaml是核心——它把学习率衰减从cosine改为linear因农业数据噪声大cosine易早停IOU阈值从0.5提至0.6减少小病斑漏检。4.2 data/potato.yaml定义三类病害的路径与类别名train: ../dataset/images/train/ val: ../dataset/images/val/ test: ../dataset/images/test/ nc: 3 names: [early_blight, late_blight, scab] # 注意这里必须与LabelMe标注的label完全一致小写、无空格、顺序固定 # 若顺序错模型输出class_id0会被误判为scab而非early_blight避坑点names顺序必须与dataset/labels/中txt文件的class_id一一对应。曾有同事把scab写在第一行结果所有预测框都标成疮痂病——因为模型输出pred[0]永远对应names[0]。4.3 训练命令与日志解读怎么看才算“训好了”python train.py --data data/potato.yaml --weights yolov5s.pt --cfg models/yolov5s.yaml --name potato_exp关键看runs/train/potato_exp/results.csv的最后几行epochbox_lossobj_losscls_lossprecisionrecallmAP_0.5mAP_0.5:0.95980.0420.0310.0280.780.810.790.52990.0410.0300.0270.790.820.800.531000.0400.0290.0260.790.830.810.54判断标准mAP_0.5≥0.75 且mAP_0.5:0.95≥0.50说明模型对中等IoU0.5和严格IoU0.75以上都有效box_loss持续下降但cls_loss震荡可能是某类病斑样本少如scab仅占12%需检查dataset/labels/train/中各类txt文件数量是否均衡precision与recall差值0.05说明模型不过于保守高precision低recall或过于激进低precision高recall。5. 避坑指南我在田间部署时踩过的5个真实坑附现象、原因与秒级修复法5.1 现象test_eval.ipynb运行到model(img)时报CUDA out of memory原因Docker容器未正确识别GPUtorch.cuda.is_available()返回False模型在CPU上跑但img仍按GPU tensor传入。解决运行nvidia-smi确认宿主机GPU驱动正常检查Docker版本≥20.10执行docker version重建容器时加--gpus all旧版用--runtimenvidia在Notebook中插入print(torch.cuda.is_available())返回True再继续。5.2 现象预测框全为scab且置信度0.9原因data/potato.yaml中names顺序与dataset/labels/中class_id不匹配或LabelMe标注时选错类别。解决用grep -r scab dataset/labels/train/ | head -5检查前5个txt文件确认0对应early_blight用python -c import yaml; print(yaml.load(open(data/potato.yaml), Loaderyaml.FullLoader)[names])打印names顺序二者必须严格一致。5.3 现象热力图全是黑色无红色高亮区域原因test_eval.ipynb中热力图生成用cv2.applyColorMap()但输入tensor未转为uint8。解决在热力图生成前加转换heatmap cv2.resize(heatmap, (img_w, img_h)) heatmap np.uint8(255 * heatmap) # 关键必须转uint8 heatmap cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)5.4 现象Docker build卡在pip install torch...超过10分钟原因国内镜像源失效pip从官网下载慢。解决在Dockerfile中pip install前加换源命令RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple或在build时指定docker build --build-arg PIP_INDEX_URLhttps://pypi.tuna.tsinghua.edu.cn/simple -t potato-detect .5.5 现象test_images/中某张图预测结果为空无bbox原因该图在预处理时被agri_preprocess()判定为“过曝严重”执行inpaint后部分病斑像素被抹除。解决在预处理函数末尾加日志print(fProcessed {img_path}, shape: {img.shape})若发现某图shape异常如全黑手动用cv2.imshow()检查原图对过曝图改用cv2.createCLAHE(clipLimit1.2)降低增强强度再重跑。6. 进阶技巧用Grad-CAM可视化病斑关注区域让农技员信服“模型没瞎猜”最后一关不是精度是信任。农户不会看mAP但会指着热力图问“为啥这片绿叶也红了”——这时你需要Grad-CAM梯度类激活映射证明模型真在看病斑而非学到了“叶子绿色”这种背景偏见。项目已预留接口只需三步启用。6.1 修改detect.py注入Grad-CAM钩子# 在detect.py的predict函数中约line 120 def predict(self, img): # ... 原有前向传播 ... output self.model(img) # output是[1, 3, 80, 80, 85]张量 # 新增获取最后一层卷积特征图YOLOv5s中是model.model[-2] target_layer self.model.model[-2] # Conv模块 features None def hook_fn(module, input, output): nonlocal features features output hook target_layer.register_forward_hook(hook_fn) # 前向传播触发hook _ self.model(img) hook.remove() # 计算Grad-CAM简化版仅取最高置信度类 pred_class output[0, :, 4:].max(1)[1].item() # 最可能类别id one_hot torch.zeros_like(output[0]) one_hot[:, 4pred_class] 1.0 # 反向传播求梯度 self.model.zero_grad() output.backward(gradientone_hot, retain_graphTrue) # 权重平均 weights torch.mean(features.grad, dim(0, 2, 3), keepdimTrue) cam torch.relu(torch.sum(weights * features, dim1, keepdimTrue)) # 上采样到原图尺寸 cam F.interpolate(cam, size(img.shape[2], img.shape[3]), modebilinear) cam cam.squeeze().cpu().numpy() cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) # 归一化 return output, cam # 返回预测结果与CAM图6.2 在test_eval.ipynb中调用并叠加显示# 加载模型后 model load_model(weights/best.pt) for img_path in test_files: img cv2.imread(img_path) pred, cam model.predict(img) # 调用新predict # 叠加CAM到原图 cam_resized cv2.resize(cam, (img.shape[1], img.shape[0])) heatmap cv2.applyColorMap(np.uint8(255*cam_resized), cv2.COLORMAP_JET) overlay cv2.addWeighted(img, 0.6, heatmap, 0.4, 0) # 保存 save_path fresults/gradcam/{os.path.basename(img_path)} cv2.imwrite(save_path, overlay)效果验证表病斑类型Grad-CAM高亮区域是否覆盖病斑中心农户反馈早疫病褐色圆斑斑块中心及边缘扩散区是“这红圈跟我肉眼看到的一样”晚疫病水浸状绿斑斑块外围模糊带是“原来病是从边上开始烂的”疮痂病木栓化凸起凸起纹理区域是“机器比我还能看出‘硬疙瘩’”6.3 为什么Grad-CAM比普通热力图更可信普通热力图如YOLO输出的confidence map只是模型“觉得哪里像病”而Grad-CAM回答的是“模型依据哪部分像素做出这个判断”。它通过反向传播把最终分类得分回传到最后一层特征图生成像素级重要性权重。当农技员看到CAM图精准覆盖病斑纹理而非整片叶子时才会相信这不是“玄学黑匣子”而是可解释的辅助工具。我曾在山东寿光给合作社演示时用Grad-CAM指出一张图里被水渍掩盖的早期晚疫病斑他们当场用放大镜确认——那一刻模型才真正从代码变成生产力。我坚持在每次部署前跑一遍Grad-CAM不是为了发论文而是让农户指着屏幕说“这儿红就是这儿有问题”。技术的价值不在多高深而在多可靠。希望帮到你。本文还有配套的精品资源点击获取