尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

番茄叶片病害目标检测:数据集构建与YOLOv8训练避坑指南

番茄叶片病害目标检测:数据集构建与YOLOv8训练避坑指南 简介这是一套面向番茄叶片病害识别的目标检测数据集适合深度学习初学者和农业视觉研究人员用于模型训练、算法对比与复现实验。数据覆盖blight-disease、mosaic-virus、redspider-infection三类典型叶片病害同时提供YOLO与VOC两种标注格式可兼容YOLO系列、Faster RCNN、SSD等常见检测模型。压缩包共2000个文件主体为1999个txt标注文件和1个指定类别信息的yaml配置文件整体大小约247.69MB。txt文件以YOLO格式保存归一化边界框与类别序号yaml文件定义具体类别名称便于训练时直接读取。标注数据已预先划分为训练集、验证集和测试集拿到后即可开展YOLOv5至YOLOv10等系列模型的训练省去自行整理标注、划分数据集的额外工作。该数据集整体图片规模为6446张目前已有393人学习使用适合用于番茄叶片病害识别研究、农业智能检测项目实践以及作为目标检测算法调参与效果对比的基准数据。1. 番茄叶片病害识别数据集目标检测落地农业的第一道门槛温室和露地番茄的早疫病、晚疫病、叶霉病往往从几片叶子上的小斑点开始等到肉眼能大面积发现整棚用药成本已经翻了几倍。番茄叶片病害识别数据集要解决的就是用目标检测框出叶片上的病斑位置和类别让巡检机器人或手机拍照后能实时给出判断。这个方向听着不复杂真正做起来才发现难的不是网络结构而是数据本身一张叶片上可能同时有十来个病斑病斑之间形态接近不同光照下颜色还漂移。适合谁想做农业视觉落点的工程师、被小目标和不均衡样本折磨的检测玩家、以及需要把YOLO系模型真正部署到棚里的人。下面按我自己跑这类数据集的流程把数据构建、格式转换、训练调参与踩坑一次说透。2. 构建番茄叶片病害目标检测数据集从图像采集到标注格式2.1 采集环节拍什么样的叶片照片才不会让模型学到错误特征先把话说在前面公开渠道能拿到的番茄叶病数据相当一部分来自 PlantVillage 这类植物病理图像集它们的特点是单叶居中、背景干净、光照均匀。这类数据做论文没问题做落地巡检会翻车——你在棚里拍的叶片背景有土壤、滴灌带、其他叶片阴影阳光角度一变同一片病叶的色调完全不同。所以采集阶段我会按三个原则来定标准。第一拍摄距离要覆盖两个尺度近景特写叶片占画面一半以上用来框小病斑中景一株或几片叶用来测模型在密集场景下的表现。第二每类病害至少要有三个生长阶段初期的针尖大小黄点、中期的典型坏死斑、晚期的连片枯死。只看中期样本训练模型对早期症状的召回率基本是零。第三背景不要刻意回避——允许土、茎、果实的干扰但保证叶片本身是画面主体。分辨率上我一般要求叶片区域不低于 800×800 像素。很多手机拍出来是 4000×3000这没问题训练时再做缩放但如果采集端就是网络摄像头或无人机下视叶片在画面里只有几十个像素那再强的检测器也白搭。这一条要写进采集规范里不然标注环节会直接崩溃标注员连病斑和叶脉都分不清框出来全是噪声。2.2 标注工具与标签体系LabelImg、X-AnyLabeling 怎么选类别怎么定采集完原始图像下一步是标注。目标检测常用标注工具里我实际用过 LabelImg、labelme 和 X-AnyLabeling。LabelImg 胜在轻量Python 直接 pip 装支持 Pascal VOC 的 XML 格式电脑配置一般也能跑labelme 更偏多边形分割虽然也能输出矩形框但矩形标注效率反而不如 LabelImg。X-AnyLabeling 是后起之秀内置了 YOLO 格式直接导出还有半自动辅助标注模型适合几百张以上的量。我的建议是样本量 500 张以内用 LabelImg 就行超过 1000 张老老实实上 CVAT 或 X-AnyLabeling 这类带团队协作和自动预标注的能省一多半时间。标签体系是这个数据集最需要较真的地方。番茄叶片病害常见的有早疫病Alternaria solani、晚疫病Phytophthora infestans、叶霉病、斑枯病、细菌性斑点病还有健康叶片。很多人会把“病斑”和“病叶”混为一谈——用整片叶子作为检测框模型学到的是“这片叶子形状像病叶”而不是“这个病灶区域在哪”这对定位毫无帮助。我的原则是一个目标检测框只框一个独立病斑或一片连片病斑区域同一张叶片上有多个分散病斑就标多个框病斑紧挨在一起时如果边界还能区分就分开框如果已经融合成一片坏死区就框成一个整体。另外要特别注意“标签歧义”早疫病的病斑有同心轮纹晚疫病的水渍状边缘颜色偏深但早期症状非常像。遇到拿不准的样本我的处理方式简单粗暴——不确定就丢给专家复核复核不了的直接删掉绝不硬塞进某一类。宁可类别样本少一点也不要制造脏标签因为脏标签对目标检测的伤害比样本少大得多。2.3 标注格式转换把 VOC XML 转成 YOLO txt 的完整处理脚本标注工具输出通常是 VOC XML 或 COCO JSON而 YOLOv8 训练要的是每张图一个 txt、每行一个目标的归一化坐标。这一步不复杂但坐标换算和类别索引的错位是高频翻车点。我一般写一个通用转换脚本# voc2yolo.py # 作用将Pascal VOC格式的XML标注转换为YOLO格式的txt标注 import os import xml.etree.ElementTree as ET from pathlib import Path def convert_xml_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() # 图像宽高YOLO坐标需要按原图尺寸归一化 img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 转换为中心点坐标 宽高并做归一化 x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 归一化后坐标越界时裁剪到[0,1]避免训练报错 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0) height min(max(height, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 每个xml对应一个同名txt写入目标目录 out_path Path(out_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) # 使用示例 if __name__ __main__: # class_names的顺序就是训练时的类别ID必须和后续yaml保持一致 class_names [healthy, early_blight, late_blight, leaf_mold] os.makedirs(yolo_labels, exist_okTrue) for xml_file in Path(xml_labels).glob(*.xml): convert_xml_to_yolo(str(xml_file), yolo_labels, class_names)这段代码的核心在两点。一是 bndbox 的四个像素坐标换算成中心点和宽高这一步公式写错会导致框要么整体偏移、要么宽高比翻倍。二是归一化YOLO 要求所有坐标除以图像宽度和高度如果原图是 4000×3000 而标注工具显示的是缩放预览图上的坐标不做对应缩放就会全图错位。我标过一个教训用 LabelImg 打开大图时界面会自动缩放但保存的 XML 里坐标始终是原始像素值所以转换前先随手打印两个样本的 txt 内容用 OpenCV 画出来看框对不对再批量跑完整个目录。3. 用番茄叶片病害数据集训练目标检测模型YOLOv8 的适配流程3.1 数据集目录结构与 YAML 配置一张图都不能放错位置拿到干净的标注后下一步是组织目录。YOLOv8 对数据集目录结构相对宽松但为了少踩坑我建议按下面这个标准布局来这也符合大多数网上流传的“处理数据集用于 yolov8 训练”教程的惯例dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml关键规则是images/train 里的图片文件名不含扩展名必须能和 labels/train 里的 txt 一一对应扩展名无所谓但 basename 必须严格一致。很多人训练时报错File not found排查到最后就是图片是 .jpg、标注是 .txt但目录里混进了 .jpeg 和 .JPG 两套扩展名YOLO 按列表匹配时漏文件。我习惯在划分数据集前先执行一遍# 检查images和labels目录下文件basename是否一一对应 for f in dataset/images/train/*; do base$(basename $f | sed s/\.[^.]*$//) if [ ! -f dataset/labels/train/${base}.txt ]; then echo Missing label for: $f fi donedata.yaml 是训练的入口配置内容不复杂但字段必须写对常见做法如下# data.yaml —— 路径用相对当前运行目录的路径即可 path: ./dataset train: images/train val: images/val # test 可以不写训练时不会用到 nc: 4 names: [healthy, early_blight, late_blight, leaf_mold]nc必须和names长度一致names的索引必须和前面 txt 里的 class_id 一致。这里的坑是如果你在 voc2yolo 脚本里 class_names 的顺序是[healthy, early_blight, ...]那 yaml 里也必须按这个顺序写否则模型会把早疫病当成晚疫病来训练。顺序错位不会报错结果就是 mAP 一直上不去属于典型的“黑匣子问题”。3.2 训练集与验证集的划分随机切分是最懒也是最后悔的做法很多教程用train_test_split随手按 8:2 切分我一开始也这么干直到一次验证集准确率虚高得离谱才发现问题。番茄叶病数据如果来自同一批拍摄、同一时期、相同光照随机切分会让训练集和验证集高度相似——模型其实记住了背景和光照分布而不是病斑特征。正确做法是按拍摄条件分组比如一批数据来自上午的温室、一批来自下午的露地、一批来自人工光源补光环境那么划分时要保证每个条件下都有一定比例进入训练集和验证集。我写一个简单的脚本按文件名前缀分组再做分层划分# split_dataset.py # 作用按照片来源批次分层划分train/val避免同源图像混入验证集 import os import random from pathlib import Path import shutil random.seed(42) # 固定随机种子保证可复现 image_root Path(all_images) label_root Path(all_labels) train_img_dir Path(dataset/images/train) val_img_dir Path(dataset/images/val) # 按文件名中的批次前缀分组例如上午采集的文件名为am_001.jpg batches {} for img_path in sorted(image_root.glob(*)): prefix img_path.stem.split(_)[0] # 取前缀作为批次标识 batches.setdefault(prefix, []).append(img_path) train_ratio 0.8 for prefix, img_list in batches.items(): random.shuffle(img_list) split_point int(len(img_list) * train_ratio) for i, img_path in enumerate(img_list): dst_img_dir train_img_dir if i split_point else val_img_dir label_src label_root / (img_path.stem .txt) if label_src.exists(): shutil.copy(str(img_path), str(dst_img_dir / img_path.name)) shutil.copy(str(label_src), str(dst_img_dir.parent.parent / labels / dst_img_dir.name / label_src.name)) print(划分完成请检查目录结构)这个脚本的关键逻辑是“按批次前缀分组再各自切分”而不是全局乱序随机。前缀可以是拍摄时间、地点、设备编号只要你的文件名里能区分不同采集环境就行。参数上train_ratio设 0.8 是常规值但样本总量不足 500 张时我会提到 0.9宁可少验证几批也要让训练集覆盖尽量多的病斑形态。3.3 启动训练Ultralytics YOLOv8 的最小可用命令数据集和配置就位后训练本身就用 ultralytics 库命令不长但每个参数都是有意义的# 使用YOLOv8n模型训练番茄叶片病害检测 yolo detect train modelyolov8n.pt datadataset/data.yaml \ epochs100 batch16 imgsz640 patience20 \ projectruns/detect nametomato_blight # 训练完成后直接评估验证集指标 yolo detect val modelruns/detect/tomato_blight/weights/best.pt \ datadataset/data.yaml参数说明modelyolov8n.pt是 COCO 预训练权重拿它微调比从零训练收敛快得多batch16在 8GB 显存下基本安全如果用了 1024 分辨率就要降到 8imgsz640是默认值但如果病斑目标普遍偏小后面要往上调patience20表示 20 轮没提升就早停。跑完看runs/detect/tomato_blight/下的results.png重点关注train/box_loss和val/box_loss两条曲线——训练损失下降而验证损失上升就是过拟合信号这时候该回头查数据增强或加正则项而不是继续加轮数。4. 番茄叶片病害目标检测的参数与评估调优让模型真正能用4.1 输入尺寸 imgsz 的选择病斑太小640 默认值不顶用YOLOv8 默认输入是 640×640对 COCO 这类常规目标够用但番茄叶病数据有个特殊性病斑尺寸占整张图的比例差异极大。有的病斑只有十几个像素缩放进 640 后几乎消失。我的做法是先统计一下标注框的尺寸分布# 统计数据集中所有标注框的像素尺寸判断是否需要增大imgsz import os from pathlib import Path label_dir Path(dataset/labels/train) img_w, img_h 4000, 3000 # 按实际原始图像尺寸填写 widths, heights [], [] count 0 for txt in label_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) 5: continue w_norm float(parts[3]) h_norm float(parts[4]) widths.append(w_norm * img_w) heights.append(h_norm * img_h) count 1 print(f共{count}个目标平均宽{sum(widths)/len(widths):.1f}px平均高{sum(heights)/len(heights):.1f}px) print(f最小宽{min(widths):.1f}px最大宽{max(widths):.1f}px)如果平均框宽小于 32 像素在 640 分辨率下只有 5 个像素左右病斑的纹理特征根本保留不下来。这时候我一般把imgsz提到 960 或 1024让模型能看到更多细节。代价是显存占用变高、训练变慢所以这个参数本质上是在小目标召回率和训练成本之间做权衡。一个常见做法是先用 640 跑通流程确认代码和数据没问题再用 1024 重新训练一轮对比验证集 mAP 来决定要不要保留大分辨率版本。4.2 学习率与 batch size 的联动一个参数不动另一个必受影响番茄叶病数据集的样本量通常不大几千张已经算多几百张也很常见。样本少时batch size 过大容易让梯度方向过早稳定到局部最优batch size 过小则训练噪声大。我的经验值是8GB 显存下 batch16、lr0 默认 0.01 可以跑通如果 batch 降到 8最好把lr0同步降到 0.005 左右否则收敛曲线会剧烈震荡。ultralytics 的训练参数里还有lrf最终学习率因子和warmup_epochs我一般不动warmup_epochs但会在样本量少于 1000 时把早停patience调大到 30 或 40。因为小数据集的验证损失波动本来就大早停阈值设太紧可能在模型还没稳住时就提前掐断训练。4.3 用混淆矩阵和 PR 曲线反推数据问题训练结束后不要只看 mAP 数字mAP 会把所有类别的表现平均掉单个类别的崩坏被掩盖。我习惯先把confusion_matrix.png调出来看它才是真正暴露数据问题的镜子。举个例子如果早疫病early_blight有 30% 被误判成晚疫病late_blight先不要急着改模型结构回去看标注集里这两类的样本是不是从同样的发病阶段采的。早疫病和晚疫病在病斑颜色、形状上的差异不同时期明显程度不一样——如果采集时恰好都在中期病斑都是褐色的圆斑网络分不清很正常。解决办法不是加网络层数而是补充更多早期或晚期的对照样本或者把容易混淆的两个阶段拆成独立的类目重新标注。PR_curve.png则用来判断置信度阈值怎么设。曲线在召回率 0.8 附近掉得很快说明模型对病斑的置信度普遍不高部署时应把conf阈值从默认 0.25 调低到 0.15宁可多点误报也别漏掉早期病斑。反过来如果曲线很饱满但 precision 偏低就把阈值调到 0.4 以上减少喷药系统被误报触发。5. 番茄叶片病害检测避坑数据、标注与训练中的五个典型翻车点5.1 一个叶片标十几个小框训练时 loss 剧烈震荡现象损失曲线上下跳正常收敛但 mAP 始终上不去。原因标注过碎。一片病叶上可能密集分布几十个针尖大的病斑标注员按照“一个病斑一个框”的标准标了二十多个框而这些框大多数只有十几像素网络很难稳定学习。更糟的是叶片边缘的小病斑在裁剪和缩放时经常被裁掉一半模型学到的是残缺目标。解决修改标注规范为“连片病斑合并、独立病斑至少占叶片面积 2% 才单独标注”。在 voc2yolo 转换脚本里增加一个过滤逻辑归一化后 width 或 height 小于 0.005 的框直接丢弃或者合并到相邻框。这一条处理完loss 曲线立刻平稳不少。5.2 早疫病和晚疫病互相误检混淆矩阵里两块明显偏亮现象验证集 PR 曲线看着不错但部署到真实棚里把晚疫病识别成早疫病且置信度还不低。原因两类病斑在颜色和纹理上确实有重叠区域更关键的是标注时的边界本身模糊。如果标注员不是植物病理专业出身很可能把同一类病斑的不同时期标成两个类别导致网络学到的类别边界是混乱的。解决把原始标注图像交给至少两位有经验的人分别标一遍计算标注一致性。不一致的样本提取出来重新讨论定标。如果实在拿不准就合并类别——只分成“健康”和“得病”两类先把二分类的召回率做到 95% 以上再逐步拆分细类。这个取舍对实际植保场景往往更实用。5.3 训练集背景太干净野外测试直接翻车现象在数据集上 mAP0.5 到了 0.95拿到温室现场一试召回率掉到 0.5 以下。原因很多公开的番茄叶病图像是摘下叶片放在纯色背景上拍的模型实际学到的是“纯色背景上的异常色块”而不是叶片上的病斑纹理。真实场景中背景有土壤、水滴、茎干阴影这些纹理噪声让模型输出大量误检。解决训练数据强制加入 30% 以上的“手持拍摄”或“田间原位拍摄”图像。如果没有现成资源就做背景替换增强把病斑区域抠出来贴到随机田园背景上生成合成样本。这个方法听着像玄学但效果立竿见影——用合成样本扩增后模型对背景干扰的鲁棒性明显提升。5.4 类别极端不平衡健康叶片占 80%模型学成了“太平绅士”现象训练日志显示 val 损失正常但打开每类别的召回率病斑类别只有 0.2。原因健康叶片样本过多模型倾向把所有目标都预测为健康叶因为这样整体 loss 最小。目标检测里的类别不平衡比分类任务更麻烦因为负样本背景和无病斑叶片混在一起。解决优先调整采样策略而不是调损失函数。把健康叶片减到总样本的 40% 以内并确保每类病害至少有 200 个标注实例。如果某些病害类别实在缺样本就在线增强时对该类别单独翻倍采样ultralytics 里可以用fraction配合自定义 sampler或者直接把该类别图像在数据目录里复制一份用同名不同扩展名的方式让网络多“看”几遍。注意复制样本只能做辅助手段最终还是要补数据。5.5 漏标病斑验证集全是漏网之鱼指标虚高却没意义现象PR 曲线异常平滑mAP 达到 0.98但用模型去检测一张明显有病的叶子什么都没框出来。原因训练集里大量病斑压根没标框模型没见过“这里有个病斑”的正样本于是把漏标区域当作背景学习。更隐蔽的是做数据增强翻转和旋转时没有同步翻转标注框导致某些增强后的样本标注错位模型被迫在错误位置学特征。解决跑一遍所有训练图像的标注可视化把images/train和labels/train的框画回图上从前后各挑 20 张肉眼检查。重点看图里明显有褐色病斑但没有任何框的区域。我有个习惯用训练好的模型对训练集做一次推理把置信度低于 0.3 但面积较大的预测结果输出为伪标注人工复核后补入真实标注。这一步等于用模型反哺数据质量能让漏标率显著下降。6. 把番茄叶片检测精度再往上顶小目标增强与难例闭环最后一公里的提升我不靠换更大的模型靠的是难例集闭环。训练几轮后把验证集里所有预测失败漏检或误检的图片单独收集到一个hard_examples目录和原始标注合并后做一轮增量训练。这个做法的底层逻辑是目标检测模型的错误往往集中在数据分布的长尾里随机补数据很难覆盖到这些尾部样本但针对失败样本的定向补强每轮都能看到 mAP 涨一截。另一个被我验证过多次的技巧是切片推理。对于大分辨率原图比如 4000×3000先在原图上做滑动窗口切片每片 1024×1024 重叠 30%分别推理后再把框映射回原图坐标。这个做法对小病斑的召回率提升非常明显因为模型不需要把整张图压缩到 640 再丢失细节。代价是推理时间成倍增加所以我会在部署时分层处理先用整图 640 快速筛查置信度低于 0.3 的区域再触发切片精检。模型部署方面如果目标是手机或边缘盒子用yolo export modelbest.pt formatonnx导出 ONNX再转 TensorRT 或 OpenVINO在 x86 设备上推理速度能从十几毫秒压到三五毫秒。注意导出的imgsz要和训练一致否则检测框位置会整体偏移。这算是目标检测部署的经典坑了。这套流程走下来我的个人习惯是每版训练结束先看混淆矩阵再看困难样本最后才看 mAP 数字。数据集的坑永远在模型之前暴露把这两类问题分开排查效率会高很多。希望这篇笔记能帮你在番茄叶片病害检测这条路上少走几段弯路早点把模型从实验台搬进大棚。本文还有配套的精品资源点击获取
返回列表