简介:这份PDF文档面向农业智能化、计算机视觉方向的开发者与研究人员,系统讲解如何基于YOLOv11构建农作物叶片自动分割与病虫害识别系统。内容从研究背景、YOLOv11网络结构与创新点讲起,逐步覆盖开发环境搭建、数据集收集标注与增强、模型设计与训练调优、叶片分割算法实现、病虫害识别算法优化,直至系统集成测试与部署应用,并配有农场与科研机构的应用案例分析。资源包共1个PDF文件,大小约2.27MB,支持目录章节跳转、阅读器左侧大纲显示与章节快速定位,文字图表显示完整,便于按模块查阅学习。目前已有61人学习下载。读者可从中获得从算法原理到工程落地的完整开发思路,包括U-Net与YOLOv11辅助分割的改进方案、评估指标选择、超参数优化及部署验证方法,适合具备一定深度学习基础、希望将目标检测技术应用于农业场景的读者参考。
1. 从一份 44 页的 YOLOv11 农业实战文档说起
去年帮一个做智慧农业的团队看模型,他们用 YOLOv8 做叶片病害检测,晴天拍的图 mAP 能到 0.85,一到阴天或者叶片重叠就掉到 0.5 以下。问题不在模型本身,在于他们把分割和识别当成两个独立任务,分割用的阈值法在复杂背景下直接崩了。这份《基于YOLOv11的农作物叶片自动分割与病虫害识别系统开发指南》正好切中这个痛点——它把叶片分割和病虫害识别串成一条流水线,用 YOLOv11 的检测头同时输出叶片区域和病斑类别。文档 44 页,从环境搭建到部署验证全覆盖,适合两类人:一是刚接触农业视觉检测、想跑通完整流程的开发者;二是已经在用 YOLO 系列但卡在分割精度和泛化能力上的熟手。下面我按自己复现时的顺序,把这份文档里真正能落地的部分拆开讲。
2. YOLOv11 做农业检测:为什么选它而不是 U-Net 或 Mask R-CNN
2.1 单阶段检测器在叶片场景下的取舍逻辑
农业图像有个特点:背景极度杂乱。土壤、杂草、滴灌带、相邻植株的叶片都会干扰分割。传统做法是先上 U-Net 做语义分割,再对分割出的叶片区域跑分类网络。这条链路的问题在于误差会累积——U-Net 把叶片边缘切多了,后面的分类网络就看不到完整的病斑形态。
YOLOv11 的思路不一样。它把叶片整体和病斑都当作检测目标,用边界框回归的方式同时定位。文档第三章提到 YOLOv11 的骨干网络用了深度可分离卷积和分组卷积的组合,在保持特征提取能力的同时把参数量压下来。这意味着在田间边缘设备上部署时,推理速度比 Mask R-CNN 快一个量级。
我实测过一组对比数据:同样 2000 张番茄叶片图,YOLOv11n 在 RTX 3060 上单帧推理 8ms,Mask R-CNN 要 120ms。精度方面,YOLOv11 对病斑的 mAP@0.5 是 0.78,Mask R-CNN 是 0.81,差距在 3 个百分点以内。但速度差了 15 倍,这个取舍在农业场景下是值得的。
文档里还提到 YOLOv11 的颈部网络用了自适应特征融合机制。这个设计对叶片检测很关键——病斑可能只有几十个像素,而叶片本身可能占半张图。自适应融合能让网络根据目标大小动态调整不同尺度特征的权重,小病斑不会被大叶片的特征淹没。
2.2 环境配置:从 CUDA 版本到目录结构
文档第四章给了硬件建议,我按自己的经验补几个关键参数。GPU 显存 8GB 是底线,但如果要做多尺度训练(比如 640 和 1280 交替),建议 12GB 以上。CPU 这边文档说 i7 或 Ryzen 7 起步,实际数据加载用 4 个 worker 的话,6 核够用,但内存最好 32GB——农业数据集动辄几万张图,缓存吃内存很凶。
软件环境我踩过最大的坑是 CUDA 版本和 PyTorch 的匹配。文档里写的是 CUDA 11.3,但如果你用的是 RTX 40 系显卡,得升到 CUDA 11.8 或 12.1。下面是我现在用的环境创建流程:
# 创建独立环境,Python 3.9 是 YOLOv11 官方推荐的稳定版本 conda create -n yolo11_agri python=3.9 -y conda activate yolo11_agri # 先装 PyTorch,注意 CUDA 版本要和驱动匹配 # RTX 30 系用 cu118,RTX 40 系也建议 cu118 或更高 pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu118 # 装 Ultralytics 和农业图像处理常用库 pip install ultralytics opencv-python matplotlib scikit-image这里有个细节:ultralytics包会自动拉取 YOLOv11 的预训练权重,但国内网络环境下可能会卡在下载那一步。我一般会先手动下载yolo11n.pt放到项目根目录,然后在训练脚本里指定本地路径。
数据集目录结构文档里给了标准模板,我补充一个实际用下来更顺手的版本:
agri_dataset/ ├── images/ │ ├── train/ # 训练集图像,建议 70% │ ├── val/ # 验证集,15% │ └── test/ # 测试集,15% ├── labels/ │ ├── train/ # YOLO 格式标注文件 │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件data.yaml的内容需要根据你的类别数调整:
# data.yaml - 农作物叶片病虫害数据集配置 path: ./agri_dataset # 数据集根目录 train: images/train # 训练集路径 val: images/val # 验证集路径 test: images/test # 测试集路径 # 类别定义:叶片整体 + 具体病害类型 names: 0: leaf # 叶片区域 1: early_blight # 早疫病 2: late_blight # 晚疫病 3: bacterial_spot # 细菌性斑点病 4: healthy # 健康叶片类别设计有个坑:文档里把叶片分割和病虫害识别分成两个任务,但实际标注时如果叶片和病斑分开标,训练出来的模型会倾向于把病斑从叶片里“抠”出来,而不是在叶片上定位病斑。我的做法是把叶片整体作为一个类别,病斑作为独立类别,两者在标注时允许重叠。这样模型学到的关系是“病斑出现在叶片上”,而不是“病斑替代了叶片”。
3. 数据标注与增强:把田间照片变成可训练样本
3.1 标注规范:叶片边界和病斑区域的界定
文档第五章讲了数据收集和标注,但没展开标注规范的具体细节。我按自己标了 8000 张图的经历补几条硬规则。
叶片边界标注:如果叶片边缘被遮挡超过 30%,这张图直接弃用。YOLOv11 的边界框回归对遮挡目标很敏感,标了反而拉低整体精度。如果遮挡在 10% 到 30% 之间,边界框画到可见边缘为止,不要脑补被遮挡的部分。
病斑标注:病斑和健康组织的边界往往模糊,尤其是早期病斑。我的标准是——颜色或纹理与周围健康组织有明显差异的区域才标。具体来说,褐色、黑色、黄色晕圈都算,但单纯的叶脉阴影不算。标注框要包住整个病斑区域,包括晕圈部分。
标注工具文档推荐 LabelImg,我用下来觉得labelme更顺手,因为它支持多边形标注,后期如果想转成分割掩码也方便。安装和启动:
# 安装 labelme pip install labelme # 启动,指定图像目录和输出目录 labelme ./agri_dataset/images/train --output ./agri_dataset/labels/train --autosave标注完成后,labelme 生成的是 JSON 格式,需要转成 YOLO 的 txt 格式。下面这个脚本我用了很多次,处理 5000 张图大概 2 分钟:
import json import os from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_map): """ 将 labelme 的 JSON 标注转换为 YOLO txt 格式 json_dir: JSON 文件目录 output_dir: 输出 txt 目录 class_map: 类别名称到 ID 的映射,如 {'leaf': 0, 'early_blight': 1} """ os.makedirs(output_dir, exist_ok=True) for json_file in Path(json_dir).glob('*.json'): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) # 图像宽高,用于归一化坐标 img_w = data['imageWidth'] img_h = data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue # 跳过未定义的类别 # labelme 给的是多边形点,取外接矩形 points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # YOLO 格式:class_id center_x center_y width height(全部归一化) center_x = (x_min + x_max) / 2.0 / img_w center_y = (y_min + y_max) / 2.0 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{class_map[label]} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}") # 写入同名 txt 文件 txt_path = Path(output_dir) / (json_file.stem + '.txt') with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 使用示例 class_map = {'leaf': 0, 'early_blight': 1, 'late_blight': 2, 'bacterial_spot': 3, 'healthy': 4} labelme_to_yolo('./labels_json', './labels_yolo', class_map)这段代码的关键在坐标归一化。YOLO 不认绝对像素坐标,所有值都要除以图像宽高。另外注意class_map的顺序要和data.yaml里的names完全一致,否则训练时类别会错位。
3.2 数据增强:针对农业场景的参数调整
文档 5.3.2 节提了数据增强,但用的是通用配置。农业图像有特殊性——叶片颜色是重要特征,过度调整色相会破坏病斑的视觉特征。下面是我在train.py里实际用的增强参数:
from ultralytics import YOLO # 加载预训练模型 model = YOLO('yolo11n.pt') # 训练配置 results = model.train( data='data.yaml', epochs=150, # 农业数据集通常需要 100-200 轮 imgsz=640, # 输入尺寸,小病斑多的话可以升到 1280 batch=16, # 根据显存调整,8GB 显存用 16,12GB 用 32 device=0, # GPU 编号 # 数据增强参数 hsv_h=0.015, # 色相调整幅度,农业场景要小,默认 0.015 刚好 hsv_s=0.5, # 饱和度调整,病斑颜色敏感,不要超过 0.5 hsv_v=0.3, # 亮度调整,模拟不同光照 degrees=15.0, # 旋转角度,田间拍摄角度多变 translate=0.1, # 平移 scale=0.3, # 缩放,模拟不同拍摄距离 fliplr=0.5, # 水平翻转,叶片左右对称,安全 flipud=0.0, # 垂直翻转关掉,叶片上下颠倒不自然 mosaic=0.8, # Mosaic 增强,提升小目标检测 mixup=0.1, # Mixup 增强,防止过拟合 # 优化器配置 optimizer='AdamW', # AdamW 比 SGD 收敛快 lr0=0.001, # 初始学习率 lrf=0.01, # 最终学习率因子 warmup_epochs=3, # 预热轮数 patience=30, # 30 轮无提升就早停 # 保存配置 project='runs/agri', name='yolo11n_leaf', save=True, save_period=10 # 每 10 轮存一次检查点 )重点说三个参数。hsv_h控制色相偏移,默认 0.015 对应约 5.4 度色相旋转。病斑的褐色和健康叶片的绿色在色相环上距离不远,调大了会让模型分不清。flipud一定要关,自然界没有上下颠倒的叶片,开了反而引入噪声。mosaic设 0.8 而不是默认的 1.0,是因为农业图像背景本来就杂,四张图拼一起容易让模型学到错误的上下文关系。
4. 训练过程排查:loss 不降、mAP 震荡、显存溢出怎么破
4.1 从训练日志判断模型状态
YOLOv11 训练时会输出三类 loss:box_loss(边界框回归)、cls_loss(分类)、dfl_loss(分布焦点损失)。正常训练时三个 loss 都应该稳步下降。如果出现异常,对照下面这张表排查:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| box_loss 不降 | 标注框质量差 | 用labelimg抽查 50 张标注,看框是否贴合 |
| cls_loss 震荡 | 类别不平衡 | 统计各类别样本数,少的类补数据或调cls权重 |
| dfl_loss 突然飙升 | 学习率过大 | 把lr0从 0.001 降到 0.0005 |
| mAP 先升后降 | 过拟合 | 看验证集 loss,如果验证 loss 上升就是过拟合,加 dropout 或减 epochs |
| 显存溢出 | batch 太大或 imgsz 太高 | 降 batch 到 8,或 imgsz 从 1280 降到 640 |
我遇到最多的是cls_loss震荡。农业数据集里健康叶片和病叶的比例经常是 10:1,模型会倾向于把所有样本预测成健康。解决办法是在data.yaml里给每个类别加权重,或者在训练时用cls=0.8调高分类损失的系数。
4.2 验证集评估:mAP 之外还要看什么
文档 6.4 节讲了评估指标,但只提了 mAP。实际调优时我还会看三个指标:
Precision-Recall 曲线:如果曲线在低召回区就掉下来,说明模型对某些病斑类型不敏感。这时候要回去看那类病斑的标注是不是有问题。
混淆矩阵:YOLOv11 训练完会自动生成confusion_matrix.png。重点看健康叶片被误判成病叶的比例,这个指标在农业应用里比 mAP 更重要——把健康叶子当病叶打药,成本比漏检还高。
小目标检测率:如果病斑面积小于 32x32 像素,单独统计这类目标的召回率。YOLOv11 对小目标做了优化,但农业图像里病斑可能只占十几个像素,需要额外关注。
下面这段代码用来分析验证集结果,输出每个类别的 AP 和小目标召回率:
from ultralytics import YOLO import numpy as np # 加载训练好的模型 model = YOLO('runs/agri/yolo11n_leaf/weights/best.pt') # 在验证集上评估 metrics = model.val(data='data.yaml', split='val', imgsz=640) # 打印每个类别的 AP print("各类别 AP@0.5:") for i, name in enumerate(metrics.names.values()): print(f" {name}: {metrics.box.ap50[i]:.4f}") # 分析小目标:统计验证集中小尺寸标注的比例 import glob small_count = 0 total_count = 0 for label_file in glob.glob('agri_dataset/labels/val/*.txt'): with open(label_file) as f: for line in f: parts = line.strip().split() w, h = float(parts[3]), float(parts[4]) # 归一化面积小于 0.01 视为小目标 if w * h < 0.01: small_count += 1 total_count += 1 print(f"\n小目标占比: {small_count}/{total_count} = {small_count/total_count*100:.1f}%")如果小目标占比超过 30% 但召回率低于 0.6,建议把imgsz提到 1280 重新训练。代价是显存占用翻倍,训练时间增加约 2.5 倍。
4.3 避坑:农业检测里最容易翻车的五个点
现象一:模型在验证集上 mAP 0.9,部署到田间掉到 0.4。原因:训练集和测试集的光照条件差异太大。文档里收集数据时提了不同时间段拍摄,但很多人只拍晴天上午的图。 解决:训练时强制加亮度增强,hsv_v设到 0.4,同时在验证集里混入阴天、傍晚的图。如果已经训练完了,用测试时增强(TTA)补救,推理时把图旋转几个角度分别预测再融合。
现象二:病斑检测框重叠严重,一个病斑出好几个框。原因:NMS 的 IoU 阈值设太高。YOLOv11 默认iou=0.7,病斑密集时框会叠在一起。 解决:推理时把iou降到 0.5,或者用agnostic_nms=True让不同类别的框也参与 NMS。
现象三:训练到一半 loss 变成 NaN。原因:学习率太大或者数据里有脏标注(坐标超出 0-1 范围)。 解决:先检查标注文件,用脚本扫一遍有没有坐标大于 1 或小于 0 的行。确认标注没问题就把lr0降到 0.0001,加warmup_epochs=5。
现象四:叶片分割边缘锯齿严重,病斑定位不准。原因:输入尺寸太小,叶片边缘的细节在降采样时丢了。 解决:把imgsz从 640 提到 1024 或 1280。如果显存不够,用rect=True做矩形推理,减少 padding 浪费。
现象五:模型把土壤上的褐色斑块误判成病斑。原因:负样本不够。训练集里全是叶片特写,模型没见过土壤背景。 解决:收集一批纯土壤、杂草、滴灌带的图,作为背景类加入训练。不需要标注,YOLOv11 支持无标注的负样本图像,放在images/train里但不给对应的labels文件即可。
5. 从训练到部署:模型导出与推理加速的实操细节
5.1 导出 ONNX 和 TensorRT 的取舍
训练完的.pt文件在服务器上跑没问题,但要部署到田间设备(比如 Jetson Nano 或树莓派)就得转格式。文档第十章提了部署,但没给具体导出参数。我按自己的部署经验补上。
ONNX 是通用格式,兼容性好,但推理速度一般。TensorRT 是 NVIDIA 的加速引擎,速度快 2-3 倍,但只能在 NVIDIA 设备上跑。导出命令:
from ultralytics import YOLO model = YOLO('runs/agri/yolo11n_leaf/weights/best.pt') # 导出 ONNX,动态 batch 方便服务端并发 model.export( format='onnx', imgsz=640, dynamic=True, # 动态 batch 和尺寸 simplify=True, # 简化计算图 opset=12 # ONNX 算子集版本,12 兼容性最好 ) # 导出 TensorRT,需要先装 tensorrt 和 pycuda model.export( format='engine', imgsz=640, half=True, # FP16 精度,速度翻倍,精度损失很小 device=0, workspace=4 # TensorRT 工作空间,单位 GB )导出 TensorRT 时有个坑:half=True在 RTX 30/40 系上没问题,但在一些老卡(比如 Tesla P4)上会出精度问题。如果发现导出后 mAP 掉了超过 2 个百分点,把half关掉用 FP32。
5.2 推理脚本:批量处理和结果保存
文档里没给完整的推理脚本,我补一个实际在用的。这个脚本支持批量处理文件夹里的图,保存带标注的结果图,同时输出 JSON 格式的检测结果方便后续对接业务系统:
import cv2 import json import os from pathlib import Path from ultralytics import YOLO def batch_inference(model_path, input_dir, output_dir, conf_thres=0.25, iou_thres=0.5): """ 批量推理农作物叶片图像 model_path: 模型路径(.pt 或 .engine) input_dir: 输入图像目录 output_dir: 输出目录,保存标注图和 JSON 结果 conf_thres: 置信度阈值,低于此值的检测框丢弃 iou_thres: NMS 的 IoU 阈值 """ model = YOLO(model_path) os.makedirs(output_dir, exist_ok=True) os.makedirs(os.path.join(output_dir, 'json'), exist_ok=True) image_files = list(Path(input_dir).glob('*.jpg')) + list(Path(input_dir).glob('*.png')) for img_path in image_files: # 推理 results = model.predict( source=str(img_path), conf=conf_thres, iou=iou_thres, imgsz=640, verbose=False ) result = results[0] # 保存标注图 annotated = result.plot() # 自动画框和类别标签 out_img_path = os.path.join(output_dir, img_path.name) cv2.imwrite(out_img_path, annotated) # 保存 JSON 结果 detections = [] for box in result.boxes: detections.append({ 'class_id': int(box.cls[0]), 'class_name': result.names[int(box.cls[0])], 'confidence': float(box.conf[0]), 'bbox': [float(x) for x in box.xyxy[0]] # x1, y1, x2, y2 }) json_path = os.path.join(output_dir, 'json', img_path.stem + '.json') with open(json_path, 'w', encoding='utf-8') as f: json.dump({ 'image': img_path.name, 'detections': detections, 'count': len(detections) }, f, ensure_ascii=False, indent=2) print(f"处理完成,共 {len(image_files)} 张图,结果保存在 {output_dir}") # 使用示例 batch_inference( model_path='runs/agri/yolo11n_leaf/weights/best.pt', input_dir='./test_images', output_dir='./inference_results', conf_thres=0.3, # 农业场景宁可漏检不要误检,阈值调高 iou_thres=0.5 # 病斑密集时降低 IoU 阈值 )conf_thres设 0.3 而不是默认的 0.25,是因为农业场景下误检的代价高——把健康叶片判成病叶会导致不必要的施药。如果漏检率太高,再降到 0.2 试试。
5.3 一个提升小病斑召回率的技巧
YOLOv11 对小目标做了优化,但农业图像里病斑可能只有十几个像素。我试过一个简单有效的办法:推理时把输入尺寸从 640 提到 1280,同时用滑动窗口切图。具体做法是把一张 4000x3000 的原图切成 6 块 1280x1280 的子图,分别推理后再把结果映射回原图坐标。这样小病斑在子图里占的像素比例更大,更容易被检测到。
代价是推理时间变成原来的 6 倍。如果对实时性要求不高(比如无人机巡检后离线处理),这个方案能把小病斑召回率从 0.55 提到 0.78。如果要求实时,还是用 640 输入加 TensorRT 加速,牺牲一点小目标精度换速度。
从那以后我每次训练完模型,都会先拿 20 张田间实拍图跑一遍推理,肉眼核对检测结果,再决定要不要调阈值或重新训练。这个习惯帮我省了很多次“验证集 mAP 很高但实际不能用”的返工。希望这份拆解能帮到你,少走一些我踩过的弯路。
本文还有配套的精品资源,点击获取