YAOTU INSIGHTS

基于YOLO的玉米田间幼苗识别与出苗率统计实战

基于YOLO的玉米田间幼苗识别与出苗率统计实战
说实话写这篇分享之前我自己也经历了很长时间的“手工数苗”阶段。几千张玉米地照片一张一张放大看拿计数器点鼠标数完一遍眼睛都快花了而且不同人数的结果还不一样——有人数出来300株有人数出来280株。后来决定把整套流程交给AI做才有了这个基于YOLO的玉米田间幼苗识别与出苗率统计项目。今天把从数据集搭建、标注标准制定到模型训练、出苗率计算这条完整的链路整理出来权当给打算做农业视觉方向的同行留一份踩坑笔记。1. 项目立项玉米出苗率为什么值得做成一个AI数据集1.1 农业一线的真实痛点先说说这事儿为什么非做不可。玉米出苗率直接决定补种决策和最终产量评估。传统做法是农技人员到地里选几个样方蹲下来数苗、数草一个样方1米长一个地块走五六个点全靠人工。问题出在两个地方一是效率低几百亩地根本数不过来抽样密度不够统计误差大二是人工数苗的主观性太强苗期玉米和某些禾本科杂草长得非常像新手和老手数出来的结果能差20%以上。我当时接到的需求很简单能不能用无人机或者手机拍一圈地里照片回来自动告诉我这块地出苗率多少、杂草密度多高、哪些区域缺苗严重。这里面最核心的技术环节就是识别——在复杂的田间背景下把玉米幼苗和杂草准确分开并且定位每一株的位置。这个任务天然适合目标检测而不是图像分类因为我们需要的是位置数量光知道图片里有没有玉米苗远远不够。1.2 检测、分割还是分类技术路线怎么选刚立项的时候团队里也有过争论。有人提出来直接用图像分类判断这块地出苗正常还是缺苗有人说用语义分割把玉米和杂草像素级分开还有人主张用目标检测框出每株苗。我的判断很明确分类方案解决不了数量统计问题分割方案精度高但标注成本和训练成本都高检测方案是性价比最优解。出苗率本质上是一个计数任务而计数任务在目标检测框架下非常好做——每个框代表一株苗数框就行。YOLO系列在速度和精度的平衡上做得最好尤其对于无人机拍摄的整田影像单张图可能包含几百个目标YOLO的推理速度能保证批处理效率。语义分割在这种密集小目标场景下标注工作量至少是检测框的三倍以上而且分割模型对边缘杂草的处理容易产生粘连后续数目标反而麻烦。检测框虽然看起来粗糙但对计数场景足够用了。1.3 出苗率计算的业务口径这里有个特别容易踩的坑出苗率到底怎么定义。农业上正规口径是出苗株数 ÷ 实播粒数但实播粒数在无人机影像里是看不出来的不同地块播种密度也不一样。我们最后采用的是行内间距推算法先按播种机参数算出理论株距再根据检测到的苗框位置筛选处于同一垄行的苗计算连续苗之间的间距如果间距超过理论株距的1.5倍就视为缺苗。这样出苗率 正常间距段数量 ÷ 理论间距段总数。这个口径的好处是不需要额外的田间数据仅凭检测结果就能估算。当然它依赖一个前提播种是相对均匀的。实际做下来对于机械播种的田块这个方法的误差可以控制在5%以内完全满足农技决策需求。这也是整个项目里我认为最有业务价值的设计点后面在第四章会展开讲具体实现。2. 数据集搭建采集、标注与格式规范2.1 图像采集的三个关键决策数据是这类项目的地基偏偏也是最容易被低估的部分。我总结下来采集阶段有三个决策直接影响模型最终效果。第一是采集高度和分辨率。无人机飞10米高和4米高拍出来的目标尺度完全不同。10米高单株玉米可能只有二三十个像素属于典型的小目标检测难度陡增。我们的做法是双高度采集先飞低空4到6米拍苗期细节再飞10到15米拍全局覆盖模型同时见两个尺度。手头没有无人机的话用手机拍也行关键是要让目标占图比例保持在一个合理范围单株苗最好不要小于20×20像素。第二是时间窗口。玉米出苗期从第一片真叶展开到三叶期大约10到15天这期间苗体形态变化不大是最佳的采集窗口。超过五叶期之后玉米和杂草形态差异变大杂草识别虽然容易了但出苗率统计的价值反而下降了。所以数据集里的图像全部限定在这个窗口避免引入形态差异太大的样本。第三是光照与背景多样性。我第一批数据集中在晴天中午段拍的结果模型在阴天和傍晚的图片上泛化很差。后面补采了大阴天、早晚侧光、雨后土面湿润发暗这些条件下的图像。田间背景也要多样化残茬地、旋耕地、覆膜地的颜色和纹理差异很大我建议每个背景类型至少占数据总量的15%不然模型很容易过拟合到某一种土壤颜色上。2.2 YOLO格式数据到底长什么样这个坑必须讲清楚因为很多第一次接触YOLO的朋友拿到别人的数据集解压后根本看不懂。YOLO的标注格式是每张图对应一个同名txt文件放在labels目录下每一行代表一个目标框内容包括五个数值类别id、归一化后的框中心x坐标、框中心y坐标、框宽度、框高度。# 示例一个玉米苗和一个杂草的标注 0 0.5321 0.4187 0.0842 0.0931 1 0.6315 0.3924 0.0518 0.0627这里0表示玉米幼苗类别1表示杂草类别。所有坐标值都除以了图片宽高所以范围在0到1之间。实际手工标注时我们用的开源标注工具能自动导出这个格式但要注意不同工具生成的类别索引顺序可能不一样使用时务必先检查一遍类别id是否和你设置的class文件对应。目录结构上也有一套约定俗成的规范训练脚本一般会按下面这样组织datasets/ corn_emergence/ images/ train/ val/ test/ labels/ train/ val/ test/ data.yamldata.yaml用来声明类别名和路径里面写清楚nc类别数和names类别名列表。别小看这个文件路径写错了、类别名对不上训练出来的模型预测结果全是乱的而且错误很隐蔽不容易察觉。2.3 标注标准什么算一株苗什么算一株草标注标准是整个数据集质量的核心这一小节建议每个做农业检测项目的人都认真看。我们当时专门花了两天制定标注规则不是随手画的。玉米幼苗的边界定义从出苗到三叶期玉米株体呈细长条状叶片中脉明显叶面有一层很淡的白色粉末感。标注框要完整包住所有展开的叶片和茎秆宁可稍微外扩一点也不要切到叶子边缘。这里我特别强调一条铁律两株苗的叶子如果有交叉不允许只标一个框必须两个框都标出来哪怕其中一个框大部分被另一个挡住。因为后续计数要依赖框的数量你漏标一株出苗率就错了一个点。杂草类的定义相对宽松一些但也必须定义清楚。田间杂草主要分禾本科和阔叶两类形态差异大。严格来说更精细的做法是把禾本科杂草和阔叶杂草分成两个类别因为某些禾本科杂草比如马唐、牛筋草幼苗期和玉米苗的相似度极高单独成一类可以避免模型被绕晕。我们最终设定了三个类别corn_seedling、grass_weed、broadleaf_weed。加上阔叶草这一个类整个标注工作量增加了不少但换来的是模型在杂草密集地块的召回率显著提升这笔投入是值得的。标注规范里还有一个细节容易被忽略边缘目标。图片边缘被截断的玉米苗到底标不标我们的规则是只要目标至少有50%的面积在视野内就标否则不标。这样既不会浪费有效样本也不会引入大量残缺目标干扰训练。2.4 数据增强与类别均衡处理数据集总数量我们控制在3500张左右其中训练2800张、验证350张、测试350张。目标总实例数大约32000个玉米苗和杂草的比例接近1:1.2略偏向杂草这是刻意为之——早期识别系统中杂草少检一株比玉米苗少检一株的影响大得多宁可让模型对杂草更敏感。增强策略我用了在线增强而不是离线增强因为在线增强每轮epoch都会生成不同的样本相当于变相扩大了数据集。用到的增强组合包括mosaic拼接4张图拼1张、随机平移和缩放、HSV色域扰动、水平翻转。田间图像不太适合做垂直翻转因为土壤纹理和阴影方向是有物理意义的翻转之后看着别扭模型特征也容易乱。关于类别均衡如果杂草和玉米苗的比例超过1:5建议用类别权重或者复制少数类小幅样本的方式拉平。我们当时测试过不做处理的版本模型在晚期epoch几乎彻底放弃杂草类precision和recall两极分化别踩这个坑。3. YOLO模型训练环境配置与参数调优3.1 训练环境与数据组织讲训练之前先说硬件。这个规模的数据集一张中等偏上的消费级显卡完全够用显存8G以上就行。我们用的是单卡方案训练时间大约6到8小时能达到收敛。如果你只有CPU环境也不是完全不能跑但要把图像尺寸降到640batch降到4训练时间会拉到几十个小时不太现实建议还是本地或云端搞一张GPU。训练前先归一化确认数据检查。这里有一个我踩过的比较深的坑从别人那里拿来的数据或者自己用工具导出的数据偶尔会出现框坐标越界比如某个坐标是1.05训练脚本会直接报错或者静默丢弃这些样本。我们的做法是在训练前跑一遍检查脚本把所有标注文件的坐标读出来但凡有小于0或者大于1的值或者是width、height为0的异常框全部过滤掉顺便输出有问题的文件名方便追溯。3.2 关键超参数的选择逻辑超参数这块我不打算堆一堆数值而是重点说说每个参数为什么这么定。图像尺寸imgsz是我认为最重要的参数。田间幼苗目标小640的输入尺寸下低空拍摄的苗大约只有30×30像素检测器会非常吃力。我们选的是1280兼顾了精度和显存开销如果你显存不够推荐用滑动窗口切图方案把大图切成1024或640的块分别推理效果比直接把图压到640好得多。训练阶段可以先用640快速验证流程通不通正式训练再上1280。批次大小batch主要受显存限制。1280尺寸下8G显存单卡batch最多给8。batch过小会导致训练震荡如果batch只能设4建议同步调低学习率并延长热身warmup轮数。学习率初始值0.01是一个稳妥起点配合余弦退火策略。农业数据集噪声比标准数据集大学习率太高会出现一种诡异的现象loss曲线看起来在下降但验证集mAP纹丝不动甚至边训练边掉那就是模型在死记训练集上的脏标注了。训练轮数方面不要盲目追求300轮。我建议用早停机制以验证集mAP为监控指标连续20轮不提升就停。我们实际跑到第180轮左右就停了再多跑只会浪费算力。3.3 训练过程如何判断收敛训练过程中我一般同时盯三样东西训练loss、验证集mAP0.5、以及最关键的——可视化预测结果。Loss下降是必要不充分条件。见过太多人看loss降了就以为训练成功了结果模型的检测框位置全偏。真正要关注的是mAP0.5要达到0.88以上mAP0.5:0.95至少要0.6这个任务的目标比较小如果后者低于0.5说明检测框定位质量堪忧出苗率统计会受影响。可视化预测这步很多人跳过我强烈建议训练完成后随机抽30到50张验证集图片跑一次推理把结果图保存下来人工过一遍。重点看两个地方密集苗区域的框有没有重叠漏检杂草长势旺盛区域的误检率高不高。这一步能发现很多指标看不出来的问题——比如模型在特定阴影地形下出现系统性漏检mAP也许只掉了0.02但对应区域的出苗率会离谱地偏低。4. 从检测结果到出苗率统计后处理方案4.1 去重与计数模型输出的是一个个检测框但框不等于苗。一张照片里同一个苗可能被重复检测拼接的无人机大图里同一株苗可能出现在相邻图块的重叠区域。直接数框出苗率能给你数出120%所以去重是必须的。去重我用的方案是基于IoU的NMS增强版把所有框投影到同一地理坐标系无人机图块定位用简单的经纬度换算然后对任意两个同类的框计算IoU如果IoU大于0.5就认为是同一个目标保留置信度高的那个。地面拍摄的单张图处理更简单普通NMS就够。注意的一点是阈值不要设太高玉米幼苗叶片细长同一株苗如果被检测成两个框两个框的交叠区域可能只有叶片部分IoU可能才0.3左右。我们最终把同一目标的判定阈值放宽到IoU大于0.25配合中心点距离小于某像素阈值的双条件判断误合并率控制在2%以内。4.2 出苗率与杂草密度计算去重之后就到了业务核心出苗率。前面提到我们不用实播粒数做分母而是用行内株距推算法。实现流程是这样的先根据检测框的坐标分布做垄行聚类。玉米播种是成行的同一行的苗框中心点在一条直线上。我们用DBSCAN聚类算法把苗框按位置聚成若干条行带聚类特征用框中心点的x、y坐标。然后对每一行内部的苗框按种植方向排序计算相邻两株之间的距离。已知播种机设置的株距例如25厘米换算成像素后大约是某固定值设定阈值相邻间距小于1.5倍株距视为正常大于这个值视为缺苗。# 伪代码计算单行的出苗率 def emergence_rate(row_seedlings, expected_spacing_px): # 按株距方向排序 sorted_points sort_by_position(row_seedlings) gaps [] for i in range(len(sorted_points) - 1): gap distance(sorted_points[i], sorted_points[i1]) gaps.append(gap) normal_count sum(1 for g in gaps if g 1.5 * expected_spacing_px) # 分子为有效间距段分母为理论间距段总数 theoretical_segments sum(g / expected_spacing_px for g in gaps) return normal_count / theoretical_segments * 100这个公式逻辑上等价于统计实际出苗的点位占理论播种点位的比例。写得糙一点没关系核心思想是间距在合理范围内说明两株苗都出齐了间距过大说明中间缺苗。杂草密度更直接杂草框总数除以观测面积得到每平方米杂草株数。这个指标对除草剂喷施决策很关键一般认为玉米苗期每平方米超过20株杂草就构成中等以上草害威胁。我们把检测结果按地块网格化生成每个网格的杂草密度热力图农技员拿着这张图就能精准定位需要重点处理的区域。4.3 结果可视化的呈现方式做了这么久项目我的体会是结果可视化做得好不好直接影响别人愿不愿意用你的系统。农技人员不看mAP曲线他们要看得懂的地块图。我们最终输出三张图第一张是原图叠加检测框框的颜色区分玉米苗和杂草绿色框是苗红色框是草视觉上非常直观第二张是出苗率分布热力图整个地块按10米×10米网格划分每个格子的出苗率用从深红到深绿的颜色渐变表示哪块地缺苗严重一眼就能看出来第三张是杂草密度直方图按地块分区的杂草等级做了五种标注。这三张图打印出来或者直接发到农户手机上现场指导价值非常高。5. 实战排坑常见问题与解决实录5.1 光照与阴影干扰这是整个项目里我们花时间最多的一个问题。玉米苗期叶片窄长晴天上午10点到下午2点之间叶片投射的阴影和叶子本身对比强烈模型很容易把深色的阴影条带误检成幼苗而把被阴影覆盖的叶片漏掉。最初我以为是模型问题加了各种数据增强都没用。后来认真看失败案例才意识到标注阶段就把阴影带进来的苗框标得不干净模型学到的是深色长条状物体就是苗这个错误特征。解决方案分两步一是补采大量阴天和侧光条件下的图像二是在标注时严格要求框内不得包含超过10%的阴影区域否则宁可删掉这张图。中期做了一次模型预测把置信度在0.4到0.6之间的疑似苗全部导出人工复核专门清洗了一批阴影误检样本模型性能立刻上了一个台阶。5.2 密集重叠苗的漏检玉米播种密度高的时候相邻株距只有20厘米出头苗期叶片互相覆盖YOLO的常规NMS会误删那些和邻近框高度重叠的真实目标。漏检一株苗看起来是小问题但密集区域一漏就是一连串出苗率直接偏低5到8个百分点。我最后的解决方案比较朴素但有效把推理的置信度阈值从默认0.25调到0.15让模型多输出一批低置信度框然后在后处理阶段用小目标优先保留的策略——当两个框重叠时优先保留面积较小的那个因为真实的小苗框往往被大框镇压掉。配合调整NMS的IoU阈值从0.45升到0.6密集区域的召回率提升了大约9%。另一个可行的方案是把输入图切块推理滑动窗口切块后单株苗的相对尺寸变大检测难度降低代价是推理时间翻倍适合离线批量处理。5.3 标注不一致导致的模型漂移这个坑发生在多人协作标注的时候。我们当时三个标注员同时干活结果同一个场景下一个人把刚出土还没展开叶子的玉米苗标成杂草另一个人标成玉米苗还有一个人干脆漏标。模型在这种矛盾标注下训练特征完全学乱验证集上表现看起来还行一换新地块就露馅。痛定思痛我们建立了一套标注复核机制每张图至少两个人独立标注用IoU比对两个人的标注结果差异超过阈值的图片进入人工仲裁环节。这个流程让数据质量显著提升但人力成本翻了接近一倍。如果你一个人做项目可以分批次标注每批次完成后隔几天用不同状态重新检查部分图也能起到类似的效果。记住一个原则宁可要500张干干净净的图也不要1500张标准混乱的图。5.4 小目标识别优化前面说过低空无人机影像里单株苗很小。小目标在常规YOLO训练中特征图分辨率不足浅层细节丢失得快。我的建议是优先保证输入分辨率另外一个有效技巧是使用自适应锚框。YOLO框架默认的锚框是针对通用数据集预设的田间幼苗框的长宽比极端细长形状和近正方形都有必须用你自己数据的真实框尺寸重新聚类生成锚框。具体做法是读取训练集所有标注框的宽高用K-Means聚类得到9个锚框尺寸写进模型配置里再训练。就这么一个操作我们的小目标检出率提升了4个百分点。6. 落地经验与后续扩展6.1 项目成本与收益评估算一笔实用性账。数据集建设这块3500张图的采集加标注两个人花了大概三周模型训练和调优大约两周。硬件成本是一块中端GPU租的话按天算也就几百块。对比传统人工调查一个500亩的农场人工数苗需要5到6个农技员干一天无人机采集加AI识别整个流程三小时出结果效率提升是数量级的。这里还没算上AI出苗率的一致性带来的决策价值——同一块地人工复查和AI结果有偏差但AI的偏差是系统性的、可修正的人工的偏差是随机的、无法预测的哪个更适合做趋势监测不言而喻。6.2 从玉米扩展到其他作物的迁移这个项目做完之后最容易的自然扩展就是把同样的流程平移到其他作物。小麦、大豆、棉花这些作物的出苗期形态差异很大但技术架构不用动。我做了几次迁移验证结论是保留原有模型作为预训练权重在新作物数据上微调比从零训练至少节省40%的标注量。通用做法是冻结前20层只训练检测头等mAP不再涨了再解冻全部层低学习率精调一轮。杂草类别上也可以做得更细比如把常见优势杂草单独建类方便做针对性除草方案。这块的数据采集同样要覆盖不同地块、不同气候年份年际间的杂草形态差异甚至比地块间的还大。6.3 最后给同行几句心里话回头看整个项目技术难度其实没有想象中大真正的门槛在数据和业务理解上。数据标注的规则定义、出苗率的口径设计、垄行识别的逻辑每一项都需要跑到地里跟农技人员聊透了才能做对。我见过不少人一上来就急着训练模型数据集才几百张标注标准稀里糊涂最后做出来的东西只能跑Demo下不了地。农业AI项目尤其如此野外场景的干扰因素太多一个在实验室里测着很美的模型拿到真实农田里大概率翻车。最近在考虑把这个流程做成一个半自动化的工具链让非技术人员也能通过简单几步完成数据采集和模型更新。出苗期就那么十几天窗口误了农时什么都白搭工具链的易用性可能比模型精度更值得投入。如果你也在做类似的田间视觉项目欢迎拿着你的数据集问题和标注规范来一起聊聊这类项目闭门造车最容易走弯路。