尧图精选

棉花叶片病害检测数据集:VOC与YOLO双格式解析与训练指南

🕒 发布时间:2026/10/1 18:07:10 📁 来源:尧图网络
简介本资源为棉花叶子病害检测数据集面向从事农业图像识别、作物病害分类与目标检测的算法工程师、研究生及竞赛选手可用于训练和验证YOLO或VOC格式的检测模型解决棉花叶片病害自动识别中样本不足的问题。压缩包共约2000个文件包含977张jpg图片、977个VOC格式xml标注文件、977个YOLO格式txt标注文件及少量说明文件整体约47.86MB图片分辨率统一为640x640标注类别共22类覆盖多种棉花叶部病害。目前已有316人学习下载适合需要快速构建基线模型或扩充数据集的开发者。数据集同时提供VOC与YOLO两套标注省去格式转换步骤可直接接入主流检测框架目录结构清晰便于按类别划分训练集与验证集帮助读者将精力集中在模型调优与病害识别效果提升上。1. 977 张棉花叶片、22 类病害这份 VOCYOLO 双格式数据集到底能干什么棉花叶片病害识别是个典型的细粒度检测任务病斑面积小、类别间视觉差异微妙很多团队卡在第一步——找不到标注质量过关、类别覆盖够全的数据。这份 977 张、22 类的棉花叶子病害检测数据集同时提供 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注图片统一 640×640jpg、xml、txt 三种文件各 977 个一一对应。它解决的不是有没有数据的问题而是标注格式能不能直接喂进训练管线的问题VOC 那套给需要解析 XML 的老流程或可视化工具用YOLO 那套直接丢进 ultralytics 系训练脚本就能跑。适合做农业病害检测的算法同学、需要快速搭 baseline 的工程团队以及拿它当 YOLO 训练练手素材的入门者。但 22 类、977 张意味着平均每类不到 45 张这个量级决定了它的定位是验证流程和跑通 pipeline而不是直接冲生产精度。2. 双格式标注怎么选VOC 与 YOLO 的目录结构和解析差异拿到压缩包解压后第一件事不是急着训练而是把目录结构和标注对应关系摸清楚。这份数据集的核心价值在于双格式同源——同一张图既有 xml 又有 txt标注框理论上应该完全一致。但实际用起来两种格式的解析路径、坐标体系、类别映射方式都不一样选错了会在训练中途才暴露问题。2.1 VOC 格式的目录组织与 XML 字段含义Pascal VOC 的标准结构是Annotations/放 xml、JPEGImages/放图片、ImageSets/Main/放划分文件。这份数据集不一定带 ImageSets因为摘要里明确说了不包含分割路径的 txt 文件所以划分得自己来。先看一个 xml 长什么样annotation folderJPEGImages/folder filenamefirc_cotton_625.jpg/filename size width640/width height640/height depth3/depth /size object namedisease_class_03/name bndbox xmin112/xmin ymin88/ymin xmax305/xmax ymax274/ymax /bndbox /object /annotation关键字段是size里的宽高确认是不是真的 640×640、object里的name类别名22 类就是 22 种不同的 name 值、bndbox的四个坐标VOC 用的是绝对像素坐标左上角 xmin/ymin、右下角 xmax/ymax。解析时最容易翻车的是有的 xml 里name带空格或特殊字符直接当类别 key 用会导致类别数对不上还有的bndbox坐标可能超出图片边界需要 clip。用 Python 快速统计类别分布和坐标合法性import os import xml.etree.ElementTree as ET from collections import Counter ann_dir Annotations cls_counter Counter() bad_boxes 0 for f in os.listdir(ann_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, f)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) for obj in root.findall(object): name obj.find(name).text.strip() cls_counter[name] 1 bb obj.find(bndbox) xmin int(float(bb.find(xmin).text)) ymin int(float(bb.find(ymin).text)) xmax int(float(bb.find(xmax).text)) ymax int(float(bb.find(ymax).text)) # 检查越界和非法框 if xmin 0 or ymin 0 or xmax w or ymax h or xmax xmin or ymax ymin: bad_boxes 1 print(类别数:, len(cls_counter)) print(各类别框数:, cls_counter.most_common()) print(非法框数量:, bad_boxes)这段脚本干三件事统计 22 类各自的标注框数量判断类别是否均衡、检查坐标是否越界或宽高为负脏标注的直接信号、确认图片尺寸是否真为 640×640。int(float(...))的写法是因为有些 xml 里坐标写成112.0这种浮点字符串直接int()会抛异常。跑完如果发现某类只有个位数框或者非法框超过几十个就得考虑清洗或重新划分。2.2 YOLO 格式的归一化坐标与 classes.txt 映射YOLO 的 txt 每行是class_id x_center y_center width height全部归一化到 0~1。和 VOC 最大的区别是VOC 存绝对坐标和类别名YOLO 存归一化坐标和类别索引。索引到名字的映射靠一个classes.txt或data.yaml里的names列表这个文件如果数据集里没带就得自己从 xml 的 name 集合生成。import os # 从 VOC 的 xml 收集所有类别名生成 classes.txt names sorted(cls_counter.keys()) with open(classes.txt, w, encodingutf-8) as f: for n in names: f.write(n \n) # 验证 yolo txt 与图片是否一一对应 img_dir images lbl_dir labels imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} lbls {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith(.txt)} print(图片无标注:, imgs - lbls) print(标注无图片:, lbls - imgs)classes.txt的顺序必须和生成 YOLO txt 时的索引顺序一致否则类别全乱。如果你是从 VOC 转 YOLO转换脚本里类别排序方式要和这里一致。上面这段还顺手查了图片和标注的配对情况——977 对 977 理论上应该完全匹配出现差集就说明有文件缺失或命名不一致。2.3 两种格式的选型建议维度VOC (xml)YOLO (txt)坐标体系绝对像素归一化 0~1类别表示字符串 name整数索引直接可用框架mmdetection、部分 TF 流程ultralytics YOLO 系可视化工具labelImg 原生支持需转回 VOC 或用 YOLO 专用工具划分文件需自己生成 ImageSets需自己写 train/val txt选型逻辑很简单用 ultralytics 的 YOLOv5/v8/v11 就直接走 txt省一层转换用 mmdetection 或需要精细控制 anchor 的老流程就走 xml。两者同源意味着你可以先用 xml 做数据审查和可视化确认无误后再转成 txt 训练转换过程本身就是一次交叉验证。3. 从解压到跑通训练YOLO 格式的目录改造与 data.yaml 配置数据审查完下一步是把它改造成训练框架能直接吃的结构。ultralytics 系对目录有固定要求977 张的规模用 8:1:1 或 7:2:1 划分都行但要注意类别均衡——如果随机划分导致某类在验证集里一个都没有mAP 会直接崩。3.1 目录结构改造与训练集划分ultralytics 期望的结构是dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml划分脚本要保证图片和标注同步移动且尽量按类别分层抽样import os import random import shutil from collections import defaultdict random.seed(42) src_img images src_lbl labels dst dataset # 按主类别分组做分层划分 cls_to_files defaultdict(list) for f in os.listdir(src_lbl): if not f.endswith(.txt): continue with open(os.path.join(src_lbl, f)) as fh: lines fh.readlines() if not lines: continue main_cls lines[0].split()[0] # 取第一个框的类别作为分组依据 cls_to_files[main_cls].append(os.path.splitext(f)[0]) train, val, test [], [], [] for cls, files in cls_to_files.items(): random.shuffle(files) n len(files) n_val max(1, int(n * 0.2)) n_test max(1, int(n * 0.1)) val files[:n_val] test files[n_val:n_val n_test] train files[n_val n_test:] for split, names in [(train, train), (val, val), (test, test)]: os.makedirs(f{dst}/images/{split}, exist_okTrue) os.makedirs(f{dst}/labels/{split}, exist_okTrue) for name in names: shutil.copy(f{src_img}/{name}.jpg, f{dst}/images/{split}/{name}.jpg) shutil.copy(f{src_lbl}/{name}.txt, f{dst}/labels/{split}/{name}.txt) print(ftrain{len(train)} val{len(val)} test{len(test)})分层抽样的关键是main_cls的取法——这里取第一个框的类别作为分组依据简单但对单类为主的图片有效。如果一张图有多个类别更严谨的做法是按类别做多标签分层但 977 张的规模下按主类别分组已经能避免某类完全缺席验证集。random.seed(42)固定随机种子保证每次划分可复现这在调参对比时很重要。max(1, ...)保证即使某类只有两三张验证集也至少有一张。3.2 data.yaml 的字段与路径陷阱path: /abs/path/to/dataset train: images/train val: images/val test: images/test nc: 22 names: 0: class_00 1: class_01 # ... 共 22 行顺序必须与 classes.txt 一致path用绝对路径最稳相对路径在不同工作目录下启动训练时经常找不到文件。train/val/test是相对path的子路径。nc必须等于names的长度多一个少一个都会在训练启动时报错。names的顺序就是类别索引的顺序和 txt 里的 class_id 严格对应——这是最容易出错的地方如果 classes.txt 是排序生成的data.yaml 里的 names 也必须按同样顺序排。3.3 启动训练与关键参数yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/cotton \ nameexp01modelyolov8n.pt用 nano 版是因为 977 张的规模下大模型极易过拟合n 或 s 版足够。imgsz640和原图分辨率一致不需要 resize省一层插值损失。batch16在 8G 显存上跑 640 分辨率基本安全显存不够就降到 8。patience20是早停22 类小数据集上验证 loss 波动大给 20 轮容忍度比默认的 50 更实际。lr00.01是 SGD 的初始学习率如果用 AdamW 可以降到 0.001。训练启动后重点盯三个信号cls_loss是否持续下降不降说明类别映射可能错了、验证集 mAP 是否在 30 轮后还在涨早停触发太早说明 patience 不够、有没有nan出现通常是标注里有非法坐标或空 txt。4. 22 类小样本的避坑清单从标注脏数据到训练崩溃977 张分 22 类平均每类 44 张这个量级下很多在 COCO 上不是问题的问题会集中爆发。下面这几条是我在实际跑这类农业数据集时反复踩到的。4.1 现象训练到十几轮 loss 突然变 nan原因标注文件里有坐标越界或宽高为负的框归一化后出现负值或大于 1 的值反向传播时梯度爆炸。VOC 转 YOLO 的脚本如果没有做 clip越界框会原样转过去。解决训练前跑一遍清洗脚本把所有坐标 clip 到 [0, 1]宽高小于 0.001 的框直接丢弃。上面 2.1 节的检查脚本能定位到具体是哪些文件。4.2 现象mAP 一直是 0但 loss 在降原因data.yaml里的names顺序和 txt 里的 class_id 对不上模型学到的类别和验证时计算的类别错位。或者nc写成了实际类别数以外的值。解决用classes.txt的行号作为 class_id 重新生成一遍 txt确保names列表和生成时的顺序完全一致。验证方法随便挑一张图用训练好的模型预测看输出的类别名是否和图上病斑对得上。4.3 现象某几个类别 mAP 始终为 0原因这些类别的样本数太少可能只有几张且在训练集里被分得更少模型根本没学到。或者这些类别的标注框特别小640 分辨率下下采样 32 倍后特征几乎消失。解决先统计每类在 train/val 里的数量少于 5 张的类别考虑合并或过采样。小目标问题可以开mosaic增强默认开并适当调大imgsz但这份数据集原图就是 640放大意义不大更实际的是检查这些小框是不是标注错误。4.4 现象验证集 loss 比训练集低很多原因验证集太小比如只有 90 多张且划分时没有分层导致验证集恰好都是容易样本。或者训练时开了dropout但验证时关了这个在 YOLO 里不常见更多是划分问题。解决重新做分层划分保证每类在验证集里至少有 2~3 张。如果某类总共就 3 张那验证集里放 1 张、训练集放 2 张接受这个类别的指标不可信在报告里注明。4.5 现象推理时框大量重叠或漏检原因conf阈值和iou阈值没调。22 类病害中有些病斑相邻默认iou0.7的 NMS 会把相邻框合并掉。解决推理时把conf从默认 0.25 调到 0.4 左右iou从 0.7 降到 0.5观察漏检和误检的平衡。这个没有万能值得在验证集上扫一遍。5. 用混淆矩阵和单类 AP 反推标注质量一个收尾技巧训练跑完拿到results.csv和confusion_matrix.png之后大多数人看一眼 mAP 就结束了。但这份 22 类小样本数据集真正的价值在于混淆矩阵能反过来告诉你哪些类别的标注本身就有问题。我的习惯是先把confusion_matrix.png里的非对角高值挑出来比如 class_05 大量被预测成 class_12那大概率不是模型不行而是这两类的标注标准在数据集里就没区分开——可能标注者自己都分不清这两种病斑。具体操作从runs/cotton/exp01/里找到混淆矩阵导出每类的 APimport pandas as pd df pd.read_csv(runs/cotton/exp01/results.csv) # 列名形如 metrics/mAP50(B)、metrics/mAP50-95(B) print(df[[epoch, metrics/mAP50(B), metrics/mAP50-95(B)]].tail(10)) # 单类 AP 在 val 阶段输出可从验证日志里提取 # 或者用 yolo val 单独跑一次加 verboseTrueyolo detect val \ modelruns/cotton/exp01/weights/best.pt \ datadataset/data.yaml \ verboseTrue \ save_jsonTrueverboseTrue会打印每一类的 APsave_jsonTrue导出 COCO 格式的预测结果方便用 pycocotools 做更细的分析。拿到单类 AP 后把 AP 低于 0.1 的类别列出来逐类抽 10 张图人工看如果标注框本身位置就偏那是标注质量问题如果框位置对但类别标错那是类别定义问题如果框和类别都对但模型就是学不会那才是样本量不够。我一般会做一张表把 22 类的 AP、样本数、平均框面积三列并排看类别训练样本数平均框面积(px²)AP0.5class_0038124000.72class_051232000.08............规律通常很明显AP 低的要么是样本数个位数要么是平均框面积小于 5000 像素²。前者靠过采样或合并类别解决后者得回到标注环节确认小框是不是漏标或错标。这个反推过程比盲目调参有用得多——小样本数据集上标注质量对最终指标的影响远大于超参。从那以后我每次拿到这类几十类、几百张的数据集都强制先跑一遍类别分布统计和混淆矩阵反查确认标注本身站得住再动模型。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →