☰
YOLOv8实战:田间作物识别与智能除草坐标输出全流程
2026/10/15 15:33:28 网站建设 项目流程

很多人第一次看到杂草和农作物幼苗并排摆在一起的照片时,都会觉得这东西根本没有难度——叶子形状不一样,颜色深浅不一样,为什么还要专门写代码去识别?但等你真把几百张野外实拍的图像丢给程序,问题就全冒出来了:同一株作物在不同光照、不同生长阶段下长得完全不一样,而不同地块里的杂草种类五花八门,有些甚至比作物还像作物。我这次做的田间识别项目,目标很朴素:从杂草丛生的实拍图像里,用 Python 把农作物一株一株框出来,输出坐标给后续的机械除草动作做引导。这篇文章把数据准备、模型选型、训练调参到落地部署的完整过程记录下来,重点讲清楚每个决定背后的原因,适合正在搞农业图像处理、智能除草或无人机巡检的读者参考。

1. 为什么“杂草里找作物”这么难:先看清问题再选技术

1.1 农业场景里那些反直觉的难点

在动手写代码之前,我花了不少时间梳理问题本身。农业场景和工业质检最大的区别在于:工业场景光照均匀、背景可控、目标姿态固定,而田间图像几乎把我能想到的计算机视觉难题全都装进了一张图里。

首先是光照。同一片叶子,在直射阳光下和阴天树荫下拍出来,RGB 颜色值能差出好几个量级。如果直接用颜色阈值分割,上午能跑通的参数,下午换块地就不行了。其次是遮挡。作物叶子和杂草叶片交叉叠在一起,在二维图像里根本没有干净的轮廓线,更别说什么“先分割再识别”的经典流程。第三是形态相似性。很多禾本科杂草在苗期和作物幼苗长得极其接近,比如玉米苗初期像一根细针,稗草在这个阶段也差不多,光看形状人眼都容易走眼。最后是尺度问题。无人机巡检图像里,一株刚出苗的作物可能只有十几个像素,脸都看不清,更别说提取纹理特征。

也就是说,传统图像处理那套“颜色阈值 + 形态学开闭运算 + 轮廓提取”的流程,在自然农田场景下几乎必挂。不是算法不够好,而是环境噪声大到特征根本不稳定。

1.2 分类、检测、分割:三条路线怎么选

同样是“识别农作物”,背后其实对应三种不同的任务:

  • 图像分类:给定一张图,判断里面有没有作物。适合大范围巡检撒网,但不知道作物长在哪。
  • 目标检测:输出每个目标的边框坐标。适合智能除草、定点喷洒,拿到坐标就能指挥机械执行器。
  • 语义分割:给每个像素打标签,精确到叶子边缘。适合需要精细操作的场景,但标注成本和计算开销都更高。

我这次的核心需求是输出每株作物的位置坐标,所以目标检测是最直接的选择。分类不给位置,分割成本高且对机械控制来说是“过度精细”——除草机械臂需要的只是一个中心点,而不是每一片叶子的轮廓。后文的所有代码都围绕目标检测来写,但我也会提一句分割方案做对比时的结论。

1.3 选型的底层逻辑:输出坐标是唯一硬指标

很多初学者有一个误区:先选个看起来很厉害的模型,再想它能干什么。我的习惯刚好相反,先列出业务硬指标,再倒推技术方案。

这次项目的硬指标有三个:第一,必须输出像素坐标,供下游机械控制使用;第二,需要能实时处理,至少每秒 10 帧;第三,模型要能在单块消费级显卡上训练,不能指望着云端算力。基于这三点,我直接把方案锁定在 YOLO 系列上。不是因为它多先进,而是因为它在“精度 - 速度 - 易用性”这个三角上的平衡最好,而且生态成熟、社区资料多,遇到问题能搜到答案。自己搭 CNN 分类网络听起来很酷,但工程落地时你会发现,光是数据加载、增强、训练监控、模型导出这一套流程就够写几千行代码,而 YOLO 开箱即用。

2. 数据集的采集与标注:成败在这里就已经决定了

2.1 拍摄规范:先想清楚部署场景再动手

数据集是整个项目里最花时间、也最决定上限的部分。模型再厉害,喂进去的图片质量不行,训练出来也是白搭。我当时定的拍摄规范是这样的:

  • 用普通手机摄像头或者地面巡检小车自带的相机,不要用专业单反,因为部署现场就是普通摄像头;
  • 覆盖多角度:俯拍为主,但也拍一些斜侧视角,模拟机械在不同高度作业时看到的景象;
  • 覆盖多时段:晴天、阴天、早上逆光、中午强光都拍一遍,这样模型见到的光照分布足够广;
  • 每类目标至少 100 张原始图片,总量到 500 张时第一版基本可以跑通。

一个容易被忽略的细节:拍摄高度要尽量和部署时的相机安装高度一致。如果部署时相机高度是 30 厘米,训练图片却都是人站着俯拍的一米五视角,那模型在部署时的表现会明显下降。目标大小、视角变形这些信息都藏在拍摄参数里,前期不统一,后面有的折腾。

我还做了一件事:每拍一批图片,就在文件夹里记录拍摄条件。比如“晴天中午,逆光,黄土地”,后面模型误检变多了,回头查这批数据,就能很快定位问题是不是出在某个特定的光照条件下。

2.2 标注策略:只标作物还是连杂草一起标

标注工具我用的是 LabelImg,操作逻辑简单,框选目标、选择类别、保存为 YOLO 格式的 txt 文件就行,一个多小时就能上手。但真正值得讨论的是标注策略——到底只标作物,还是把杂草也标出来?

第一版我建议只标作物。理由有两个:一是省时间,因为农田图像里的杂草数量往往远多于作物,标全杂草的工作量可能是标的作物的好几倍;二是如果杂草类别标注不齐全,模型反而会学会只关注作物本身的特征,而不是“去和某个特定的杂草做对比”。我后来试过把几类常见杂草也标进去,加了两个类别,但精度并没有明显提升,训练时间却变长了。

不过只标作物也有代价:模型不知道“杂草长什么样”,在杂草密集的区域容易把部分杂草框成作物。这个问题我放在后面“样本不平衡”部分详细讲,这里先埋个伏笔。

2.3 数据增强:用最少的图片把模型“喂饱”

在没有外部数据集、纯靠自己采集的情况下,数据增强是绕不开的一步。好在 YOLOv8 的训练流程里默认开启了马赛克增强、随机翻转、缩放和色彩抖动,所以代码层面几乎不用写任何额外的增强逻辑。

但这里有一个值得留意的点:Ultralytics 默认的色彩抖动范围是经过工业场景调校的,直接用在农业图像上有时会“增强过头”。比如它会把绿叶的颜色随机调成偏黄甚至偏枯,这在真实场景里不常见,反而让模型学到一些不存在的颜色变化。我踩过一次这个坑,后来通过 fiddling 配置项把 HSV 的增强幅度调低了,把增强重点放在几何变换上,比如轻微透视扭曲和随机裁剪——因为田地里叶片姿态变化才是真实存在的变量。

3. 基于 YOLOv8 的完整 Python 实现

3.1 环境安装与数据集目录组织

YOLOv8 的 Python 接口做得很干净,安装就是一条命令:

pip install ultralytics

如果你的机器没有现成的 PyTorch 环境,可能还需要先装 torch 和 torchvision。这里有个容易踩的坑:ultralytics 依赖的 PyTorch 版本不能太旧,如果机器上原本装的是很久以前的版本,建议先升级再装,否则会出现看似安装成功、import 报错的怪问题。我有一次在环境里折腾了半天才发现是 torch 和 torchvision 版本不配套。

数据集的目录结构建议按下面这样组织,全部放在同一个根目录下:

crop_data/ ├── train/ │ ├── images/ │ └── labels/ └── val/ ├── images/ └── labels/

LabelImg 保存的标注文件默认是每张图片对应一个同名的 txt 文件,里面每一行代表一个目标框,格式是“类别 中心x 中心y 宽度 高度”(都归一化到 0-1)。注意训练集和验证集的图片绝不能混用,否则验证指标会虚高,看起来模型很牛,一上真实场景就现原形。

3.2 训练脚本:关键参数逐个说

训练前要写一个数据配置文件,告诉 YOLO 数据在哪、有几个类别、类别叫什么:

# crop_data.yaml train: ./train/images val: ./val/images nc: 1 names: ['crop']

然后就可以写训练脚本了:

from ultralytics import YOLO model = YOLO('yolov8s.pt') # 使用预训练权重做迁移学习 model.train( data='crop_data.yaml', epochs=80, imgsz=640, batch=16, device=0, patience=10, workers=4, seed=42, cos_lr=True, )

逐个说几个我认为最重要的参数:

  • 预训练权重:加载yolov8s.pt,让模型从在 COCO 上学到的特征开始训练,收敛速度和最终精度都远好于随机初始化。这里的“s”是模型尺寸,s 是 small,还有 n、m、l、x 几个档位。农业目标不算复杂,s 档足够,速度还快。
  • imgsz=640:训练图像分辨率。这个值越大,小目标越容易被检测到,但显存占用成平方上升。我的显卡是 8GB 显存,640 是稳妥选择。
  • batch=16:受显存限制。如果你显存大,可以考虑 32,训练更快而且通常更稳。
  • patience=10:连续 10 个 epoch 验证指标不提升就提前停止,避免浪费时间。

训练开始后,终端会实时打印每个 epoch 的 loss、精度、召回率等指标。Ultralytics 还会自动保存best.pt(验证集上表现最好的权重)和last.pt(最后一个 epoch 的权重),不用自己手动管理。我习惯训练完直接把best.pt拿出来做后续推理验证。

3.3 推理与可视化:从模型输出到可用坐标

训练完成后,推理代码同样很简单:

from ultralytics import YOLO import cv2 model = YOLO('runs/detect/train/weights/best.pt') results = model.predict('field_test.jpg', conf=0.25, imgsz=640) for r in results: boxes = r.boxes print(f'检测到 {len(boxes)} 个目标') for b in boxes: x1, y1, x2, y2 = b.xyxy[0].tolist() cls = int(b.cls[0].item()) conf = float(b.conf[0].item()) print(f'作物 {cls}, 置信度 {conf:.2f}, 坐标 ({x1:.0f}, {y1:.0f}) - ({x2:.0f}, {y2:.0f})')

conf=0.25是置信度阈值,只有置信度高于这个值的目标才会被输出。这个值需要在实际验证集上根据精度和召回率的平衡来调,并不是越低越好。我刚开始用 0.25,发现杂草误检比较多,后来在验证集上画了 F1-置信度曲线,确定 0.42 左右才是当前数据下的甜点值。

要想直观看到检测效果,直接把框画回原图:

img = cv2.imread('field_test.jpg') for b in boxes: x1, y1, x2, y2 = map(int, b.xyxy[0].tolist()) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f'{conf:.2f}', (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite('result.jpg', img)

跑出结果之后,不要只看几张贴图就完事了。我建议写一个批处理脚本,把整个验证集的每张图都跑一遍,统计总体的精确率、召回率、mAP,再按图片逐一检查失败案例。比如把检测置信度低于 0.1 但标注里确实有作物的图片单独收集起来,这些往往是“难样本”,分析它们的共性往往能指导下一轮数据采集。

3.4 为什么选 YOLOv8 而不是自己搭 CNN

我知道有些读者会觉得,用现成的 YOLO 是不是太“拿来主义”了?自己写个 ResNet 做分类再滑窗检测,不是显得更有技术含量吗?

我从实际角度说:自己搭 CNN 做作物分类,在实验室里确实能跑出不错的结果,因为测试集是从同一批数据里分出来的,分布完全一致。但一上真实农田,泛化能力立刻崩塌。YOLOv8 不仅是一个模型,它自带了一套完整的数据增强、训练策略、anchor 匹配、NMS 后处理、指标评估和导出工具链,这些工程细节加起来的价值远超过模型网络结构本身的差异。与其花三个星期造轮子,不如把精力投在数据采集和实地测试上,这才是农业识别项目里真正的胜负手。

4. 训练路上踩过的坑:小目标、光照和样本不平衡

4.1 小目标检测:幼苗只有十几个像素时怎么办

第一版模型在验证集上的 mAP 到了 0.85,看着挺不错,但一到无人机拍的图上,漏检率陡然升高。我排查后发现,问题就出在目标尺度上:近景拍摄的作物幼苗能占 100 多个像素,而无人机视角下每株可能只有 15 到 20 个像素。YOLO 的网络结构经过多次下采样后,小目标的特征信息在这个尺寸下已经所剩无几。

这一轮的解决思路我先后试了三种:

  • 把imgsz从 640 提高到 1280。网络输入变大,小目标的像素占比自然变高。代价是训练时间和显存占用大约翻了三倍,推理帧率也降到不能实时跑的边缘。
  • 引入 SAHI 切图推理。思路很简单:把大图切成有重叠的小块,每个小块分别送进模型检测,再把所有结果合并回原图坐标系。这是目前解决农业遥感小目标问题最实用、成本最低的方案,而且 YOLOv8 和 SAHI 配合得很顺。
  • 换更大尺寸的模型(从 s 到 m 甚至 l)。效果有提升,但不如前两个方案显著。

我最终的方案是前两招打组合:训练时保持imgsz=640,推理时用 SAHI 切图。这样既保住了训练效率,又解决了小目标检测问题。如果你的硬件条件允许,直接imgsz=1280训练是最省事的思路——前提是你受得了那个训练时长。

4.2 光照与色彩:数据增强不是万能的

农业图像的第二个大头是光照。我遇到过一个很典型的情况:模型在阴天拍摄的验证集上表现良好,但一到晴天正午,大量漏检。分析原因是训练集里晴天图片占比太少,模型没见过强光下叶片反光、阴影遮挡的样子。

一开始我想用数据增强来补,把色彩抖动幅度调得很大,结果非但没帮上忙,反而让模型把一些杂草误认成了作物。原因是过度调色后的“假图片”干扰了模型对绿色植物纹理的真实认知。后来改用 CLAHE(限制对比度自适应直方图均衡化)做预处理,在训练和推理时都用同样的 CLAHE 参数,相当于把图像先统一到一个标准对比度空间,模型不用再自己适应复杂光照。这个改动单独就贡献了大约四个百分点的 mAP 提升。

那段预处理代码很短,几行就能写完:

import cv2 def process(img): lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

最关键的一条经验是:训练和推理必须走同一条预处理管道。如果训练时不做 CLAHE,推理时做了,模型看到的数据分布和训练时不一致,精度反而下降。

4.3 标注不一致导致的震荡与解决

另一类问题不太容易被人提,但它非常折磨人:标注不一致。所谓不一致,就是一百张训练图里,有的标注把整株作物连根茎都框进去了,有的只框了叶片,还有的把被土半遮住的幼苗直接跳过了。这种不一致会让模型训练非常震荡,loss 怎么都降不下去。

我后来复盘,发现根因是标注人员对“什么算一株作物”的理解不统一。这个在人类眼里很模糊的问题,在模型眼里就是白纸黑字的错。

解决思路是给标注规则定死几条硬标准:一株作物只框最核心的绿色叶片区域,不包含周围泥土;叶片被遮挡超过一半但能明确判断的,仍算一株;完全分辨不清是作物还是杂草的,宁可跳过也不乱标。我花了一整个下午把之前的数据重新过了一遍,删掉了一些明显不规范的框,重新训练后模型的收敛速度和最终指标都上了一个台阶。

这段经历给我一个很大的教训:标注质量的重要性远高于标注数量。与其匆忙标两千张质量参差的图,不如好好标一千张严谨一致的图。模型学的不是图片本身,而是你标注里的规律。

4.4 一次完整的迭代记录:从 77% 到 91% 的 mAP50 提升路径

为了让上面的经验更直观,我把中间几次关键迭代的改动和效果整理成了一个简表,这比任何抽象的建议都更有说服力:

迭代版本主要改动mAP50 结果
V1首批 500 张图片,imgsz=640,YOLOv8s0.77
V2增加 CLAHE 预处理,训练推理统一0.81
V3清洗标注,统一框选规则,剔除歧义框0.86
V4增加晴天正午和背光场景图片 200 张,SAHI 切图推理0.91
V5替换 YOLOv8m,提高置信度阈值到 0.420.92

每一步改进其实都不是靠模型结构,而是靠数据和预处理。这是农业视觉项目和普通目标检测比赛最大的不同:比赛拼模型,田里拼数据。我在做 V4 那轮时,只是有针对性地补拍了模型漏检最严重的时段数据,几乎没有调任何模型参数,mAP 就涨了五个点。这说明模型本身已经在正常工作,缺的是数据覆盖。

5. 模型导出与部署:从 Python 脚本到实际机械动作

5.1 ONNX 导出与推理加速

在开发环境跑通 Python 脚本只是第一步,真正要部署到巡检小车或者除草机器人上,通常不会直接在设备里装整个 PyTorch 环境。一来包体积太大,二来 CPU 上 PyTorch 的推理效率不算高。我这次部署到一台低功耗边缘设备上,用的是 ONNX 格式的模型。

导出只需要一行:

model.export(format='onnx', dynamic=True, half=False)

dynamic=True允许动态 batch 输入,half=False是因为有些边缘设备的 NPU 对半精度支持不够稳定。导出后再用onnxruntime做推理,速度比原始 PyTorch 在 CPU 上快接近一倍,而且安装包小很多:

pip install onnxruntime

这里有一个需要留意的点:导出后的 ONNX 模型在边界框解码等预处理、后处理上和原始 PyTorch 推理略有差异,最好用同一组测试图片跑一遍对比结果,确认坐标误差在可接受的范围内再上真机。我在第一次导出时发现框的位置整体偏移了几个像素,排查后发现是预处理阶段的图像缩放方式没对齐。

5.2 检测框坐标如何换算成执行器动作

模型输出的只是图像中的像素坐标,要让机械执行器动作,还需要一个坐标映射过程。我的做法比较简单粗暴,适用于固定相机安装的巡检小车:

  • 先在相机视野里放置几个已知间距的标定物,算出图像坐标系到地面坐标系的像素与米换算比例;
  • 检测到作物后,取检测框底边中心点作为植株根部位置(因为叶子中心会有透视偏差,底部中心更接近真实位置);
  • 用换算比例把图像坐标换算成相对小车中心的平面偏移量;
  • 将这个偏移量通过串口发送给下位机,驱动除草机械臂或喷洒装置。

如果你的项目里相机装在机械臂上,随臂运动,那就需要完整的手眼标定流程,复杂度会高很多。这里不展开,但提一句:先做固定安装的验证,再做运动版本,不要一上来就挑战最复杂的情况,否则出了问题你根本分不清是识别错还是标定错。

5.3 部署时最容易忽视的现场差异

部署阶段我遇到的最大问题是“训练场景”和“部署现场”不一致导致的灾难性掉点。室内模拟田测试精度 90%,一到真正的地里,光线、土壤颜色、作物品种全变了,精度掉到 70%。后来我学到一个很实用的思路:正式部署前,先到现场拍两百张图,用训练好的模型在“实测模式”下跑一遍,把置信度低的样本挑出来,人工修正后做一次小规模的微调训练。

微调训练不是重新从零开始,而是在已有的best.pt基础上继续训练几十个 epoch,数据量也不用大,200 张就够了。这一步操作简单,但能让模型迅速适应新的现场环境,可以说是“性价比”最高的调优手段。我后来每一次换地块,都会重复这个流程,模型精度基本能稳定在 90% 以上。

5.4 前后端联动:一个最小可用的巡检脚本框架

等你把模型、标定、通信这几件事都打通,就可以顺手搭一个最小的联动框架。拿我这次来说,就是让巡检小车每帧拍到图后,先跑模型,再判断画面里有没有作物框,如果有,就计算目标中心点的地面坐标,然后把坐标包成一条简单的帧协议发送给下位机。这样的框架代码加起来不到一百行,但因为每一块都已经在前期分别验证,联调的时候几乎没出岔子。

我在实际调试中体会到,模块化地去组织这个流程,比把识别、坐标换算、通信全塞在同一个主循环里要省心得多。每类功能的职责边界清晰,线上出了问题也知道去哪一层排查:漏检查模型权重和预处理,框偏了查标定和坐标换算,动作没执行查串口协议。

最后再分享一点我的亲身体会:这类农业视觉项目,数据的时间占比七成以上,模型部分反而是最顺利的环节。标注的一致性、拍摄条件与部署场景的统一性,都是看不见但决定成败的细节。如果让我重来一次,我会在第一周就把“标注规则文档”写出来,而不是凭感觉边标边改。希望这篇文章能帮你少走一些弯路,把精力真正花在刀刃上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询