ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用YOLOv11识别作物生长阶段,驱动精准施肥决策

2026/10/5 14:55:31 拓冰建站 浏览量
用YOLOv11识别作物生长阶段,驱动精准施肥决策 简介这是一份三十七页的PDF实践文档围绕智慧农业场景下YOLOv11作物生长阶段识别与精准施肥决策展开适合智慧农业从业者、农业信息化人员以及目标检测方向学习者作为系统参考。文档共三十七页压缩包内仅含一个PDF文件大小约二点三兆字节排版完整支持目录章节跳转与阅读器大纲快速定位。目前已有五十四人学习下载可用于快速建立农业检测项目的整体技术框架。内容按十个章节循序渐进从YOLO系列算法演进、智慧农业应用背景到作物数据集构建、图像预处理、模型训练与优化、精准施肥决策算法设计均有详细展开后半部分还覆盖了系统集成开发、实践案例分析和未来挑战逻辑完整。对需要落地YOLOv11农业检测方案或了解精准施肥决策流程的读者这份文档提供了从数据处理到应用部署的完整参考路径便于按章节查阅。1. 智慧农业里的 YOLOv11作物生长阶段识别到底解决什么问题作物不会说话但它的长相一直在“汇报”自己的状态——是刚破土的幼苗期还是进入快速生长的旺长期或是已经开始抽穗、等待收获。这些生长阶段直接决定了施肥的种类和用量苗期需要氮肥促根促叶花芽分化期得补磷钾坐果之后氮肥一多反而贪青晚熟。传统做法靠人下地巡田用眼睛看、凭经验判断一片几百亩的农场走一圈下来大半天过去而且不同技术员的判断口径还不一样。这就是智慧农业项目里最常被提起的真实痛点不是没有数据而是没有一套能把“作物长到哪一步了”这件事自动、量化、持续记录下来的视觉方案。YOLOv11 是 Ultralytics 继 YOLOv8、YOLOv9、YOLOv10 之后推出的目标检测模型在保持高精度的同时进一步优化了推理速度和部署友好性。把 YOLOv11 放到农田场景里做作物生长阶段识别本质上就是训练一个能区分“苗期—拔节期—抽穗期”等多个类别的检测模型然后用识别结果去驱动一套施肥决策规则。这个方向吸引人的地方在于硬件成本极低一台普通工控机甚至能跑边缘部署数据采集靠无人机或固定摄像头就能完成而决策逻辑也不复杂——检测到的类别和置信度作为输入查一张施肥策略表就能输出每亩用量建议。本文就围绕这条完整链路讲清楚数据怎么准备、模型怎么训、推理结果怎么落到施肥决策上以及哪些坑是跑农田项目时一定会遇到的。2. 为什么选 YOLOv11 做作物阶段识别检测精度之外还有三个理由2.1 作物阶段识别本质上是细粒度分类问题YOLOv11 的分类头更实用作物生长阶段识别从算法角度看既可以是整图分类也可以是目标检测。整图分类的做法是把一张农田照片整体打上一个标签比如“这是拔节期”问题在于农田照片里往往同时存在多个生长阶段的地块边界还不整齐整图打标既浪费标注人力又让模型学不到空间位置信息。实际上作物是成行种植的一行里面相邻几株的发育进度几乎一致但不同行之间可能有差异——追肥早晚、灌溉不均都会造成这种差异。用检测框把每个小区、每一行甚至每一株框出来然后对框内作物判断阶段这才是贴合田间实况的做法。YOLOv11 在模型结构上延续了 YOLOv8 的 C2f 特征提取模块并引入了更精细的注意力机制来强化小目标特征。作物的阶段判别特征非常细微——比如玉米的拔节期和喇叭口期区别在于叶片的卷曲程度和叶耳的位置这种差异在特征图上可能只占几个像素。Ultralytics 官方公布的 COCO 精度数据里YOLOv11 的 mAP 比同体积的 YOLOv8 提升了约 2 到 3 个百分点其中一部分增益就来自对细节特征更敏感的特征融合方式。对作物阶段识别这种“差一点就差一个类别”的细粒度任务来说这种特性比单纯刷检测框的通用精度更重要。2.2 轻量级版本能在 Jetson 和树莓派上实时跑边缘部署才符合农田场景农田场景的通信条件普遍不可靠几百亩的种植区里架 WiFi 不现实4G 信号也经常只有两三格。如果把每张图片都回传服务器识别一来一回延迟好几秒无人机巡航拍一亩地几百张照片更是能把流量直接跑爆。所以智慧农业项目里的目标检测模型必须能在靠近摄像头的边缘设备上完成推理只把结果类别、坐标、置信度回传压缩数据量。YOLOv11 提供了 n / s / m / l / x 五个尺度的版本其中 n 版在 COCO 上的 mAP 约 39.5%模型体积只有约 5.4MB在 Jetson Nano 上用 TensorRT 加速后推理一张 640×640 的图片大约只需 15 到 25 毫秒。这个性能水平意味着一个边缘盒子可以同时处理四路摄像头的实时视频流每秒钟完成约 10 帧以上的识别完全跟得上慢速巡田或固定点位定时拍摄的节奏。相比之下基于 Transformer 的 DETR 系列检测器虽然精度上限更高但参数量动辄三四千万在边缘设备上很难达到实时的处理帧率。2.3 生态成熟度决定了项目能走多远数据格式、训练脚本、部署工具链全是现成的这一点是农业项目选型时最容易低估的因素。农业场景的项目往往不是一次性交付而是要持续迭代——今年种玉米、明年改种大豆模型就要重新训同一个农场的大棚和露天田光照条件不同就要做域适应。如果框架生态不健全每次迭代都意味着从数据转换开始重写一遍代码项目很容易烂尾。YOLOv11 基于 Ultralytics 框架训练一条命令、导出 ONNX/TensorRT 也是一条命令数据标注用 LabelImg 或 X-AnyLabeling 输出 YOLO 格式就能直接开训。还有一个实际好处是社区积累。农田环境里各种极端情况——强逆光、叶片重叠、泥土飞溅遮挡——几乎都能在开源社区找到类似的踩坑案例和针对性数据增强方案。比如有人发现 YOLO 系列模型在雨天泥土飞溅到叶片上的场景里误检率明显上升解决办法是加入随机遮挡的数据增强这些经验不用自己从头摸索。做项目选型时我一般会看一眼目标检测框架的 GitHub issue 活跃度Ultralytics 的 issue 响应速度在同类项目里属于第一梯队这保证了农忙季节出问题时不会卡在等官方回复上。3. 先解决数据问题作物生长阶段数据集的采集规范与标注策略3.1 阶段类别怎么定义用农业技术员的语言别用算法思维硬分数据集的质量直接决定了模型在田里的表现上限。很多算法工程师拿到作物生长阶段这个任务第一反应是按生长天数来分段——比如出苗后 0 到 20 天是苗期、21 到 40 天是拔节期。这个思路在实验室里说得通一落地就出问题同一批种子因播种深度不同出苗时间能差 3 到 5 天同一块地里低洼处的积水和高地段的干旱能让相邻两行作物的发育差出一个完整的阶段。所以阶段标签必须由农业技术员根据作物形态来定算法工程师只负责把类别定义固化下来。我建议的做法是在项目启动时请农技员到现场做一次标注规范培训把每个阶段的形态特征写在标注文档里配合示例照片。以玉米为例把生长阶段分成 5 类苗期3 到 5 片展开叶、拔节期茎节开始伸长可摸到节、喇叭口期顶部叶片卷成喇叭状、抽雄期雄穗从顶部抽出、吐丝期花丝从苞叶伸出。注意这 5 类不是严格按时间先后互斥的——一块田里可能同时存在抽雄期和吐丝期的植株所以检测模型天然比整图分类更合适因为每个框独立判断类别框之间的类别不需要一致。# stage_label_check.py # 标注规范性检查统计每张图片的类别分布和框尺寸辅助发现标签噪声 import os from collections import Counter label_dir datasets/corn/labels/train stage_names {0: seedling, 1: jointing, 2: trumpet, 3: tasseling, 4: silking} size_counter Counter() stage_counter Counter() for file in os.listdir(label_dir): if not file.endswith(.txt): continue with open(os.path.join(label_dir, file), r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f[格式错误] {file}: {line.strip()}) continue cls_id int(parts[0]) w float(parts[3]) # bbox 宽度归一化 h float(parts[4]) # bbox 高度归一化 stage_counter[cls_id] 1 size_counter[small] 1 if w * h 0.005 else 0 size_counter[normal] 1 if w * h 0.005 else 0 print(各类别标注数量) for stage_id, name in stage_names.items(): print(f {name}: {stage_counter[stage_id]}) print(f小目标占比框面积0.5%图像面积: {size_counter[small]/(size_counter[small]size_counter[normal]):.1%})这段脚本在标注完成后跑一遍能从两个维度发现数据集的核心问题——类别不均衡和小目标占比异常。如果某类别的标注数量只有其他类别的十分之一训练出来的模型在那类上基本就是盲猜需要补采数据而不是硬调参。小目标占比过高则暗示采集距离太远框面积太小导致阶段特征在模型特征图上只剩一两个像素这时候应该调整拍摄高度或改用更高分辨率输入。3.2 数据采集的三个物理约束光照窗口、拍摄角度、图像分辨率作物阶段识别的训练数据采集有着明显的农业行业特殊性。首先拍摄光照窗口要统一。大田作物建议选在上午 9 点到 11 点、下午 2 点到 4 点两个窗口这两个时段太阳高度角适中叶片反光不强烈色彩还原度最好。中午顶光拍摄会导致叶片高光溢出暗部细节完全丢失模型学到的是打光模式而不是作物特征傍晚拍的照片整体偏红训练时模型会把色偏当成类别特征部署到正常光照下精度立刻跳水。拍摄角度方面固定点位摄像头建议以 45 度俯角拍摄行间这个角度能看到完整的植株形态——茎秆、叶片伸展方向和顶部结构都能分辨。正上方俯拍虽然便于统一尺度但叶片互相遮挡几个关键判别部位玉米的雄穗、小麦的旗叶会被上层叶片盖住。无人机采集则建议飞行高度保持在 3 到 5 米太高了框里包含太多株作物阶段不统一太低了单张照片覆盖面积小采集效率太低。分辨率约束说到底是目标尺寸的问题。一个 3 到 5 厘米的玉米雄穗在 4K 摄像头 3 米距离拍摄时大约占 80×80 像素缩放到 640×640 之后是 20×20 像素左右对 YOLOv11 来说属于小目标。要保证缩放后关键特征花丝的伸出长度、叶片的卷曲程度至少占 10×10 像素原始图像上目标尺寸不应该低于 60×60 像素。这个比例可以通过简单估算来验证先确定拍摄距离和相机焦距算出视野范围再用视野宽除以图像分辨率得到每像素对应的实际尺寸。3.3 标注策略的实操细节遮挡边界框、多类共存、置信度筛选标注环节最常引起模型精度波动的三个细节分别是对遮挡目标的处理、多阶段共存目标的类别分配、以及对低置信度标注的筛选。作物场景里叶片互相遮挡极其常见一个植株的苗期特征可能被前面一株的叶片挡住一半。标注规范建议遮挡面积小于 30% 的目标正常标注框定可见部分即可遮挡超过 50% 的弱化处理标注时把框缩小到可见区域完全看不到判别特征的直接不标。不要试图按照“经验”去补全被遮挡的部位模型会学到错误的形状先验。同一株作物上存在多个阶段特征时比如下部叶片还在展开、顶部已开始抽雄类别取更晚的阶段。规则是固定的以生殖器官雄穗、花丝、果穗的出现为最高优先级其次是营养器官的特殊形态喇叭口、拔节最后才是叶片数量。这样保证了标签判断口径统一。低置信度标注则指那种看不清、需要猜才能确定类别的目标——远距离的小目标、运动模糊、极端逆光下的目标——这类标注应该直接删除而不是硬着头发打一个标签。模型在模糊样本上学的不是特征而是噪声你会看到验证集上准确率不低但实际部署一测就翻车。# 数据划分命令按地块划分而不是随机划分防止同地块图片进训练集和验证集 python -c import os, random, shutil # images 目录按地块子目录组织field1/, field2/, field3/ random.seed(42) fields os.listdir(datasets/corn/images) train_fields random.sample(fields, int(len(fields) * 0.7)) val_fields [f for f in fields if f not in train_fields] for split, split_fields in [(train, train_fields), (val, val_fields)]: os.makedirs(fdatasets/corn/{split}/images, exist_okTrue) os.makedirs(fdatasets/corn/{split}/labels, exist_okTrue) for f in split_fields: for img in os.listdir(fdatasets/corn/images/{f}): base os.path.splitext(img)[0] shutil.copy(fdatasets/corn/images/{f}/{img}, fdatasets/corn/{split}/images/{img}) txt fdatasets/corn/labels/{f}/{base}.txt if os.path.exists(txt): shutil.copy(txt, fdatasets/corn/{split}/labels/{base}.txt) print(ftrain fields: {len(train_fields)}, val fields: {len(val_fields)}) 这个数据划分逻辑很多项目都会踩坑——直接用随机划分会让同一个地块、几乎同一时刻拍摄的连续照片分别落到训练集和验证集里模型在验证集上看到的其实就是训练集的“微表情”验证精度虚高 5 到 10 个百分点是常态。按地块划分后验证集里完全是模型没见过的田块评估的是真正的泛化能力。提示按地块划分的代价是训练集数量可能明显减少。如果地块数量少少于 10 块建议改用按拍摄时间窗口划分——把不同天拍摄的照片拆开也能起到类似效果。4. 用 YOLOv11 训练作物阶段识别模型环境、参数与训练策略4.1 Ultralytics 环境配置0 基础也能跑通的最小命令从零开始配置 YOLOv11 训练环境核心依赖就两样PyTorch 和 Ultralytics 包。推荐直接用 conda 建独立环境避免和系统 Python 环境互相污染。CUDA 版本方面PyTorch 2.x 搭配 CUDA 11.8 或 12.1 都能顺畅运行重点是要先确认显卡驱动支持的 CUDA 版本用nvidia-smi查看驱动对应的最高 CUDA 版本号然后安装对应的 PyTorch 版本。# 创建 Python 3.10 环境并安装基础依赖 conda create -n yolo11 python3.10 -y conda activate yolo11 # 安装 PyTorch以 CUDA 12.1 为例其他版本去 pytorch.org 查对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 安装 Ultralytics 和推理依赖 pip install ultralytics opencv-python # 验证安装是否成功能打印版本号说明环境正常 python -c import ultralytics; print(ultralytics.__version__)环境配置里最常见的失败点是 PyTorch 装成了 CPU 版。很多教程让人直接pip install torch这个命令在多数情况下装的确实是 CPU 版训练速度慢 20 倍以上。判断方法很简单进入 Python 后执行import torch; print(torch.cuda.is_available())输出 False 就说明装错了需要卸载后用带 index-url 的安装命令重装。另一个高频问题就是 conda 环境装了新包后终端里命令找不到这是因为 conda 环境的 bin 目录没有加入 PATH重启终端或重新conda activate一般能解决。4.2 数据集 YAML 与模型配置五类目标的参数文件写法Ultralytics 框架的数据配置是通过一个 YAML 文件描述的路径、类别名、类别数量三项缺一不可。路径建议写绝对路径相对路径在不同机器上切换时很容易因为工作目录不同而报找不到文件。类别名要用英文小写加下划线中文字符在标签文件中会导致编码错误某些工具链在导出 ONNX 模型时也会因为非 ASCII 字符报错。# corn_stages.yaml # 作物生长阶段数据集配置 path: /home/user/datasets/corn # 数据集根目录的绝对路径 train: train/images # 训练集图片目录相对 path val: val/images # 验证集图片目录相对 path names: 0: seedling # 苗期3-5片展开叶 1: jointing # 拔节期茎节可摸到 2: trumpet # 喇叭口期顶部叶卷成喇叭状 3: tasseling # 抽雄期雄穗抽出 4: silking # 吐丝期花丝伸出苞叶训练时需要注意 YAML 里的类别数量必须和标签文件里的类别 ID 对应。如果标签里出现了cls_id5而 YAML 只定义了 0 到 4训练不会直接报错但 loss 计算会乱模型训练几百轮后精度始终上不去。排查方法是用前面写的数据检查脚本跑一遍所有标签文件确认类别 ID 都在range(len(names))内。数据类型上images 目录里放.jpg或.png都行Ultralytics 会在首次训练时自动生成缓存文件加速后续 epoch 的读取。4.3 训练命令与核心参数调优小目标场景的必调项基础训练命令并不复杂一个脚本就能启动。但这套默认参数对大目标通用检测效果好对作物阶段识别这种细粒度、多小目标的场景必须做针对性调整。# 用 YOLOv11s 作为基础模型训练作物阶段识别 yolo detect train \ modelyolo11s.pt \ datacorn_stages.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.005 \ lrf0.01 \ patience30 \ augmentTrue \ mosaic0.5 \ degrees5 \ translate0.1 \ scale0.3 \ fliplr0.5 \ projectruns/train \ namecorn_exp1几个关键参数的调整逻辑和默认值差异很大按需说明如下。imgsz640是精度和速度的平衡点作物阶段判别特征细小如果显存充足比如 12GB 以上可以试imgsz800小目标检测精度通常能提升 2 到 3 个百分点但训练时间相应增加约 50%。mosaic0.5是我从默认值 1.0 降下来的——Mosaic 增强可以大幅提升模型对遮挡和小目标的鲁棒性但对作物场景来说如果把四个不同地块、不同光照的图片拼在一起模型很容易学到“地块色调”这种伪特征所以把概率折半让模型更多看到自然场景的完整图像。degrees5限制旋转增强的角度范围农作物是竖着长的旋转 90 度的样本违背物理先验学不到有价值的信息反而可能带来噪声。scale0.3控制尺度缩放范围默认值 0.5 意味着训练样本里的目标可以被缩放到一半大小过度缩小会让本来就小的目标完全失真。batch16的设定取决于 GPU 显存大小以 8GB 显存为例YOLOv11s 640 输入 batch 16 大约是勉强放得下的水平。显存不足时会自动降低 batch但频繁的显存等待会影响训练效率。lr00.005是相对默认值 0.01 做的下调——数据集只有几千张时学习率太大容易在训练初期就震荡细粒度特征的收敛不稳定小数据集保守一点更稳。patience30是早停等待轮数验证集 mAP 连续 30 轮没有提升就自动停止训练避免无效的长时间空转。4.4 训练过程中的监控信号loss 曲线和验证集 mAP 怎么读每一轮训练结束后Ultralytics 会输出box_loss、cls_loss、dfl_loss和验证集的mAP50、mAP50-95等指标。新手最容易犯的错就是盯着 mAP 看mAP 一旦涨得慢就急着调参实际上训练初期的 mAP 波动本来就大关键是看三类 loss 的下降趋势。cls_loss是分类损失直接对应阶段识别的准确度box_loss是框回归损失对应定位精度。如果cls_loss稳步下降而box_loss停滞说明模型能认出生长阶段但框不准——常见原因是标注框边缘不齐需要回看标注质量而不是调参。另一个重要信号是训练集 loss 和验证集 loss 的差距。训练集 loss 持续下降、验证集 loss 在某个点开始回升这是过拟合的典型特征说明模型开始死记训练集里的地块特征比如某块地的土壤颜色。处理办法依次是增加数据增强概率、降低模型尺度从 l 降到 m 或 s、提前早停阈值。Ultralytics 框架下优先试mosaic0.7mixup0.2的增强组合这个组合通常能延后过拟合出现 20 到 40 轮如果还不够就降模型尺度。5. 推理部署与施肥决策从检测框到每亩用量的完整链路5.1 模型导出与推理脚本保存检测结果到本地的最简实现训练完成后模型文件是一个.pt权重里面包含训练时的配置信息直接用于部署不是不行但体积大、推理速度也不是最优。常规做法是把它导出为 ONNX 格式再用 ONNX Runtime 推理这样部署端不需要安装 PyTorch依赖管理干净很多对边缘设备也更友好。# 导出 ONNX 格式Opset 版本选 12兼容大部分边缘设备的 CPU 推理 yolo export modelruns/train/corn_exp1/weights/best.pt formatonnx opset12导出后的best.onnx可以直接用 ONNX Runtime 加载推理。下面的推理脚本会输出每张图片的检测结果把框、类别、置信度保存成 JSON方便后续决策模块读取。# inference.py # 用 ONNX Runtime 做批量推理输出带框标注的图片和结构化 JSON import cv2 import json import numpy as np import onnxruntime as ort # 类别名称与训练 YAML 保持一致 CLASS_NAMES [seedling, jointing, trumpet, tasseling, silking] # 加载 ONNX 模型并读取输入输出信息 session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape # [1, 3, 640, 640] # 后处理NMS 去掉重叠框只保留置信度最高的框 def postprocess(predictions, conf_thres0.35, iou_thres0.5): # predictions 形状: [1, 84, 8400] - 转置为 [8400, 84] preds predictions[0].transpose(1, 0) boxes [] for pred in preds: class_scores pred[4:] # 每个类别的置信度 class_id np.argmax(class_scores) confidence class_scores[class_id] if confidence conf_thres: continue # 前 4 个值是中心点坐标和宽高还原为框坐标 cx, cy, w, h pred[:4] x1 (cx - w / 2) * input_shape[3] y1 (cy - h / 2) * input_shape[2] x2 (cx w / 2) * input_shape[3] y2 (cy h / 2) * input_shape[2] boxes.append([x1, y1, x2, y2, confidence, int(class_id)]) # 按置信度排序后用 NMS 过滤重叠框 boxes.sort(keylambda b: b[4], reverseTrue) keep [] for box in boxes: overlap False for kept in keep: if iou(box, kept) iou_thres: overlap True break if not overlap: keep.append(box) return keep def iou(a, b): # 计算两个框的交并比用于 NMS 去重 ax1, ay1, ax2, ay2 a[:4] bx1, by1, bx2, by2 b[:4] xx1 max(ax1, bx1); yy1 max(ay1, by1) xx2 min(ax2, bx2); yy2 min(ay2, by2) inter max(0, xx2-xx1) * max(0, yy2-yy1) union (ax2-ax1)*(ay2-ay1) (bx2-bx1)*(by2-by1) - inter return inter / union if union 0 else 0 results [] for img_path in [field1_001.jpg, field1_002.jpg]: img cv2.imread(img_path) img_resized cv2.resize(img, (640, 640)) blob img_resized[:, :, ::-1].transpose(2, 0, 1) / 255.0 blob np.expand_dims(blob, axis0).astype(np.float32) predictions session.run(None, {input_name: blob})[0] detections postprocess(predictions) results.append({image: img_path, detections: detections}) # 绘制检测框并保存到 out/ 目录 for x1, y1, x2, y2, conf, cls_id in detections: cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) label f{CLASS_NAMES[cls_id]} {conf:.2f} cv2.putText(img, label, (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(fout/{img_path}, img) with open(detections.json, w) as f: json.dump(results, f)脚本里 ONNX Runtime 的输入是一个形状为[1, 3, 640, 640]的 float32 张量数值范围必须是 0 到 1这一点和训练框架内部的预处理保持一致。后处理中的 NMS 逻辑虽然写了两个函数实际用的是闭包iou和keep列表核心逻辑是经典的置信度排序加交并比去重。注意把输入尺寸640和模型实际输入尺寸对齐如果导出时imgsz不是 640这里也要相应修改。5.2 精准施肥决策模块检测结果如何映射到施肥量检测模型输出的是一组带类别和置信度的目标框它本身不直接回答“施多少肥”的问题。施肥决策需要把检测结果转化成决策变量。常见做法有两层第一层是统计阶段分布对同一片地块的多张照片计算各阶段目标的占比得到“该地块当前主要处于什么阶段”的判断第二层是结合土壤养分数据和目标产量查施肥推荐表得到具体用量。阶段占比的计算有一个关键细节是简单的目标数占比还是考虑框面积加权。我的经验是目标数占比更稳定。作物个体大小差异显著——一株抽雄期的玉米占地面积是一株苗期的 5 倍以上如果按面积加权抽雄期的占比会被放大得离谱按目标数统计则更接近农技员“数一数地里有多少株到了这个阶段”的习惯。# fert_decision.py # 根据检测结果统计阶段占比输出施肥建议 def make_decision(detections_json): with open(detections_json) as f: data json.load(f) stage_count {name: 0 for name in CLASS_NAMES} for frame in data: for det in frame[detections]: stage_count[CLASS_NAMES[det[5]]] 1 total sum(stage_count.values()) if total 0: return {status: no_crop_detected, advice: 当前视野内未检测到作物请检查摄像头角度} stage_ratio {k: v / total for k, v in stage_count.items()} # 以占比最高的阶段作为该地块的决策依据 dominant_stage max(stage_ratio, keystage_ratio.get) # 施肥决策表不同阶段的氮磷钾配比建议单位kg/亩 # 数值基于常见大田作物的经验施肥量落地前需由农艺师根据当地土壤修正 fert_plan { seedling: {N: 5, P2O5: 3, K2O: 3, note: 促根壮苗氮肥为主但不宜过量}, jointing: {N: 12, P2O5: 4, K2O: 8, note: 营养生长高峰期重施氮肥}, trumpet: {N: 8, P2O5: 5, K2O: 10, note: 氮磷钾均衡防止旺长}, tasseling: {N: 2, P2O5: 6, K2O: 12, note: 以磷钾为主氮肥控制}, silking: {N: 0, P2O5: 4, K2O: 8, note: 追施钾肥促进灌浆}, } result fert_plan[dominant_stage] # 置信度加权修正当优势阶段占比不足 50% 时给出混施建议 if stage_ratio[dominant_stage] 0.5: result[note] 该地块存在明显阶段混叠建议分小区追肥 return {dominant_stage: dominant_stage, stage_ratio: stage_ratio, fert_plan: result}这段决策逻辑的关键在于它是规则驱动而不是又一个黑盒模型。用规则驱动的好处是每一条建议都能回溯——农技员能看懂为什么推荐施 12 公斤氮肥而不是面对一个神经网络输出的数字无从验证。实际落地时这张决策表里的数值一定要让当地农艺师签字确认不同作物、不同土壤肥力基础、不同目标产量下的推荐施肥量差异巨大算法能提供的只是阶段判断的准确性和可追溯性。5.3 置信度阈值与决策可靠性低于哪个阈值就该人工介入在施肥决策链路中模型输出的置信度不只是用来过滤检测框的质量指标更是决策可靠性的一道闸门。如果一张图片所有的检测框置信度都低于 0.6说明模型对当前画面很不确定——可能是罕见的极端光照、雾天或摄像头镜头被泥土遮挡。这时候如果还按照检测结果直接触发施肥建议风险极高。常见的做法是设置两级机制置信度高于 0.7 的检测结果直接进入统计模块参与决策置信度在 0.5 到 0.7 之间的结果记录到日志但不参与统计低于 0.5 的框直接丢弃。当某块地的有效检测数量低于阈值比如 5 张有效图里的不足 20 个框时系统输出“需人工复核”而不是施肥建议。这个看似保守的设计在实际项目中恰好是最省心的——施肥决策一旦出错损失的是一季产量多一次人工巡检成本远低于误判风险。6. 农忙时节最容易翻车的地方作物检测与施肥决策的 5 个避坑记录6.1 标注类别混叠同一株作物两个阶段特征同时存在有的标注员看到一株玉米下部叶片还在展开、顶部已经抽雄会纠结到底标“喇叭口期”还是“抽雄期”。如果训练集里同样的形态有时标 2、有时标 3模型学到的边界就是一团噪声推理时同一个区域反复在两个类别之间跳变。解决办法是在标注规范里明确“以更晚阶段为准”的原则并且在标注工具里给每个框增加备注字段让标注员记录“有歧义”的框训练前统一复查一遍。这类标注歧义通常集中在类别相邻的两个阶段——苗期和拔节期、喇叭口期和抽雄期建议在标注培训时把相邻类别的对比图专门列出来。6.2 光照突变导致误检率飙升项目上线第一周最容易暴露的问题就是模型在阴天和晴天的表现差异。训练数据大多采集中午晴天部署时赶上连续阴天检测框数量直接跌一半置信度全线走低。原因是作物在不同光照下的色彩表现差异极大——阴天叶片偏暗绿色、晴天偏黄绿模型如果把颜色当成了阶段判别的主要依据就会翻车。解决思路有两个第一采集数据时覆盖多种天气条件宁可单类别的样本数少一点也要保证光照多样性第二训练时加强色彩类数据增强hsv_h、hsv_s、hsv_v三个参数各调到 0.02 左右强迫模型不依赖绝对颜色。6.3 无人机航拍与固定摄像头视角不一致这个坑出现在“训练数据用无人机采集、部署时用固定低位摄像头”的配置里。无人机俯拍看到的是作物的顶部形态固定低位摄像头看到的是侧面形态同一阶段的作物在两个视角下的视觉特征完全不同——玉米的雄穗从顶部看是一个塔状结构从侧面看就是一根细枝条。直接把无人机的训练集迁移到低位摄像头推理置信度会非常难看。最稳妥的做法是部署阶段用目标设备重新采集 200 到 300 张图做微调如果时间不允许至少要做一个“视角模拟”的数据增强——把训练图随机裁剪并旋转 30 到 45 度让模型对视角变化不那么敏感。6.4 土壤背景干扰除草剂喷洒后的枯草被当成作物农田里除了目标作物还有杂草和残留的秸秆。春播前后的地表上去年的玉米秸秆碎片散落各处形状和颜色与新出苗的作物幼苗非常相似——都是绿色或黄绿色的细长条。这种情况会让苗期的误检率特别高。一条可行的对策是采集背景负样本凡是没有什么目标但容易误检的区域单独拍一批图放进去作为不包含标注的“背景图”。在 Ultralytics 里只要把背景图直接放到训练集 images 目录下且不生成对应标签文件模型就会把这些图当作全背景样本学到“这里没有目标要输出低置信度”。6.5 决策表参数与当地农艺条件脱节YOLOv11 检测到生长阶段只是决策链路的上半截下半截的施肥量如果照搬所谓“标准参数”就会出事故。不同区域的土壤类型、降水模式、目标品种的需肥规律差异极大同一份“拔节期施氮肥 12kg/亩”的推荐在东北黑土地和南方红壤上的效果完全不同。算法团队必须和技术方确认两件事当地过去三年的平均施肥方案是什么以及有没有取样检测过土壤速效氮磷钾含量。如果没有土壤数据决策模块至少要有“基于产量目标的反推”而不是直接套固定数值——这个逻辑可以和农技员一起做一次简单的计算推导确定。7. 精度验证与模型迭代用 mAP 之外的指标判断模型能不能真正落地训练完成后很多项目拿验证集 mAP 数字好看就宣告完成真正部署到农田里表现不佳。这里面的差距不在模型本身而在验证方式与真实场景的偏差。除了 mAP我认为有三个指标在智慧农业场景里更重要每类别的召回率、误检密度和决策准确率。每类别召回率反映的是“这个阶段有没有被漏掉”。施肥决策依赖的是阶段占比统计如果一个阶段只有 60% 的召回率意味着 40% 的植株被漏检或误检成了其他阶段最终算出来的优势阶段可能直接偏掉。查看每个类别的召回率是评估落地可行性的第一优先级——mAP50 到 0.85 但某个类别召回率只有 0.55这类模型在真实地块上的决策就是赌博。误检密度是每张图平均有多少个假阳性框。农田场景对误检的容忍度其实很低一个误检框就会增加一个不存在的植株进而拉偏阶段占比。如果一个模型的 mAP 很高但误检密度也高那它在农艺师眼里就是不靠谱的。我建议的验证方式是在采集的验证集之外单独找 200 张含杂草、秸秆、农机具、人影的干扰图专门测误检密度超过每图 0.3 个就要处理。决策准确率则是端到端的指标——把推理结果送入决策模块输出的施肥建议和农技员的判断做一致性比对。这个指标直接决定了业务方愿不愿意买单。实现方式是让农技员对同一批图片给出“这个地块处于什么阶段”的回答然后和系统决策结果比较。一致性达到 90% 以上说明模型真正进入了可用状态。模型迭代方向的判断也离不开指标拆解。如果只有某几个类别召回率低优先补该类别的训练数据而不是调整模型结构如果类别间混淆严重且集中在相邻阶段优先检查标注口径而不是加数据如果整体 mAP 卡住不动可以试试用 YOLOv11x 或更大的输入尺寸。最后模型文件记得按地块和季节版本管理——同一个农场在不同年份种不同品种的作物模型需要持续迭代。农田里的视觉方案最终的衡量标准只有一个——农技员认不认可系统的判断。这需要算法工程师真的去田里跑几趟感受一下强光下的人工识别难度理解叶片遮挡对农业判断的干扰才能真正把模型调成“懂农业”的状态。做这个项目的过程中我最大的教训就是不要坐在电脑前凭 mAP 数字想象模型在田里的表现。希望这篇笔记能帮你少走几段弯路让 YOLOv11 这座桥真正联通视觉识别和精准施肥之间的断点。本文还有配套的精品资源点击获取