资讯动态

植物病害检测数据集处理与YOLOv8训练全流程指南

发布时间:2026/9/24 18:17:41 来源:尧图企业网站定制
简介这份资源是面向计算机视觉与农业AI方向的植物病害检测数据集适合从事图像分类、目标检测研究的学生、算法工程师及竞赛选手使用。数据集源自PlantDoc项目旨在解决非实验室环境下植物病害图像稀缺、标注成本高的问题覆盖13种植物、17类病害可支撑早期病害识别与可扩展检测建模。压缩包共约2000个文件整体948.81MB以jpg图像与同名xml标注文件为主另含csv汇总表、md说明文档及少量png、jpeg样本图像与标注一一对应便于直接用于分类或检测任务训练。目前已有2053人学习下载。数据集包含约300人时的人工标注实验表明使用该数据建模可将分类准确率提升多达31%读者可据此复现基线模型、开展迁移学习或扩充自有数据降低视觉病害检测的入门门槛。1. 拿到一个植物病害数据集压缩包先别急着解压你从网上拖下来一个「用于视觉植物病害检测的数据集标记(包含2000多张图片).zip」双击解压看到一堆文件夹和几个标注文件然后呢很多人卡在这一步图片能看标记看不懂想拿去训练 YOLOv8 或者做分类发现格式对不上类别名对不上train/val 也没分。这个标题背后真正要解决的问题不是「有没有数据」而是「怎么把这份带标记的视觉植物病害检测数据集变成能直接喂给模型训练的标准格式」。2000 多张图片在目标检测里属于小数据集但植物病害检测这个场景有个特点病斑区域往往边界模糊、类间差异小比如早疫病和晚疫病在叶片上的表现很接近所以标记质量比数量更关键。这份数据适合谁适合想跑通「农业视觉检测」全流程的工程师、做课程设计的学生、以及需要快速验证病害识别方案可行性的团队。它不适合直接拿来冲 SOTA但足够你把数据清洗、格式转换、增强、训练、评估这条链路完整走一遍。我一般拿到这种压缩包第一件事不是解压到桌面而是先看目录结构和标注文件格式因为后面所有操作都取决于标注是 VOC XML、COCO JSON 还是 YOLO txt。这一步判断错了后面全是返工。2. 先摸清数据集的目录结构与标记格式三种主流标注怎么认2.1 解压后先跑一遍目录树和文件计数不要用鼠标一层层点直接上命令行。下面这段脚本会告诉你图片总数、标注文件总数、以及它们是否一一对应。# 查看压缩包内文件列表不解压 unzip -l 用于视觉植物病害检测的数据集标记(包含2000多张图片).zip | head -50 # 解压到指定目录 unzip 用于视觉植物病害检测的数据集标记(包含2000多张图片).zip -d ./plant_disease_data # 统计图片和标注文件数量 cd plant_disease_data find . -iname *.jpg -o -iname *.png -o -iname *.jpeg | wc -l find . -iname *.xml -o -iname *.json -o -iname *.txt | wc -l逻辑说明unzip -l先看结构避免解压出一堆嵌套压缩包。find用-iname忽略大小写因为很多数据集里.JPG和.jpg混着来。如果图片数和标注数差很多说明要么有负样本无标注图片要么标注文件命名和图片没对齐。参数说明-d指定解压目录别解压到当前目录否则和你的代码混在一起。wc -l统计行数就是文件数。2.2 三种标注格式的识别方法与转换优先级植物病害检测数据集常见的标记格式就三种识别方法如下表格式文件特征打开后长什么样转换目标VOC XML每张图一个.xml含objectname病斑/namebndbox转 YOLO txtCOCO JSON一个大的.json含images、annotations、categories转 YOLO txtYOLO txt每张图一个.txt每行class x_center y_center w h归一化直接用判断方法看标注文件扩展名和内容。如果是一堆.xml基本是 VOC如果只有一个annotations.json或instances.json是 COCO如果是一堆.txt且每行 5 个数字已经是 YOLO 格式。我一般优先转成 YOLO txt因为 YOLOv8 原生吃这个格式而且 txt 文件小、读取快。COCO 转 YOLO 要注意bbox是[x_min, y_min, width, height]绝对坐标需要先转成中心点归一化坐标。import json import os def coco_to_yolo(coco_json_path, output_dir, img_width640, img_height640): 将 COCO 格式标注转为 YOLO txt 注意这里假设所有图片尺寸一致实际应按每张图真实尺寸归一化 with open(coco_json_path, r, encodingutf-8) as f: data json.load(f) # 建立 image_id 到文件名的映射 img_id_to_name {img[id]: img[file_name] for img in data[images]} # 建立 category_id 到连续索引的映射YOLO 要求从 0 开始 cat_id_to_idx {cat[id]: idx for idx, cat in enumerate(data[categories])} os.makedirs(output_dir, exist_okTrue) for ann in data[annotations]: img_name img_id_to_name[ann[image_id]] txt_name os.path.splitext(img_name)[0] .txt txt_path os.path.join(output_dir, txt_name) x, y, w, h ann[bbox] # 转中心点归一化 x_center (x w / 2) / img_width y_center (y h / 2) / img_height w_norm w / img_width h_norm h / img_height cls_idx cat_id_to_idx[ann[category_id]] with open(txt_path, a, encodingutf-8) as f: f.write(f{cls_idx} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n) print(f转换完成类别映射{cat_id_to_idx})逻辑说明COCO 的bbox是左上角坐标加宽高YOLO 要的是中心点坐标加宽高且全部除以图片宽高做归一化。cat_id_to_idx这一步很关键因为 COCO 的类别 id 可能不是从 0 连续开始的而 YOLO 要求类别索引必须从 0 开始连续。参数说明img_width和img_height如果所有图片尺寸一致可以直接传不一致的话需要读每张图的实际尺寸用 PIL 或 OpenCV 读一下。我一般会先跑一遍identify或者 Python 脚本确认尺寸分布。2.3 类别名映射别让「病斑」和「病害」混在一个索引里植物病害数据集的类别名经常不统一比如有的标「early_blight」有的标「早疫病」有的标「病斑」。转 YOLO 之前必须统一成英文小写、下划线分隔的类别名并生成data.yaml。# 生成 YOLOv8 需要的 data.yaml import yaml class_names [early_blight, late_blight, healthy, leaf_spot] # 按你的实际类别改 data_config { path: os.path.abspath(./plant_disease_data), train: images/train, val: images/val, nc: len(class_names), names: class_names } with open(data.yaml, w, encodingutf-8) as f: yaml.dump(data_config, f, allow_unicodeTrue, default_flow_styleFalse)逻辑说明nc是类别数names是类别名列表顺序必须和标注文件里的类别索引一致。path写绝对路径避免训练时找不到文件。参数说明allow_unicodeTrue防止中文类别名被转义。如果你的类别名有中文建议在data.yaml里保留中文但训练时 YOLO 会按索引读不影响。3. 把 2000 张图切成 train/val 并做病害场景增强3.1 分层抽样切分别让某个病害类别在验证集里消失2000 多张图如果随机切 8:2很可能某个稀有病害类别在验证集里一张都没有。正确做法是按类别分层抽样。import os import shutil import random from collections import defaultdict def split_dataset(img_dir, txt_dir, output_dir, val_ratio0.2, seed42): 按类别分层切分保证每个类别在 train/val 中都有 random.seed(seed) # 收集每个类别对应的图片 cls_to_imgs defaultdict(list) for txt_file in os.listdir(txt_dir): if not txt_file.endswith(.txt): continue img_name os.path.splitext(txt_file)[0] # 找对应图片 for ext in [.jpg, .jpeg, .png]: img_path os.path.join(img_dir, img_name ext) if os.path.exists(img_path): break else: continue # 读 txt 第一行的类别索引 with open(os.path.join(txt_dir, txt_file), r) as f: first_line f.readline().strip() if not first_line: continue cls_idx first_line.split()[0] cls_to_imgs[cls_idx].append((img_path, os.path.join(txt_dir, txt_file))) train_imgs, val_imgs [], [] for cls_idx, items in cls_to_imgs.items(): random.shuffle(items) n_val max(1, int(len(items) * val_ratio)) val_imgs.extend(items[:n_val]) train_imgs.extend(items[n_val:]) # 复制文件 for split, items in [(train, train_imgs), (val, val_imgs)]: img_out os.path.join(output_dir, images, split) lbl_out os.path.join(output_dir, labels, split) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for img_path, txt_path in items: shutil.copy(img_path, os.path.join(img_out, os.path.basename(img_path))) shutil.copy(txt_path, os.path.join(lbl_out, os.path.basename(txt_path))) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)}) split_dataset(./plant_disease_data/images, ./plant_disease_data/labels, ./plant_disease_data)逻辑说明先按类别分组每个类别内部随机打乱后按比例切分保证每个类别在验证集里至少有一张max(1, ...)。复制而不是移动保留原始数据方便后面重新切分。参数说明val_ratio0.2是验证集比例2000 张图大概 400 张验证。seed42固定随机种子保证可复现。3.2 病害检测的增强策略颜色抖动比几何变换更重要植物病害检测有个特殊性病斑的颜色和纹理是核心特征所以增强时颜色抖动HSV 调整比随机旋转更有效。但颜色抖动幅度不能太大否则健康叶片可能被抖成病斑颜色。# YOLOv8 训练时的增强参数写在训练命令里 # 关键参数hsv_h0.015, hsv_s0.7, hsv_v0.4, degrees10, translate0.1, scale0.5逻辑说明hsv_h是色调抖动植物病害场景建议调小到 0.015因为色调变化太大会让病斑颜色失真。hsv_s饱和度抖动可以稍大0.7 左右模拟不同光照条件。degrees10旋转角度不要太大叶片方向在自然场景中基本固定。参数说明这些参数在 YOLOv8 训练时通过命令行传入比如yolo train modelyolov8n.pt datadata.yaml epochs100 hsv_h0.015 hsv_s0.7。如果发现验证集 mAP 波动大先把hsv_h降到 0.01 试试。3.3 用 YOLOv8 跑通第一轮训练最小命令与显存控制数据切好、data.yaml写好之后第一轮训练不要直接上大模型。用yolov8n.pt先跑通确认数据管道没问题。# 最小训练命令 yolo train modelyolov8n.pt datadata.yaml epochs50 imgsz640 batch16 device0 # 如果显存不够降 batch 或 imgsz yolo train modelyolov8n.pt datadata.yaml epochs50 imgsz416 batch8 device0逻辑说明yolov8n是最小的模型2000 张图用 nano 模型足够跑通。imgsz640是默认输入尺寸如果图片本身分辨率低可以降到 416。batch16在 8GB 显存上一般能跑不够就降到 8。参数说明device0指定第一块 GPUCPU 训练把device改成cpu但会很慢。epochs50先看 loss 曲线是否正常下降正常后再加到 100 或 200。4. 标记质量排查2000 张图里藏着的五类坑4.1 坑一标注框超出图片边界现象训练时 loss 突然变成 NaN或者验证集 mAP 为 0。原因VOC 或 COCO 转 YOLO 时x_center或y_center算出来小于 0 或大于 1。解决转换后加一步校验把越界框裁掉或丢弃。def validate_yolo_labels(label_dir): 检查 YOLO txt 中是否有越界坐标 bad_files [] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r) as f: for line_num, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: bad_files.append((txt_file, line_num, 字段数不对)) continue cls, x, y, w, h parts x, y, w, h float(x), float(y), float(w), float(h) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_files.append((txt_file, line_num, f越界: {x},{y},{w},{h})) print(f发现 {len(bad_files)} 个问题) for item in bad_files[:20]: print(item) return bad_files逻辑说明YOLO 格式要求所有坐标归一化到 0~1 之间宽高必须大于 0。越界框通常是因为原图尺寸和标注时用的尺寸不一致。参数说明label_dir是 YOLO txt 所在目录。发现越界后要么重新按正确尺寸归一化要么直接丢弃这些框。4.2 坑二图片和标注文件名大小写不一致现象训练时提示找不到标签文件但明明有。原因Linux 区分大小写Leaf_001.jpg和leaf_001.txt对不上。解决统一转小写。# 批量重命名图片和标注为小写 for f in *; do mv $f $(echo $f | tr A-Z a-z); done逻辑说明tr A-Z a-z把大写转小写。在图片和标注目录各跑一遍。参数说明跑之前先备份重命名不可逆。4.3 坑三类别索引不连续现象训练时提示Class index out of range。原因COCO 的category_id可能是 1、3、7转 YOLO 时没重新映射成 0、1、2。解决用enumerate重新生成连续索引前面 COCO 转 YOLO 的代码已经处理了。4.4 坑四同一张图有多个标注但 txt 文件被覆盖现象一张图明明有 3 个病斑但 txt 里只有 1 行。原因写 txt 时用了w模式而不是a每次覆盖。解决用追加模式或者先收集所有标注再一次性写入。4.5 坑五验证集里出现训练集图片现象验证集 mAP 高得离谱但实际测试效果差。原因切分时没去重同一张图既在 train 又在 val。解决切分前先对图片做 MD5 去重。import hashlib def file_md5(path): with open(path, rb) as f: return hashlib.md5(f.read()).hexdigest() # 去重示例 seen {} for img in os.listdir(img_dir): md5 file_md5(os.path.join(img_dir, img)) if md5 in seen: print(f重复: {img} 和 {seen[md5]}) else: seen[md5] img逻辑说明MD5 相同就是同一张图不管文件名是否一样。发现重复后只保留一张。参数说明hashlib.md5对大图也很快2000 张图几秒钟跑完。5. 从 2000 张到可用模型小数据集的进阶技巧2000 张图训练 YOLOv8如果直接跑 200 轮大概率过拟合。我一般会做两件事一是用预训练权重做迁移学习二是用交叉验证选最佳轮次。迁移学习不用多说yolov8n.pt本身就是在 COCO 上预训练的加载它就能用。关键是冻结层数前 10 层是 backbone植物病害和 COCO 的差异较大我一般只冻结前 5 层让后面的层微调。# 冻结前 5 层训练 yolo train modelyolov8n.pt datadata.yaml epochs100 freeze5 lr00.001freeze5表示冻结前 5 层lr00.001是初始学习率比默认的 0.01 小因为微调不需要太大学习率。交叉验证在小数据集上很有用但 YOLOv8 没有内置 k-fold需要自己写。我一般用 5 折每折跑 50 轮看哪一折的 mAP 最高然后用那一折的轮次在全量数据上重训。# 5 折切分示例在分层切分基础上改 from sklearn.model_selection import KFold # 假设 all_items 是 [(img_path, txt_path), ...] kf KFold(n_splits5, shuffleTrue, random_state42) for fold, (train_idx, val_idx) in enumerate(kf.split(all_items)): train_items [all_items[i] for i in train_idx] val_items [all_items[i] for i in val_idx] # 复制到 fold_{fold}/images/train 等目录 # 然后跑 yolo train逻辑说明KFold把数据分成 5 份每次用 4 份训练、1 份验证。跑完 5 折后看哪一折的mAP50最高记录对应的epoch数。参数说明n_splits5是折数2000 张图每折 400 张验证足够评估。random_state42固定随机种子。还有一个技巧是测试时增强TTA在验证时开启augmentTrue能提升 1~2 个点的 mAP但推理速度会慢一倍。如果部署环境对速度不敏感可以开。# 验证时开启 TTA yolo val modelruns/detect/train/weights/best.pt datadata.yaml augmentTrue最后说一个我踩过的坑植物病害数据集的标注框经常把整个叶片框进去而不是只框病斑。这种标注训练出来的模型会倾向于检测叶片而不是病斑。如果发现模型在健康叶片上也输出高置信度先检查标注框是不是太大了。我一般会统计一下标注框面积占图片面积的比例如果大部分超过 50%说明标注有问题需要重新标或者用分割模型。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价