ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

农作物病害实例分割数据集:从标注格式到YOLOv8训练落地

农作物病害实例分割数据集:从标注格式到YOLOv8训练落地 简介这份农作物病害实例分割数据集面向农业AI诊断、精准农业监测及植物病理学交叉研究场景适合从事YOLO实例分割任务开发与农业图像算法验证的工程师、科研人员及农业院校师生使用。资源包共582个文件以290张jpg图像与290个txt标注文件为主体另含1个yaml配置和1个docx说明文档压缩包约58.81MB标注采用YOLO多边形格式精确勾勒病害区域边界。数据集总计290张图片按训练集258张、验证集23张、测试集9张划分覆盖细菌性枯萎病、褐条病与花叶病三类典型病害兼顾细菌性与病毒性样本类别代表性较强。已有89人学习关注。读者可借助其直接开展实例分割模型训练与部署用于病害区域自动识别、早期诊断系统搭建及农业物联网实时监测也可作为教学与科研中的标注范例和实验基线降低数据采集与标注成本。1. 农作物病害实例分割数据集从标注格式到 YOLOv8 训练落地的完整路径拿到一个名为农作物病害实例分割数据集_20251117_001317.zip的压缩包第一反应不应该是直接解压丢进模型而是先搞清楚它到底以什么格式标注、类别怎么定义、掩码是多边形还是 RLE。农作物病害识别这件事分类模型只能告诉你「这片叶子有病」目标检测能框出病斑位置但只有实例分割才能精确到每一个病斑的像素边界——这在计算病害面积占比、评估严重程度、做精准施药决策时是刚需。这个数据集面向的就是这类场景叶片上的多个独立病斑需要被逐个分割出来而不是笼统地标一个框。适合已经跑通过 YOLOv8 检测任务、想往分割方向升级的工程师也适合农业 AI 方向需要快速验证实例分割方案可行性的团队。下面从数据集结构拆解开始一步步走到训练、评估和部署。2. 拆开压缩包农作物病害实例分割数据集的目录结构与标注格式判定2.1 先看目录树再决定怎么处理解压之后别急着写训练脚本先花五分钟把目录结构摸清楚。常见的实例分割数据集组织方式有两种COCO 风格的annotations/images/分离结构以及 YOLO 风格的images/labels/平行结构。前者标注文件是单个 JSON后者是每张图对应一个 TXT。# 先看压缩包内文件列表不解压 unzip -l 农作物病害实例分割数据集_20251117_001317.zip | head -50 # 解压到指定目录 mkdir -p ./agri_disease_seg unzip 农作物病害实例分割数据集_20251117_001317.zip -d ./agri_disease_seg # 查看目录树只看两层 find ./agri_disease_seg -maxdepth 2 -type d | sort逻辑说明unzip -l先窥探内容避免解压出一堆无关文件。find -maxdepth 2控制输出深度快速判断是 COCO 还是 YOLO 结构。如果看到annotations/instances.json就是 COCO 风格如果看到labels/下全是.txt就是 YOLO 风格。参数说明-d指定解压目标目录建议单独建目录避免污染当前工作区。-maxdepth根据实际嵌套深度调整一般 2 到 3 层足够判断结构。2.2 判定标注格式多边形、RLE 还是 YOLO 分割行实例分割的标注格式直接决定后续转换脚本怎么写。COCO 格式的segmentation字段可能是多边形点列表[[x1,y1,x2,y2,...]]或 RLERun-Length Encoding。YOLO 分割格式则是每行class_id x1 y1 x2 y2 ...坐标是归一化后的多边形点。import json import os # 如果是 COCO 格式检查 segmentation 字段类型 ann_path ./agri_disease_seg/annotations/instances.json if os.path.exists(ann_path): with open(ann_path, r) as f: coco json.load(f) # 看第一條标注的 segmentation 类型 seg coco[annotations][0][segmentation] if isinstance(seg, list): print(多边形格式点数, len(seg[0]) // 2) elif isinstance(seg, dict): print(RLE 格式size, seg.get(size)) # 统计类别 for cat in coco[categories]: print(cat[id], cat[name]) else: # 检查 YOLO 格式 label_dir ./agri_disease_seg/labels if os.path.exists(label_dir): sample os.listdir(label_dir)[0] with open(os.path.join(label_dir, sample)) as f: line f.readline().strip() parts line.split() print(类别ID:, parts[0], 多边形点数:, (len(parts) - 1) // 2)逻辑说明COCO 的segmentation如果是 list 就是多边形每个子列表是一串x,y交替的点如果是 dict 就是 RLE需要pycocotools解码。YOLO 格式直接读一行就能判断第一个数是类别 ID后面全是归一化坐标。参数说明len(seg[0]) // 2得到多边形顶点数顶点太少比如少于 3 个说明标注质量可能有问题。类别统计用来确认病害种类数量常见的有早疫病、晚疫病、叶斑病等。2.3 类别分布与样本均衡性检查在动手训练之前必须知道每个类别有多少个实例。农作物病害数据集经常出现长尾分布——健康叶片样本多某种罕见病害只有几十个实例。不查清楚就训练模型对少数类基本没反应。from collections import Counter # 假设已加载 COCO 格式 cat_counter Counter() for ann in coco[annotations]: cat_counter[ann[category_id]] 1 id2name {c[id]: c[name] for c in coco[categories]} for cid, cnt in cat_counter.most_common(): print(f{id2name[cid]}: {cnt} 个实例) # 同时统计每张图的实例数判断是否有多病斑同图的情况 img_ann_count Counter() for ann in coco[annotations]: img_ann_count[ann[image_id]] 1 multi sum(1 for v in img_ann_count.values() if v 1) print(f含多个病斑的图像数: {multi} / {len(coco[images])})逻辑说明Counter统计每个类别的实例总数most_common()按数量降序排列。第二个统计看有多少张图包含多个病斑——这个比例高说明实例分割确实有必要检测框会重叠。参数说明如果某个类别实例数少于 100训练时需要过采样或加类别权重。多病斑图像占比超过 30% 就值得做实例分割而不是检测。3. 从 COCO 到 YOLO 分割格式转换脚本与四个边界坑3.1 转换脚本多边形坐标归一化与类别重映射YOLOv8 的分割训练需要 YOLO 格式的标注每张图一个 TXT每行class_id x1 y1 x2 y2 ...坐标是相对于图像宽高的归一化值。COCO 的多边形坐标是绝对像素值需要除以宽高。import json import os from PIL import Image def coco_to_yolo_seg(coco_json, image_dir, output_label_dir): with open(coco_json, r) as f: coco json.load(f) # 建立 image_id - (file_name, width, height) 映射 img_info {} for img in coco[images]: img_info[img[id]] (img[file_name], img[width], img[height]) # 类别 ID 重映射为 0 起始连续整数 cat_ids sorted([c[id] for c in coco[categories]]) cat_id_map {old: new for new, old in enumerate(cat_ids)} # 按 image_id 聚合标注 from collections import defaultdict img_anns defaultdict(list) for ann in coco[annotations]: img_anns[ann[image_id]].append(ann) os.makedirs(output_label_dir, exist_okTrue) for img_id, anns in img_anns.items(): fname, w, h img_info[img_id] label_name os.path.splitext(fname)[0] .txt lines [] for ann in anns: seg ann[segmentation] if not isinstance(seg, list): continue # 跳过 RLE需要单独处理 cls_id cat_id_map[ann[category_id]] for poly in seg: # 归一化并保留 6 位小数 coords [] for i in range(0, len(poly), 2): x min(max(poly[i] / w, 0.0), 1.0) y min(max(poly[i1] / h, 0.0), 1.0) coords.append(f{x:.6f}) coords.append(f{y:.6f}) if len(coords) 6: # 至少 3 个点 lines.append(f{cls_id} .join(coords)) with open(os.path.join(output_label_dir, label_name), w) as f: f.write(\n.join(lines)) print(f转换完成类别映射: {cat_id_map}) coco_to_yolo_seg( ./agri_disease_seg/annotations/instances.json, ./agri_disease_seg/images, ./agri_disease_seg/labels )逻辑说明先建立图像 ID 到文件名和尺寸的映射再把类别 ID 重映射为从 0 开始的连续整数——YOLO 要求类别 ID 必须连续。按图像聚合标注后逐条转换坐标裁剪到[0,1]防止越界。参数说明:.6f保留 6 位小数精度足够且文件不会太大。len(coords) 6确保至少 3 个点退化的多边形直接丢弃。如果遇到 RLE 格式的segmentation需要用pycocotools的annToMask转成二值掩码再提取轮廓。3.2 坑一坐标越界与退化多边形现象转换后的 TXT 里有坐标小于 0 或大于 1训练时报NaN loss或直接崩溃。原因COCO 标注中偶尔有多边形顶点超出图像边界或者标注工具导出的坐标有浮点误差。解决转换时强制裁剪到[0,1]如上面脚本中的min(max(...))。同时过滤掉面积过小的多边形——计算多边形面积小于图像面积 0.1% 的直接丢弃。3.3 坑二类别 ID 不连续导致训练报错现象YOLOv8 训练启动时报AssertionError: class id must be in range [0, nc)。原因COCO 的类别 ID 可能是 1, 3, 7 这种不连续的而 YOLO 要求从 0 开始连续编号。解决转换时做重映射如脚本中的cat_id_map。同时生成data.yaml时names列表的顺序必须和重映射后的 ID 一致。3.4 坑三图像与标签文件名不匹配现象训练时提示No labels found但labels/目录下明明有文件。原因图像是.jpg标签是.txt但文件名前缀不一致——比如图像叫IMG_001.jpg标签叫img_001.txt大小写或前缀不同。解决转换脚本中用os.path.splitext(fname)[0]统一取文件名主干确保一一对应。转换后跑一遍校验脚本检查每个图像是否有对应标签。4. YOLOv8 实例分割训练data.yaml 配置与关键超参设置4.1 data.yaml 的五个必填字段YOLOv8 分割训练的配置文件比检测多一个task字段且names必须与类别数严格对应。# data.yaml path: ./agri_disease_seg # 数据集根目录 train: images/train # 训练集图像相对路径 val: images/val # 验证集图像相对路径 test: images/test # 测试集可选 task: segment # 关键指定分割任务 nc: 5 # 类别数根据实际修改 names: # 类别名称顺序必须与训练标签中的 ID 一致 0: 早疫病 1: 晚疫病 2: 叶斑病 3: 锈病 4: 健康逻辑说明path是根目录train和val是相对于根目录的路径。task: segment告诉 YOLOv8 加载分割头而不是检测头。names用字典或列表都行但顺序不能错。参数说明nc必须等于names的长度。如果类别数超过 10建议用列表形式写names更紧凑。test字段可选不写就不做测试集评估。4.2 训练命令与 batch size 的显存估算# 单卡训练imgsz 根据图像实际尺寸调整 yolo segment train \ data./data.yaml \ modelyolov8n-seg.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ projectagri_seg \ nameexp1逻辑说明modelyolov8n-seg.pt加载预训练的分割模型n 是最小版本显存不够就换 n够就换 s 或 m。imgsz640是输入尺寸如果原始图像是 1024 以上可以设 1024 但显存翻倍。patience20表示 20 轮验证指标不提升就早停。参数说明batch16在 8GB 显存上跑 640 尺寸的 n 模型基本够用。如果 OOM先降 batch 到 8再降 imgsz 到 512。lr00.01是初始学习率分割任务通常比检测稍小0.005 到 0.01 之间比较稳。4.3 训练过程监控看哪些指标、什么时候该停训练启动后终端会输出每轮的box_loss、seg_loss、cls_loss和mAP50。分割任务重点看seg_loss和mask mAP50。# 训练完成后查看结果 ls agri_seg/exp1/ # 关键文件results.csv每轮指标、weights/best.pt最佳权重、 # confusion_matrix.png混淆矩阵、val_batch0_pred.jpg验证集预测可视化逻辑说明results.csv可以用 pandas 读取画曲线重点看metrics/mAP50(M)是否还在涨。如果seg_loss持续下降但mAP50不涨说明过拟合需要加数据增强或减模型复杂度。参数说明mAP50(M)是掩码 IoU 阈值 0.5 时的平均精度分割任务的核心指标。mAP50-95(M)更严格但农作物病害场景下 0.5 就够用。5. 避坑与排查农作物病害分割训练中最容易翻车的五个地方5.1 坑一验证集 mAP 很高但实际预测全是背景现象训练日志里mAP50(M)到了 0.8但拿新图预测时模型输出全是背景没有任何掩码。原因验证集和训练集来自同一批图像分布太相似。模型学会了「猜」验证集的类别分布但没有真正学到病斑的视觉特征。解决手动划分验证集时确保不同批次、不同光照条件的图像分开。如果数据集本身多样性不足用albumentations做离线增强扩充训练集。预测时降低置信度阈值试试yolo segment predict conf0.1。5.2 坑二小病斑被完全忽略现象大块病斑分割得很准但直径小于 20 像素的早期病斑一个都检不出来。原因YOLOv8 的 P3 特征图 stride 是 8对应 640 输入下最小感受野约 8 像素。小于这个尺寸的目标在浅层特征中信息太少。解决把imgsz提到 1024 或 1280让 P3 特征图覆盖更多像素。或者在数据增强中加copy_paste把小病斑复制到其他位置增加样本。如果显存不够用yolov8s-seg换yolov8n-seg小模型的特征提取能力更弱。5.3 坑三掩码边缘锯齿严重现象预测出的病斑掩码边缘呈锯齿状和真实边界差距大。原因YOLOv8 分割头输出的掩码原型分辨率是 160x160输入 640 时上采样到原图尺寸时细节丢失。解决训练时设overlap_maskTrue默认开启推理时用retina_masksTrue获得更高分辨率的掩码。如果还不够换yolov8x-seg或yolov8l-seg大模型的原型掩码通道更多。5.4 坑四类别不平衡导致少数类完全学不到现象早疫病和晚疫病样本多mAP 正常锈病只有几十个实例mAP 接近 0。解决在data.yaml同级目录建一个hyp.yaml设置cls_pw1.0开启类别权重。或者用WeightedRandomSampler做数据重采样。最直接的办法是离线过采样把少数类的图像复制多份文件名加后缀区分。5.5 坑五训练 loss 震荡不收敛现象seg_loss在 0.5 到 2.0 之间反复跳始终不下降。原因学习率太大或者 batch size 太小导致梯度噪声大。解决先把lr0降到 0.001 试 10 轮。如果还震荡把batch翻倍显存允许的话。另外检查数据标注是否有大量错误——用yolo segment train的--verbose模式看每批数据的损失分布异常高的批次对应的图像大概率标注有问题。6. 掩码后处理与面积计算把分割结果变成可用的病害评估指标6.1 从预测掩码到病斑面积占比训练完模型只是第一步实际业务要的是「这片叶子上病斑占了多少面积」。YOLOv8 预测返回的masks是二值掩码直接算像素占比就行。from ultralytics import YOLO import numpy as np import cv2 model YOLO(agri_seg/exp1/weights/best.pt) results model.predict(test_leaf.jpg, conf0.25, retina_masksTrue) for r in results: if r.masks is None: print(未检测到病斑) continue # masks.data 形状 [N, H, W]N 是检测到的实例数 masks r.masks.data.cpu().numpy() img_h, img_w r.orig_shape total_pixels img_h * img_w for i, mask in enumerate(masks): # 调整掩码到原图尺寸 mask_resized cv2.resize(mask, (img_w, img_h), interpolationcv2.INTER_NEAREST) area_ratio mask_resized.sum() / total_pixels cls_name r.names[int(r.boxes.cls[i])] print(f病斑 {i}: 类别{cls_name}, 面积占比{area_ratio:.4f})逻辑说明retina_masksTrue让预测返回原图分辨率的掩码避免手动上采样。r.masks.data是[N, H, W]的张量每个通道对应一个实例的二值掩码。cv2.resize用最近邻插值保持二值性。参数说明conf0.25是置信度阈值农作物病害场景可以降到 0.15 提高召回。area_ratio就是病斑面积占比超过 0.3 通常意味着严重感染。6.2 多病斑合并与严重度分级一张叶子上可能有多个同类病斑业务上需要合并计算总感染面积。# 按类别合并掩码 from collections import defaultdict cls_masks defaultdict(list) for i, mask in enumerate(masks): cls_name r.names[int(r.boxes.cls[i])] cls_masks[cls_name].append(mask) for cls_name, mask_list in cls_masks.items(): # 逻辑或合并同类掩码 merged np.zeros_like(mask_list[0]) for m in mask_list: merged np.logical_or(merged, m 0.5) ratio merged.sum() / total_pixels # 分级 if ratio 0.05: level 轻微 elif ratio 0.15: level 中度 elif ratio 0.30: level 重度 else: level 极重 print(f{cls_name}: 总面积占比{ratio:.4f}, 严重度{level})逻辑说明np.logical_or合并同类掩码避免重叠区域重复计算。分级阈值根据实际业务调整这里给的是通用参考值。参数说明m 0.5把概率掩码二值化。分级阈值需要和农学专家确认不同作物的耐受度不同。6.3 一个容易忽略的细节掩码面积计算时的图像预处理还原如果推理时用了imgsz640但原图是 2048x2048YOLOv8 内部会缩放。retina_masksTrue会自动还原到原图尺寸但如果你手动处理masks.data必须自己 resize 回去。我一般直接在predict里加retina_masksTrue省掉这一步。另外注意r.orig_shape返回的是(height, width)顺序别搞反了。这套流程跑下来从拿到压缩包到算出病斑面积占比顺利的话半天能跑通。最耗时间的往往是标注格式转换和验证集划分——这两步做扎实后面训练基本不会出大问题。我自己的习惯是转换完先拿 10 张图可视化检查一遍确认掩码和原图对齐了再启动训练能省下大量排查时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表