搞智慧农业的朋友应该都遇到过这种尴尬:植株出了问题,最先有反应的往往是叶片,但等肉眼能确诊的时候,病害很可能已经传染开一片了。番茄健康叶片检测这个课题,我前前后后做了大半年,最大的瓶颈反而不是模型算法,而是数据——自己下地拍叶片、拍完还得清洗、标注,折腾了小半个月才凑出几百张勉强能用的图。所以当我看到这套3100张的番茄叶片YOLO检测数据集时,第一反应是松了口气。它的定位很明确:给智慧农业场景下的叶片健康状态自动识别做训练样本,格式是现成的YOLO标注,不用再折腾转换。无论你是刚入门YOLO的学生,还是做大棚监测落地的工程师,这套数据都够你从数据校验、模型训练一路跑到部署验证,不夸张地说,能帮你省下至少两周的原始数据采集时间。
1. 项目定位:为什么锁定番茄叶片检测
1.1 番茄是病害监测的典型场景
番茄可以说是设施农业里种植量最大的茄果类蔬菜之一,温室大棚里通常是高密度栽培,环境高温高湿,一旦出现早疫病、晚疫病这类气传病害,扩散速度非常惊人。叶片作为植株最先暴露问题的部位,在视觉上非常直观:早疫病会出现同心轮纹状褐色病斑,晚疫病呈现水渍状暗绿色斑块,叶霉病在叶背形成灰绿色霉层,黄化曲叶病则让新叶明显变小、卷曲、发黄。这些带有明显颜色和纹理差异的症状,天然适合用计算机视觉来做自动识别。
有人可能会问,为什么不用图像分类模型,非要上目标检测?因为实际生产里,一株番茄上往往同时存在健康叶片、早期病斑叶片和严重病叶,分类模型只能告诉你"这张图里有病叶",却说不清病斑在哪个位置、占多大面积。而目标检测能一次性输出检测框、类别和置信度,后续无论是做精准施药、统计病情指数,还是联动喷药机器人定点作业,都需要这种带空间位置的信息。
1.2 为什么数据集格式选择YOLO
现在目标检测领域的数据集格式五花八门,COCO用的是冗长的JSON嵌套结构,VOC是XML文件,而YOLO格式一行文本就是一个目标。YOLO标注本身是归一化的中心点坐标加宽高,用txt保存,占用空间极小,读取速度也快,配合Ultralytics生态(YOLOv8、YOLO11系列),从训练、验证到导出部署模型,全流程几乎没有额外转换成本,这对中小团队和独立开发者非常友好。
等到你想换其他框架做交叉验证时,YOLO txt格式转成COCO或VOC也非常容易,网上有现成脚本,反过来也一样。相对而言,如果数据集一开始就给COCO那种超长JSON,每次打开都要解析大文件,训练前还要额外预处理,效率确实差不少。我自己在项目里就吃过这个亏,早期拿到一批VOC格式的叶片数据,光是批量转YOLO格式、处理XML里各种嵌套标签就浪费了一整天。所以从工程效率角度,YOLO格式依然是目前最"省心"的选择。
1.3 3100张这个规模在实践中的意义
3100张图在通用视觉数据集面前不算大,像COCO那种几十万张的规模,个人项目根本消化不了。但叶片检测有个特点:目标形态相对固定,场景比较单一,背景无非是温室、大田、实验台几种。对这种任务,几千张高质量标注图足以训练出一个工程可用的基线模型。关键要看数据的多样性——是否涵盖不同光照条件、不同生长阶段、不同拍摄角度、不同品种的番茄叶片。
实践经验里,我会把数据按8:2或者9:1划分训练集和验证集。这里有个容易被忽略的坑:如果图片来自多个大棚,最好按大棚分组划分,而不是把所有图片混在一起随机切。否则同一个大棚里同一个批次的叶片,可能同时出现在训练集和验证集里,验证指标会虚高,一到新环境就露馅。所以拿到3100张这个规模的数据,第一步先别急着训练,先看目录组织、看图片分布,再决定怎么划分。
2. 数据集结构与标注规范解析
2.1 标准目录组织与data.yaml
一套规范的YOLO数据集,通常长这样:
tomato_leaf_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages和labels一一对应,每一张jpg/png图片,在labels的同名目录下有一个同名的txt文件。打开训练时,框架会根据data.yaml里的路径去读图片和标签。data.yaml是整套数据集的中枢配置文件,我一般会写成:
path: /path/to/tomato_leaf_dataset train: images/train val: images/val test: images/test nc: 4 names: ['healthy_leaf', 'diseased_leaf', 'yellow_leaf', 'wilted_leaf']其中nc代表类别数量,names是类别名字列表,顺序必须和标注txt里的数字id严格对应。假如数据集的类别不是这四类,而是包含更细的病害类型,比如early_blight、late_blight、leaf_mold,那就把names改成对应的名字,nc也随之调整。最容易翻车的地方就是nc和names长度不一致,或者names顺序和标签文件里实际使用的id错位。
2.2 标注坐标格式与可视化校验
YOLO的标签txt,每一行代表一个目标,格式是:
<类别id> <x_center> <y_center> <width> <height>五个数字全部归一化到0到1之间。以一张宽为W、高为H的图片为例,如果某个病斑的真实像素坐标是(xmin, ymin, xmax, ymax),那么转换公式是:
- x_center = (xmin + xmax) / 2 / W
- y_center = (ymin + ymax) / 2 / H
- width = (xmax - xmin) / W
- height = (ymax - ymin) / H
一个标准的标注文件内容是这样的:
0 0.512 0.473 0.182 0.214 1 0.784 0.631 0.153 0.168拿到数据后,别光看txt,一定要把标注框可视化出来,和原图叠在一起检查。我自己最常用的一段OpenCV脚本是这样的:
import cv2 img_path = 'tomato_leaf_dataset/images/train/0001.jpg' label_path = 'tomato_leaf_dataset/labels/train/0001.txt' img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path, 'r', encoding='utf-8') as f: for line in f.readlines(): cls, xc, yc, bw, bh = map(float, line.split()) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite('check_0001.jpg', img)跑完脚本后,拉出几十张图翻一遍,重点关注两类问题:一是框是否紧贴目标,有没有把半片叶子或整株苗框进去的;二是标签坐标是否超出边界,比如x_max大于1或者width算出来是负数,这些都会直接干扰训练。
2.3 拿到数据集先做的三件事
拿到任何标注数据集,我都建议按固定流程做一遍"健康检查",可以省掉后面训练时的大把排查时间。
第一,写一个统计脚本,扫描所有标签文件。检查每个txt是否为空文件,检查每一行的五个数字是否都在合法区间,尤其防止出现坐标值为负或大于1的情况。第二,抽样式可视化,上面那段OpenCV脚本改成遍历多个文件,生成一张拼接图,快速过一遍标注质量。第三,统计类别分布和标注框的数量,看看每个类别下有多少个目标框。如果某个类只有几十个框,而其他类有上千个,那训练时这个类的mAP很可能起不来,后续需要针对性地做增强或采样。
3. 实操:用PyCharm搭建YOLO环境并训练
3.1 PyCharm环境安装与依赖配置
YOLO在Windows和Linux上都能跑,我自己习惯在Linux服务器上训练,本地用PyCharm做代码编辑和远程调试。如果你只有一台带NVIDIA显卡的Windows机器,同样没问题。先创建独立的Python虚拟环境,避免和系统里的其他项目互相污染依赖。
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics装完ultralytics之后,PyTorch一般会作为依赖自动装上。但如果你用的是比较新的显卡,建议先到PyTorch官网确认对应CUDA版本的安装命令,再手动安装PyTorch,最后装ultralytics。我自己就遇到过装完直接训练,发现torch.cuda.is_available()返回False的情况,折腾了半天才发现是装成了CPU版本的torch。验证GPU是否可用的命令很简单:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"在PyCharm里新建项目时,选择刚才创建的conda环境作为解释器,这样终端里训练、调试脚本、看变量都是一套环境,省心很多。
3.2 data.yaml配置与训练启动
确认数据集的路径、命名没问题后,就可以启动训练了。这里假设你的data.yaml放在数据集根目录下,训练代码可以写成:
from ultralytics import YOLO model = YOLO('yolov8s.pt') results = model.train( data='data.yaml', epochs=150, imgsz=640, batch=16, lr0=0.01, patience=30, device=0, workers=4, )模型选择上,如果你的显卡显存小于8G,建议先用yolov8n.pt跑通流程,n模型参数量最小,训练速度快,适合先验证数据和配置有没有问题。如果显存比较充足,比如12G以上,直接上yolov8s.pt,精度会比n高一截。至于m和l,在叶片检测这种目标任务不算复杂的场景里,未必能带来明显收益,反而训练时间成倍增加,性价比不高。
瓦片尺寸imgsz默认640,对大多数叶片图片已经足够。如果你的原始图片分辨率很高,叶片在整张图里占比偏小,可以试试imgsz=960或者1280,但显存占用会明显上升,需要同步调小batch。batch优先用16或32,显存不够就降成8或4,不建议为了凑batch把图片尺寸压得太小,病斑纹理细节一旦丢失,模型很难学出有效的判别特征。
3.3 训练过程中的关键判断
训练启动后,终端里会实时刷新loss和mAP指标。第一轮loss通常是1到2左右,或者更高,这很正常。随着epoch增加,train_loss和val_loss都会下降,val的mAP50会逐步上升。如果看到train_loss一直下降,但val_loss在某个epoch后开始反弹上升,这就是典型的过拟合信号,说明模型开始"背"训练集里的细节,而不是学习泛化特征。
Ultralytics自带的patience参数就是早停机制,我习惯设置成30,意思是如果连续30个epoch验证集指标没有提升,训练就自动停止,可以避免无意义的算力消耗。训练结束后,项目目录下会生成runs/detect/trainX文件夹,里面包含weights/best.pt和weights/last.pt。best.pt是验证集表现最好的权重,部署时优先用这个。
4. 调参与避坑经验
4.1 数据增强参数怎么配
YOLO自带的增强策略已经很丰富,mosaic、copy_paste、fliplr、flipud、hsv_h、hsv_s、hsv_v等,默认配置对大多数任务都适用。叶片检测里有个值得注意的地方:mosaic增强会把四张图拼在一起,可能把不同光照、不同背景的叶片混在同一张训练图里,这对提升模型鲁棒性有帮助,但在训练最后阶段,如果mosaic比例太高,反而会干扰模型收敛。Ultralytics提供了close_mosaic参数,可以在最后10个epoch自动关闭mosaic,让模型在接近真实分布的输入上精调。
另外,番茄叶片的病害特征和颜色强相关,HSV增强里的色相扰动建议不要调太大。如果hsv_h设置过高,绿色叶片可能被改成红色,或者病斑的褐色纹理被抹掉,模型学到的颜色语义就乱了。我用叶片数据训练时,一般把hsv_h保持在默认值附近,hsv_s和hsv_v可以稍微放宽,模拟不同光照强度下的颜色饱和度变化。
4.2 显存不足与训练中断的应对
训练中途报CUDA out of memory是最常见的问题。应对思路是分步降级:先把batch从16降到8,如果还不够,就把imgsz从640降到512,再不够就换更小的模型,从yolov8s降到yolov8n。注意,改动任何一个参数后都要重新启动训练,不要只改batch继续resume,数据和模型配置不一致容易出幺蛾子。
还有一个很多人不知道的选项是梯度累积。不降低batch的情况下,如果想让等效batch变大,可以设置accumulate参数。比如batch=16,accumulate=2,等效的优化器batch就是32。这个方法在显存有限但想复现论文大batch效果时很有用。不过叶片检测任务里,训练batch在32以内一般就够稳定了,没必要过度追求大batch。
4.3 标注质量导致的模型问题
如果训练过程loss曲线正常,但最终的mAP50惨不忍睹,先别急着换模型结构,回头看数据。我踩过一个印象很深的坑:有一批标注文件里,类别id从1开始而不是0开始,导致所有检测框的类别整体错位。模型训练时把"健康叶片"当成了"病斑叶片",精度自然差得离谱。还有一次是某个标签文件里出现一行只有4个数字,训练时解析报错,Ultralytics直接把这条记录跳过,导致模型漏学了一部分目标。
这类问题靠训练日志很难发现,一定要在训练前就用脚本把整个数据集的标注文件全部扫描一遍。我一般会同时输出三个统计量:空标签文件数、行数字数异常的文件数、坐标超界的目标数。任何一个异常,宁可先花时间清洗,也不要直接拿去训练。
5. 模型导出、部署与扩展方向
5.1 导出ONNX并部署到边缘设备
训练完成后,导出部署模型是最后一公里。如果把best.pt直接丢给摄像头推理,需要有完整的PyTorch环境,而且推理速度一般。更常见的做法是导出ONNX格式:
model = YOLO('runs/detect/trainX/weights/best.pt') model.export(format='onnx', imgsz=640, dynamic=True)导出的onnx文件可以用ONNX Runtime跑CPU推理,也可以在Jetson这类嵌入式设备上转换成TensorRT引擎,进一步加速。实测下来,一张640x640的图片在Jetson Orin Nano上,TensorRT FP16推理大概几十毫秒级别,足够应对大棚内的定时巡检。如果没有专用硬件,用普通PC加Intel的OpenVINO也能获得不错的加速效果。
5.2 农业现场部署的注意事项
从训练环境搬到真正的温室大棚,会碰到不少实验室里没有的问题。首先是摄像头安装位置,如果固定安装在棚顶,拍到的叶片往往距离远、角度斜,检测框可能很小;如果装在巡检机器人上,机器移动带来的运动模糊会明显降低检测精度。建议部署前先用现场采集的几十张图片做一次快速验证,看看模型在真实光照下的泛化表现,再决定是否要补充数据微调。
叶片重叠也是个大麻烦。番茄植株枝叶茂密,健康叶片和病斑叶片经常互相遮挡,检测框可能会把两个目标框在一起,或者漏掉被遮挡的病斑。在实际项目里,我倾向于检测任务只负责"发现可疑目标",下一步再用分割模型或图像裁切放大做精细判断,这样比强行让检测模型在一个框里区分多片叶子要可靠得多。
5.3 下一步延伸:病害严重度分级与多模态分析
检测能力稳定之后,完全可以在它基础上继续扩展应用。一个比较实用的方向是病害严重度分级:统计每个检测框面积占整张叶片面积的比例,或者统计同一片区域里病斑框的密度,把病害程度分成轻、中、重三级。这样农户收到的不再是"发现病斑"的简单警报,而是可以直接指导用药浓度的分级建议。
更进一步,可以把YOLO的检测结果和温湿度传感器、土壤数据结合起来做多模态分析。早疫病、晚疫病的爆发往往和特定温湿度条件高度相关,如果叶片检测发现初期病斑,同时传感器数据也显示环境适合病害扩散,系统就可以提前预警,把处置时间从"发现后"提前到"即将发生",这才真正发挥智慧农业的预测价值。
最后说一点我自己踩过的坑吧。刚开始用这个级别的数据集训练时,我总想着把模型换大、把epochs拉满,结果常常是val mAP前期涨得好看,一到新场景就崩。后来我把更多精力放在数据本身的清洗和均衡上,把光照过暗、严重遮挡、标注框偏移的图剔掉,再用mosaic和HSV增强补齐多样性,模型反而稳了很多。对叶片检测这类任务来说,在数据准备上多花一小时,可能比在调参上多花一整天都值。另外提醒一句:验证集最好和训练集来自不同株系或不同拍摄批次,否则模型其实在"背答案",换到真实场景会露馅。这套3100张的番茄叶片数据集,正好是一个可以让你把完整链路跑通、把坑都踩一遍的合适起点。