ARTICLE DETAIL

资讯详情

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

苹果树叶病害数据集:VOC/YOLO/JSON格式转换与YOLOv8训练全攻略

苹果树叶病害数据集:VOC/YOLO/JSON格式转换与YOLOv8训练全攻略 简介面向智慧农业与病害智能识别场景的苹果树叶病害检测数据集聚焦花叶病、斑点落叶病等常见叶部病害适合课程设计、算法竞赛及实际项目开发使用覆盖从入门实践到算法调优的常见需求。压缩包共3665个文件约84.45MB包含916张jpg原图及916个xml、916个json、917个txt标签分别对应VOC格式、通用标注格式和YOLO格式主流检测框架可按需读取显著减少格式转换成本。图片背景较丰富目标有大有小、角度多样纯手工标注带来精准的边界框整体类别分布与场景多样性有利于提升模型泛化能力。已有1208人学习下载可支撑病害识别App或智慧农业系统的快速原型验证也可作为课程实验、竞赛作品和论文对比实验的可靠数据基础。1. 苹果树叶病害数据集916张图值不值得下先看三种标注格式标题里写着三种病害后面只列出花叶病、斑点落叶病两个名字第三类具体是什么解压后打开标签清单才能见分晓。这套数据一共916张苹果树叶图像同一批标注同时给出VOC、YOLO、JSON三种格式表面看是“多送了两份标签”实际上这三套标签恰好覆盖了目标检测从业者最常用的三种工具链。正在做农业病害识别、需要练手YOLO训练或者想搞懂标注格式转换的人这份数据可以直接拿来跑通从数据准备到训练的完整流程不用自己从零标注一张图。2. 拆开压缩包看标注格式VOC XML、YOLO TXT与JSON的坐标差异2.1 解压后的目录结构图像和三种标签怎么对应拿到这类打包资源我第一件事不是点开文件夹看图片而是先用命令行把目录结构铺开。常见做法是这样unzip apple_leaf_diseases.zip -d apple_leaf cd apple_leaf tree -L 2解压之后应该能看到类似下面这种层次结构不同版本打包在子目录名上可能有差异但大体逃不出这个框架JPEGImages/存放916张苹果树叶图像以jpg格式为主Annotations/VOC格式的XML标签一张图像对应一个同名XMLlabels/YOLO格式的TXT标签也可能叫YOLOLabelsannotations.json或json/JSON格式标签全量标注集中在一个文件里这类打包里图像文件名和标签文件名高度同构一般规律是apple_mosaic_001.jpg对应apple_mosaic_001.xml后缀不同、前缀完全一致。别小看这个规律后面写校验脚本、做train/val划分全靠这份同名匹配关系跑批量处理。还要注意一个容易忽略的目录压缩包在Mac系统下打包时会多出__MACOSX目录和一堆._开头的隐藏文件前者是系统残留后者是原始文件的元数据副本跟数据集内容没有关系。解压后如果发现标签数量比图像数量多先别急着怀疑数据有问题多半是这些隐藏文件混进来凑数了。2.2 VOC XML和JSON标签基于像素的坐标体系VOC格式的标签长这样一个XML文件描述一张图里的所有目标框annotation folderJPEGImages/folder filenameapple_mosaic_001.jpg/filename size width640/width height480/height depth3/depth /size object nameapple_mosaic/name bndbox xmin120/xmin ymin80/ymin xmax520/xmax ymax420/ymax /bndbox /object /annotation这里所有坐标都是原始像素值size节点记录了图片的真实宽高。判断一个目标在图像中的位置直接取xmin、ymin、xmax、ymax就行不用做任何换算。同一份数据的JSON标签通常按COCO风格组织bbox字段长度是4分别代表左上角x、左上角y、框宽、框高同样是基于像素的坐标系。区别在于JSON把整个数据集的标注集中到一个文件里而VOC是每张图一个XML。这种“全量标注集中存放”的结构对接pycocotools这类评估工具特别方便。提示VOC和JSON的坐标都基于原始图像像素。后续做数据增强、缩放时这两套坐标需要跟着图像同步变换否则标注会漂移。2.3 YOLO TXT标签归一化坐标才是训练时直接喂的数据YOLO格式的标签完全不同每一行代表一个目标框格式是“类别ID x_center y_center width height”坐标全部归一化取值在0到1之间2 0.3436 0.4637 0.4612 0.4757这一行数字的含义是类别ID为2目标框中心点在图像宽度方向的0.3436处、高度方向的0.4637处框宽度占整张图的0.4612框高度占0.4757。相比VOC的像素坐标归一化坐标的最大优势是不依赖图像分辨率——训练时不管输入512还是640标签都不用改模型在自己的缩放逻辑里解析同一套0到1的坐标。从VOC像素坐标换算成YOLO归一化坐标是这四条公式x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height拿上面XML里的例子验证一下xmin120, xmax520图片宽640那么x_center(120520)/2/6400.5width(520-120)/6400.625。这个手算过程能用来验证转换脚本算得对不对也能检查异常。2.4 三种格式的对应关系与常用转换方向三种格式不是三份独立标注而是同一批标注的三种表达方式互相转换只差一层公式。进行苹果树叶病害检测时最常见的路线是把VOC或JSON转成YOLO的TXT格式喂给主流目标检测框架。格式存储单位坐标基准结构典型用途VOC XML每图一文件像素 (xmin, ymin, xmax, ymax)树状XMLFaster R-CNN、SSD、VOC评估JSON全量一文件像素COCO bbox字典数组COCO工具链、pycocotoolsYOLO TXT每图一文件归一化 (x_center, y_center, w, h)纯文本行YOLOv5/v8训练转换逻辑本身不复杂但批量跑起来路径、类别顺序、越界处理这些细节很容易出问题。下一章我用完整脚本演示一遍从校验到转换的全过程。3. 从压缩包到可训练数据集完整性校验、VOC转YOLO脚本与数据集划分3.1 先做完整性校验图像和标签数量必须一一对应做过数据整理的人都知道解压完直接开训是大忌。训练跑到一半报错很大比例是数据完备性问题。我这里的第一步是先用Python扫一遍目录。import os # 路径参数改成你解压后的实际路径 img_dir ./JPEGImages ann_dir ./Annotations imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] xmls [f for f in os.listdir(ann_dir) if f.lower().endswith(.xml)] # 取文件名主干做同名匹配例如 apple_mosaic_001 img_names set(os.path.splitext(f)[0] for f in imgs) xml_names set(os.path.splitext(f)[0] for f in xmls) print(图像数量:, len(imgs)) print(XML数量:, len(xmls)) print(有图无标:, len(img_names - xml_names)) # 空样本会拖累训练 print(有标无图:, len(xml_names - img_names)) # 会引发读取异常逻辑说明脚本把图像和XML的文件名主干分别取出来做成集合再用集合差集算出两边对不上的样本。有图无标指的是图像没有对应标注这类样本丢进训练集等于教模型“这张图没有任何目标”有标无图更严重训练代码按文件名读取图像时文件不存在直接抛异常。正常情况这两个差值都应该是0一旦不为0就得先把数据集补齐而不是急着训练。集合差集的操作复杂度是O(n)916张图的规模刷一下就出结果。3.2 批量把VOC XML转成YOLO TXT核心脚本与参数说明确认图像和标签对得上之后做格式转换。之所以要转是因为YOLOv8、YOLOv5的原生训练接口只认TXT格式的归一化标签直接把XML丢进去是不行的。下面这个脚本是完整可跑的版本。import os import xml.etree.ElementTree as ET from PIL import Image # 路径参数按你的实际目录改这四项 src_xml_dir ./Annotations src_img_dir ./JPEGImages dst_txt_dir ./labels os.makedirs(dst_txt_dir, exist_okTrue) # 类别清单列表顺序即类别ID从0开始 # 注意第三个类别名称是占位写法务必以压缩包内标签清单为准 classes [apple_mosaic, apple_alternaria, apple_rust] def get_image_size(img_path): 读取图片宽高用于像素坐标归一化 with Image.open(img_path) as img: return img.width, img.height def convert_one_xml(xml_path, img_path, txt_path): tree ET.parse(xml_path) root tree.getroot() img_w, img_h get_image_size(img_path) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: print(f跳过未知类别: {name} in {xml_path}) continue cls_id classes.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 w (xmax - xmin) / img_w h (ymax - ymin) / img_h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 遍历XML目录批量转换 for xml_name in os.listdir(src_xml_dir): if not xml_name.endswith(.xml): continue stem os.path.splitext(xml_name)[0] xml_path os.path.join(src_xml_dir, xml_name) img_path os.path.join(src_img_dir, stem .jpg) txt_path os.path.join(dst_txt_dir, stem .txt) if not os.path.exists(img_path): print(f图片缺失: {img_path}) continue convert_one_xml(xml_path, img_path, txt_path) print(转换完成输出目录:, dst_txt_dir)逻辑说明核心是convert_one_xml函数先读取XML里的size和object再把每个目标框的像素坐标套归一化公式写成TXT行。classes列表顺序就是类别ID顺序必须和后续训练时的data.yaml保持完全一致否则模型会把病害类别学串。参数说明src_xml_dir、src_img_dirVOC标签和原图的路径解压后在哪就写哪。dst_txt_dirYOLO TXT标签的输出目录脚本会自动创建。classes类别清单把压缩包内标签文件里能看到的类别名按顺序填进来。填错顺序的后果是类别ID错位模型能收敛但识别结果对不上真实病种。转换里加了min(max(...))裁剪这是防呆做法个别标注框如果标出图像边界归一化后会出现大于1或小于0的值这种值丢给YOLO训练会直接报错裁剪到边界至少能让训练先跑起来后续再定位具体是哪张图的人工标注问题。3.3 划分train/val并生成训练集目录916张的规模不算大按8:2划分比较常见训练约730张、验证约180张。小数据集上验证集占比太低会导致评估波动大所以不建议用默认的9:1样本量不足时验证集至少留20%。import os import random import shutil random.seed(42) img_dir ./JPEGImages txt_dir ./labels train_img_dir ./images/train val_img_dir ./images/val train_txt_dir ./labels/train val_txt_dir ./labels/val for d in [train_img_dir, val_img_dir, train_txt_dir, val_txt_dir]: os.makedirs(d, exist_okTrue) all_files [f for f in os.listdir(img_dir) if f.lower().endswith(.jpg)] random.shuffle(all_files) val_count int(len(all_files) * 0.2) val_files all_files[:val_count] train_files all_files[val_count:] for f in train_files: stem os.path.splitext(f)[0] shutil.copy2(os.path.join(img_dir, f), os.path.join(train_img_dir, f)) shutil.copy2(os.path.join(txt_dir, stem .txt), os.path.join(train_txt_dir, stem .txt)) for f in val_files: stem os.path.splitext(f)[0] shutil.copy2(os.path.join(img_dir, f), os.path.join(val_img_dir, f)) shutil.copy2(os.path.join(txt_dir, stem .txt), os.path.join(val_txt_dir, stem .txt)) print(train:, len(train_files), val:, len(val_files))逻辑说明先把所有图片文件名列出来按种子42打乱取前20%作为验证集剩下80%作为训练集然后用shutil.copy2把图像和对应TXT标签复制到按images/train、images/val、labels/train、labels/val组织的目录里。这个结构正是YOLOv8官方data.yaml默认期望的布局放在原压缩包之外操作原始资源不被破坏。random.seed(42)这一行别删。不固定随机种子每次划分结果都不同训练复现和效果对比都没法做。为什么用42这个数字没有任何玄学只是约定俗成的习惯值保证大家复现时划分结果一致。注意划分之后的图像和标签目录必须保持同名对应关系。图像进images/train标签就必须进labels/train两边目录名要一字不差。这是YOLO训练时最常见也最隐蔽的配置错误。4. 用YOLOv8把苹果树叶病害数据训起来Anaconda环境、data.yaml与训练参数4.1 YOLOv8的Anaconda环境配置先装环境。我一般用Anaconda建独立环境避免跟其他项目抢依赖Python版本选3.10Ultralytics对它的兼容性比较稳。conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu118依赖装完后用一行代码确认GPU是否真的可用python -c import torch; print(torch.cuda.is_available())输出True说明GPU可用后面训练能走CUDA加速输出False就得检查NVIDIA驱动、CUDA版本和PyTorch版本之间的匹配关系。这点经常被忽略——ultralytics装好了但torch装成了CPU版本训练速度慢到怀疑人生问题不在数据集在环境。4.2 data.yaml写对三个关键点YOLOv8通过YAML文件描述数据集路径、类别数量和类别名。这里最容易出错的不是格式而是内容跟数据不匹配。path: /home/you/apple_leaf # 数据集的绝对路径 train: images/train # 相对于path的训练图像目录 val: images/val # 相对于path的验证图像目录 nc: 3 names: [apple_mosaic, apple_alternaria, apple_rust]三个关键点path用绝对路径不用相对路径。工作目录一变相对路径就失效训练时会报“数据集为空”这种误导性错误。nc必须等于实际类别数。这里的3对应三种病害但名字里列出第三类请以压缩包实际标签为准。names的顺序必须和第3章转换脚本里的classes列表完全一致。YOLO训练时TXT每行的第一个数字就是这里列表的索引顺序错一个识别结果就会张冠李戴。4.3 训练命令与参数选择环境就绪、数据布局无误之后训练命令一行就跑起来yolo detect train data/home/you/apple_leaf.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20 projectruns nameapple_leaf训练参数说明参数建议值说明modelyolov8n.ptn是nano版本预训练权重迁移学习初始化imgsz640输入尺寸YOLO内部用letterbox做等比缩放填充epochs100配合patience早停实际轮数看验证集表现batch16按显存调8G显存用16通常没问题patience20验证集mAP连续20轮不提升则停止训练projectruns训练日志和权重输出根目录modelyolov8n.pt这行是关键。916张图从零开始训练效果会很不稳定用预训练权重做初始化是标准做法。nano版本参数量小在这个数据规模上收敛快先跑通流程后续要精度再往上换yolov8s.pt或yolov8m.pt。4.4 训练过程看什么训练启动后终端会滚动输出每个epoch的loss、mAP50、mAP50-95这些指标。我一般关注两个地方results.csvruns目录下自动生成训练结束后用pandas一读就能画出loss曲线和mAP曲线比肉眼盯终端靠谱。best.pt与last.pt训练结束会保存两个权重。best.pt是验证集mAP最高的一轮部署推理用它没跑。训练过程中如果loss不降、mAP始终在低位徘徊直接把训练停掉回去检查标签质量比调整学习率有效得多。这算是我踩过多次后的血泪经验。5. 避坑指南苹果树叶病害数据从解压到训练最容易翻车的五个点5.1 解压后标签数量对不上隐藏文件在捣乱现象图像名单和标签名单做差集发现多了几十个“标签文件”而且文件名看不懂。原因压缩包在Mac系统下打包__MACOSX目录和._开头的AppleDouble文件被当成标签扫进来了。解决解压后先做一轮清理把以._开头的文件和__MACOSX目录直接删掉再跑完整性校验。这个动作应该养成肌肉记忆解压任何数据集都先清一遍。5.2 类别ID错位模型收敛但预测结果张冠李戴现象loss正常下降验证集mAP也不低推理时把花叶病识别成斑点落叶病每个病种都差一位。原因VOC XML里的类别名是字符串YOLO TXT里的类别ID是数字转换脚本里classes列表顺序和训练时的names顺序不一致模型学的是“第0类某个名字”推理时名字对不上号。解决转换完成后随机挑三张TXT标签对照XML原始标注人工核对一次再确认脚本里的classes与data.yaml的names顺序一字不差。这个检查一分钟能省掉训练十几个小时的返工。5.3 归一化坐标出现负数或大于1的值现象训练到一半报坐标相关错误或者日志里出现bbox越界的警告。原因标注框画出了图像边界或者在转换脚本里忘了除以图片宽高。前者是人工标注问题后者是代码问题。解决转换脚本里保留min(max(...))裁剪逻辑保证输出落在0到1区间。但裁剪只是兜底正确做法是在校验阶段把越界原图找出来用标注工具修正边界框从源头消除无效数据。5.4 图片尺寸不统一导致batch读取报错现象训练时出现shape mismatch或部分图像读取失败程序中断。原因压缩包里的叶片图像来自多个来源宽度高度不一致甚至混入灰度图、单通道PNG模型读batch时tensor形状对不上。解决训练参数固定imgsz640YOLO的letterbox逻辑本身能处理不同尺寸的输入但灰度图必须提前转成RGB三通道。预处理脚本里统一用cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)过滤一遍后面训练就稳了。5.5 病害类别样本不均衡少量样本的AP明显偏低现象花叶病样本多另一个病种样本少训练后多数样本的类mAP50有0.85以上少数样本只有0.5左右。原因数据分布本身不均衡模型见过的大类样本多、学得充分小类样本少、特征没学够。解决对样本少的类别做定向数据增强旋转、翻转、HSV扰动都可以放大有效样本量。更省事的做法是给少数类提高损失权重YOLOv8里可以按类别设置cls权重让模型在训练时更重视少数类。两个手段结合比盲目堆epochs有效得多。6. 进阶用法把训练好的权重接进推理脚本按病害类别调置信度训练完成后best.pt就是能直接用的检测权重。用Ultralytics的Python接口写推理脚本代码很短但值得把细节抠细一点from ultralytics import YOLO model YOLO(runs/detect/apple_leaf/weights/best.pt) results model.predict(test/apple_mosaic_021.jpg, conf0.30, imgsz640) for r in results: for box in r.boxes: cls int(box.cls[0]) conf float(box.conf[0]) print(r.names[cls], round(conf, 3), box.xyxy[0].tolist())这段代码的关键在conf0.30。置信度阈值设太高会漏检设太低会误检具体多少应该按病害类别分开调。花叶病的症状表现相对分散病斑边界模糊模型对它天然不够自信阈值可以调到0.25斑点落叶病的病斑特征明显但容易跟土壤阴影混淆0.35能滤掉不少背景误检。评估阶段我习惯用验证集算一次mAP观察每一类的mAP50单独值。某类明显低于平均线时优先回头检查该类样本数量和数据质量而不是继续调训练参数。从上一份数据集栽过跟头之后我养成了一个固定习惯解压完先跑完整性校验转换完随机抽三个TXT跟XML核对类别顺序开训前列印data.yaml的names与标签文件清单逐项比对。从那以后我每拿到一套带VOC/YOLO/JSON标注的植物病害数据都强制走一遍这套流程翻车率低了很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表