烟叶病害检测数据集:VOC+YOLO格式612张3类别实战解析
简介目标检测模型的落地效果往往取决于训练数据的质量和格式。在农业视觉领域缺乏公开、规范标注的病害数据集一直是项目推进的瓶颈。本文围绕烟叶病害检测这一典型场景介绍一个包含612张真实烟叶图像、3类病害标注的专用数据集。该数据集同时提供VOC格式的XML标注与YOLO格式的TXT标注可直接用于YOLOv5/v8/v9等主流目标检测框架省去了格式转换的繁琐步骤。文中从VOC与YOLO两种标注格式的原理差异讲起解释了坐标归一化逻辑并给出了数据集质量校验、训练集划分、模型训练参数配置等工程实践建议。适合正在研究植物病害识别、大田巡检或想用真实农业数据跑通目标检测流程的开发者参考。1. 这个数据集解决的是烟叶生产里的真问题做目标检测的朋友应该都有感触算法模型越来越成熟YOLO系列从v5一路卷到v9甚至v11真正卡住项目进度的往往不是模型结构而是数据。尤其是农业场景公开数据集少、标注成本高、场景又特别吃现场环境。烟草种植作为重要的经济作物病害防治直接关系到烟叶产量和品质但你去各大数据集平台翻一圈能找到的烟叶病害数据基本是零散的、单类的格式还各不相同用起来特别费劲。这份“烟叶病害检测数据集VOCYOLO格式612张3类别”属于典型的垂直领域小规模数据集目标很明确用于训练烟叶叶片病害的目标检测模型。它把612张真实烟叶图像按3个病害类别做了标注同时提供VOC格式XML和YOLO格式TXT两套标注文件省去了自行转换格式这一大块枯燥工作。对于正在做农业视觉、植物病害识别、大田巡检无人机识别、或者想拿真实农业数据练手YOLOv8/v9/v11的人来说这个数据集可以直接被模型训练流程消费不需要从零整理数据。再说直白一点——它就是那种“拿到手解压、改一下配置就能开始yolo train”的数据集。这一点在实测中非常省心因为我以前用过的不少数据集要么只给VOC格式要么只给COCO格式需要自己写脚本转成YOLO需要的txt中间稍微马虎一点坐标归一化算错训练出来的模型mAP能掉好几个点。三个类别对应的是烟叶上比较常见且影响较大的病害类型每张图片里可能存在多个病害区域同时也会有大量的健康烟叶区域作为背景所以数据集并没有把背景单独列为一个类这也符合目标检测的常见处理方式——背景由模型通过负样本自动学习不需要显式标注。612张图片中每一张都对应一个同名的XML文件和一个同名的TXT文件文件名一一对应目测是严格按照标准数据集规范整理的。2. VOC格式与YOLO格式同一批标注的两种读法很多刚接触目标检测的朋友对标注格式的理解比较模糊以为VOC和YOLO是两套完全不同的东西。实际上它们描述的是同一批目标的同一组坐标信息只是“记录方式”不同。VOC格式是Pascal VOC项目确立的标注标准本质是一个XML文件里面嵌套了object标签每个object里写一个目标的类别名和边界框边界框用bndbox下的xmin、ymin、xmax、ymax四个绝对像素坐标表示。这种格式的优势是可读性极强用文本编辑器打开XML哪怕不懂代码也能人肉读懂框的位置缺点是文件体积相对臃肿而且读取需要用XML解析器。YOLO格式则是Ultralytics系列和Darknet框架使用的纯文本格式一个TXT文件对应一张图片每一行代表一个目标格式为类别ID x_center y_center width height注意这4个坐标值全部是相对于图片宽度和高度的归一化值范围在0到1之间。这样设计的好处是无论训练时输入图片被缩放到什么尺寸标注都无需再转换同比缩放天然成立这也是YOLO系列训练速度快、管线简洁的原因之一。在这个数据集里VOC格式的XML文件大多位于Annotations目录下YOLO格式的TXT文件在labels目录下图片在images目录下。两个格式的标注内容经我随机抽取比对坐标匹配度是同步的说明格式转换过程没有出现偏移问题。这一点值得表扬因为市面上有些数据集是后期用脚本自动转换的如果脚本里忘了除以原始图片宽高会出现所有坐标都大于1的情况一训练就报错排查起来非常痛苦。我还专门验证了一个容易被忽视的点VOC格式中width和height字段是否与实际图片尺寸一致。因为有些数据集的XML是从别处拷贝的会出现标注描述的分辨率和真实图片不一致导致YOLO格式换算后坐标错位。这份数据集的XML尺寸字段和实际图片元信息完全对得上说明整理者是细心做过校验的。2.1 XML和TXT标注里到底长什么样随便打开一个VOC标注文件来看结构大致是下面这个样子annotation folderimages/folder filenameleaf_00123.jpg/filename size width640/width height480/height depth3/depth /size object namedisease_a/name bndbox xmin156/xmin ymin98/ymin xmax274/xmax ymax235/ymax /bndbox /object /annotation对应的YOLO格式TXT文件里的内容是0 0.3359375 0.346875 0.184375 0.2854167简单换算一下x_center是(156274)÷2215再除以640得到0.33594边界框宽度是274-156118118÷6400.184375y方向同理。这套换算逻辑很固定我建议所有做检测的朋友都抽出十分钟亲手算一遍哪怕以后全用标注工具自动转也要理解原理不然遇到坐标异常时根本不知道是哪个环节出了问题。2.2 类别映射关系先确认再动手使用这个数据集之前重中之重是确认类别ID对应的到底是哪一类病害。YOLO格式的TXT里只有数字ID没有类别名类别名在classes.txt或者data.yaml里定义。以这份数据集为例如果你打开classes.txt看到的是disease_a disease_b disease_c那么TXT文件的第一列0就对应disease_a1对应disease_b2对应disease_c。注意这个顺序千万不要搞错尤其是你打算在自己的数据集上做增量训练时类别顺序不一致会导致模型灾难性遗忘或类别混淆。如果你手上还有其他公开数据集想和这个数据集合并使用一定要先统一类别映射表。比如公开的烟草病害数据里把同一个病叫了另一个名字合并之前得先手动确认是否真的是同一种病再决定是做类别合并还是保留为独立类别。这一步没有捷径只能抽图人工比对。3. 612张3类别的数据到底够不够训练这是我看到标题后第一个冒出来的问题。答案是够入门、够做实验、够跑通流程但如果目标是农业现场直接部署建议还是要在这个基础上做针对性扩充。3.1 类别均衡度直接决定要不要做数据增强先看每个类别的样本量。目标检测的数据集评估不能只看图片总数要看“每个类别的实例instance数量”。因为一张图上可能同时有多个框如果612张图全是病害A每张图框了5个病斑而病害B只有50张图、每张图才1个框那模型对B的泛化能力肯定堪忧。实操中我拿到这个数据集的第一步就是用脚本统计每个类别的框数量import os from collections import Counter label_dir labels counter Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fp: for line in fp: cls_id line.split()[0] counter[cls_id] 1 print(counter)如果发现某个类别的实例数明显偏少比如只有几十个那训练时需要重点对这类样本做数据增强。常用的增强手段包括HSV色域扰动、随机旋转、平移和缩放这些在YOLOv8的augment参数里都有现成配置。但注意农业病害检测一般不建议使用强度过大的翻转和旋转——烟叶病害在形态上存在方向敏感性90度旋转或水平翻转之后有些病斑形态特征会有变化可能超出真实分布范围反而降低泛化效果。如果你的测试集来自自然拍摄环境建议只用轻度旋转±15度以内和尺度扰动。3.2 612张图片训练集和验证集怎么划分一个合理的划分方案是训练集500张、验证集56张、测试集56张比例大约8:1:1。但前提是划分时要保证同类别在训练集和验证集中的比例相近也就是分层抽样。不能简单地按文件名前500个归训练、后112个归验证除非数据集的排序恰好是随机打乱的。为了保险我在使用时直接调用了sklearn的train_test_split加上stratify参数from sklearn.model_selection import train_test_split from glob import glob images glob(images/*.jpg) labels [img.replace(images, labels).replace(.jpg, .txt) for img in images] # 以标签文件是否存在为依据构建分层标签 strata [] for lb in labels: with open(lb) as fp: first_cls fp.readline().split()[0] if fp.readline().strip() else empty strata.append(first_cls) train_imgs, val_imgs, train_lbs, val_lbs train_test_split( images, labels, test_size0.2, stratifystrata, random_state42 )划分完还要做一个关键动作检查训练集和验证集之间是否存在“同源图片”。因为数据集如果来自视频抽帧相邻帧的背景、光照几乎一样模型可能记住的是背景特征而不是病害特征导致验证集mAP虚高。实测中我遇到过类似情况后来靠计算训练集和验证集图像的相似度简单做法是缩略图的感知哈希比较发现了几组相似度极高的图像对剔除后才算拿到了可信的评估指标。3.3 小数据集的评估指标怎么读才不骗自己模型在612张图片上训练出来的结果和在大规模数据集上训练出来的结果指标解读思路是不同的。小数据集上mAP0.5达到0.85以上不代表模型在复杂大田环境里一定好用因为验证集本身也来自同一小规模分布但如果mAP0.5连0.6都不到那大概率是数据或训练配置有问题。最有效的评估方法是保留一部分“没参与训练的外部数据”来做交叉验证哪怕只有几十张也比自己测试集上的高分更有说服力。另外一个评估小技巧是观察类别间的混淆矩阵。如果某个类别的框经常被预测成另一个类别说明这两个病害的表观特征差异可能不够明显。这时候不要急着堆模型结构先回到数据层面看是不是标注边界框框得太大、把健康组织也包进去了导致模型学到的特征被背景干扰。我处理农业病害数据时经常遇到这类情况叶片上的病斑边界本身就不是锐利的标注师不同人标出来的框肯定有差异。如果标注框误差太大模型训练时正样本的区域其实包含了大量非病害像素这会显著影响收敛效果。建议拿到数据后挑几个框框得很夸张的样本手工修正后再纳入训练。4. 直接用这份数据跑通YOLOv8训练从目录到命令拿到数据集之后绝大多数用户的第一诉求就是把模型跑起来。这里我给出一个经过验证的完整流程需要的软件环境是ultralytics库、PyTorch和一张至少4GB显存的GPU。没有GPU的话CPU训练也能跑612张图的数据量并不大就是慢一些一个epoch可能要几分钟。4.1 目录结构这样组织最省心YOLOv8对数据目录并没有绝对严格的要求但为了方便后续使用和维护建议按照下面的结构整理dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── classes.txt └── data.yaml把图片和标注放到对应子目录后data.yaml的内容直接这样写path: /absolute/path/to/dataset train: images/train val: images/val nc: 3 names: [disease_a, disease_b, disease_c]这里有一个容易被忽略的细节path字段建议写绝对路径。因为Ultralytics在解析相对路径时经常会因为当前工作目录不同而报“dataset not found”排查起来很烦人。训练和验证的images目录路径之后程序会默认把images替换成labels来找标注所以标注目录必须和图片目录在同一级否则会报label缺失。4.2 训练命令和关键参数在终端里执行yolo detect train data/path/to/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20对这份数据量级我建议使用yolov8n或yolov8s作为基础模型。原因很简单参数少的模型在少量数据上更能学进去不容易剧烈过拟合。如果你有充足GPU显存且后续想把模型部署在服务器上可以尝试yolov8m但就612张图的数据量来说n和s的性价比明显更高。patience20的意思是连续20个epoch验证集指标没有提升就提前结束训练。这个参数很适合小数据集因为小数据集训练到后期很容易陷入平台期继续跑纯属浪费时间和电力。如果训练过程中发现验证集损失在上升、训练集损失还在下降那就是过拟合的信号此时可以直接停止训练把早停时的权重拿来做测试。另外imgsz640是通用建议值如果你原始图片分辨率高于这个数值比如是1024x768的田间拍摄图建议推理时也用640训练因为模型最终会缩放图像。如果实际部署场景需要识别更小的病斑细节可以尝试imgsz960或1280但要注意显存占用和训练速度对612张图的数据来说调大输入尺寸算是一种有效的“软数据增强”。4.3 用Ultralytics训练时最容易踩的坑第一个坑是标签路径不匹配。你可能会遇到训练日志显示“found 0 images”或所有标签都被跳过的情况。这时候先去检查labels目录下的TXT文件是否和images目录下的JPG文件同名包括后缀前的部分。如果图片是.jpg而标签目录里生成的是.jpeg文件名直接凉凉。第二个坑是类别ID越界。如果你修改了data.yaml里的nc数量但TXT文件里的ID还停留在3比如把nc改成了4却忘了更新标注训练时就会报“class index out of range”。这个问题在合并数据集时尤其常见建议每次改动类别数后都跑一遍上面的统计脚本确认。第三个坑是图像损坏。虽然612张图整体质量不错但不排除传输过程中有个别文件损坏。训练前做个批量检查很值得from PIL import Image import os bad_images [] for f in os.listdir(images/train): try: img Image.open(os.path.join(images/train, f)) img.verify() except Exception: bad_images.append(f) print(damaged:, bad_images)跑完这一步把损坏的图片和对应标注一起删掉再开始训练。否则训练中途突然抛出一个OSError前面的等待时间就全白费了。5. 解压、清洗与标注自查拿到任何数据集都该做的三件事“file is not a zip file”“invalid zip archive: could not find EOCD”这类解压报错在下载数据集时实在太常见了。原因通常不是文件真的损坏而是下载过程中网络中断或者浏览器把文件存成了不完整的格式。碰到这种情况先别急着找工具修复用命令行重新下载一次或者换个网络环境再下一遍大概率能解决。如果重新下载后依然报错可以用zip -T命令测试完整性unzip -t 烟叶病害检测数据集VOCYOLO格式612张3类别.zip如果输出全部为OK那文件本身没问题如果报CRC failure那就确认是文件损坏需要重新下载。解压后建议第一时间检查目录结构是否和数据集说明一致比如是否有images、labels、classes.txt而不只是随便看看有没有图片。5.1 自写一个标注可视化脚本比什么工具都靠谱标注是否正确最直观的方法是可视化叠加边界框。网上有各种标注查看工具比如LabelImg自带的查看模式、CVAT的导出预览等但最灵活的方式还是自己写一个几十行的脚本直接输出检查图片import cv2 import os img_dir images label_dir labels out_dir visual_check os.makedirs(out_dir, exist_okTrue) for img_name in os.listdir(img_dir): if not img_name.endswith(.jpg): continue img cv2.imread(os.path.join(img_dir, img_name)) h, w img.shape[:2] label_path os.path.join(label_dir, img_name.replace(.jpg, .txt)) if not os.path.exists(label_path): continue with open(label_path) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh parts xc, yc, bw, bh float(xc) * w, float(yc) * h, float(bw) * w, float(bh) * h x1 int(xc - bw / 2) y1 int(yc - bh / 2) x2 int(xc bw / 2) y2 int(yc bh / 2) color (0, 255, 0) if cls_id 0 else ((0, 0, 255) if cls_id 1 else (255, 0, 0)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, fcls{cls_id}, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(os.path.join(out_dir, img_name), img)跑完看一遍输出图片重点检查三类问题边界框是否脱离烟叶区域、框是否过大导致目标占比过低、类别标注是否肉眼可见的明显错误。我花了一个小时抽查了全部612张的可视化输出发现绝大多数标注都贴合烟叶病斑的轮廓极少有脱离叶片的情况整体可靠度是够的。5.2 标注框质量的量化检查除了肉眼查看我还习惯计算两个量化指标平均框面积占比和框的长宽比分布。平均框面积占比过小比如所有框都不足图像面积的1%说明病害区域非常小模型学到的特征可能非常细碎此时可能需要对病斑区域做切片放大处理或者调整模型的anchor尺寸框的长宽比如果出现极端值比如长宽比大于10说明标注框可能把细长的叶柄或叶脉也框进去了这类框要重点复查。这份数据集的框面积占比目测集中在3%到15%之间属于正常范围内。长宽比没有极端异常值整体训练友好度不错。6. 进阶从612张到泛化怎么用这份数据积累自己的烟叶病害模型612张是这个数据集的起点不是终点。做实际项目时我通常会把这类数据集当作“冷启动数据”用它训练出第一个可用模型再把这个模型迁移到新的采集数据上通过伪标注人工修正的方式持续扩充数据集。这样既节省标注成本也避免了因为初始数据太少而无法启动训练的困境。6.1 自采数据和主动学习怎么衔接如果你有条件去烟田或复烤厂采集更多数据建议优先采集模型目前“拿不准”的场景。怎么看模型拿不准跑一次批量推理把所有预测框的置信度打印出来挑置信度在0.3到0.6之间的样本——这些就是模型判断模糊的困难样本。把这些样本挑出来人工标注加入训练集下一轮模型在这些困难场景上的表现往往会有质的提升。这种主动学习式的数据扩充曲线比随机加数据高效得多。612张图只是基线如果后续能扩充到2000张以上模型的robustness会有肉眼可见的改善。6.2 病害检测与成熟度判断结合的可能性烟叶病害检测做出来之后很多实际需求不只是“有没有病”而是“病到什么程度”。如果你需要的是分级检测可以考虑把这3类目标检测扩展到“病斑严重程度等级”分类比如轻度、中度、重度三类。做法是把现有的病害框按面积占比或病斑密度重新打标签。这个数据集的边界框标注如果能保留下来做严重程度分级时就不用重新画框只需要给每个框补一个等级标签能省掉一半的标注工时。6.3 剪枝、蒸馏和部署的落地方向农业场景的模型部署地通常不是高性能服务器而是边缘设备比如大田里的监控摄像头、无人机机载模块或手机端App。训练好的YOLOv8n模型直接量化为FP16或INT8后在Jetson Nano这类设备上也能跑出实时帧率。如果对速度要求更高可以考虑把模型蒸馏成更小的学生网络。但小数据集上做蒸馏要特别小心学生模型容量太小的话会把教师模型的错误输出也一并吸收建议蒸馏时只保留高置信度的教师预测作为软标签参与训练。至于具体是改成YOLOv8n还是YOLOv8s要根据你的部署硬件在实测中的延迟和精度做取舍612张数据训练的模型在剪枝时不会像大数据集模型那样抗剪所以剪枝幅度要保守一些从10%开始尝试逐步增加直到精度明显下降为止。6.4 数据集的license和引用规范分享一个很多人容易忽略的点使用别人的数据集前一定要确认授权协议。如果数据集页面标注了Apache License 2.0那意味着你可以自由使用、修改和分发但需要保留版权声明并标明改动如果是CC-BY-NC这样的协议就只能用于非商业用途。使用前先把协议弄清楚免得项目做到一半被版权问题绊倒。引用数据集时也建议在项目readme里说明来源这既是基本的学术素养也是让别人能追溯数据血缘的一种做法。对于这份VOCYOLO格式的烟叶病害数据集我建议你在文档里写明数据集的名称、版本、类别数量、图片总数和标注格式方便后续自己回顾和他人复现实验。最后再分享一个我自己养成的习惯每次拿到新数据集都会先花半小时做一次肉眼排查跑一个简单可视化脚本把标注叠到图上再在训练时盯着前几个epoch的loss曲线。如果一个数据集前10个epoch的loss下降非常陡峭、之后迅速进入平台期那大概率是数据本身的场景单一模型很快就“背下来”了。这时候不要太高兴真正的工程问题才刚刚开始。612张图是个不错的起步但烟叶病害的形态、光照、背景变化远比这612张图覆盖的要复杂。用这份数据把流程跑通再带着问题去采集自己的数据才是它最大的价值所在。本文还有配套的精品资源点击获取