简介面向目标检测与农业智能化研究的YOLO葡萄叶片病害检测数据集聚焦真实农田场景中的葡萄叶片常见病害包含1000张拍摄清晰、角度多样的高质量图片每张均提供VOC、COCO、YOLO三种主流格式的标注文件可以直接送入YOLO系列模型进行训练和验证免去自行标注的繁琐。资源内还附赠Linux与Windows双平台下的YOLO环境搭建教程、针对自定义数据集修改并训练模型的实战案例以及三个Python划分脚本支持一键生成训练集、验证集和测试集方便按需组合数据。整个压缩包共2000个文件以xml和txt标注文件为主同时包含html操作说明、py脚本和yaml模型配置整体大小仅34.79MB下载解压快速。目前已有457人学习适合刚接触目标检测的学生、农业AI科研人员或开发者可直接基于该数据包复现完整的葡萄病害识别流程并迁移到其他作物场景。1. YOLO葡萄叶片病害检测拿到这份数据集第一件事不是训练做葡萄叶片病害检测的人多半不是卡在模型上而是卡在数据上。自己下田拍叶片不难难的是拍完还要一张张打标、转格式、按比例切训练集这些活干一次就知道有多磨人。这个标题里的数据集把1000张葡萄叶片病害图片连同VOC、COCO、YOLO三种标签格式、划分脚本和训练教程一起打包等于把“从数据到能跑的模型”中间那段最脏的活提前干完了。它适合三类人想做农业病害检测但没精力从头标注的工程师、需要一份干净数据跑通YOLO训练流程的学生、以及打算用迁移学习把模型复用到其他作物叶片病害上的开发者。我建议你拿到手先别急着训练把三种格式的差别和目录结构看清楚后面能少踩很多坑。2. 数据集里的三种标签格式VOC、COCO、YOLO分别怎么用2.1 先看目录1000张图与三种标注的存放位置解压后你先别双击图片看热闹第一件事是确认目录结构。这类打包数据集的常见摆放方式是images/或JPEGImages/存放全部1000张原图文件名通常是一串编号比如000001.jpg。Annotations/VOC格式的XML标注文件一个图片对应一个同名的.xml。labels/YOLO格式的TXT标注文件一个图片对应一个同名.txt。annotations/或coco/COCO格式的JSON标注文件一般整个数据集汇总成一个.json。classes.txt或names.txt类别名单一行一个类名顺序就是类别编号的对应关系。scripts/或split/划分脚本后面章节会重点讲。README.md或使用说明标注约定、来源、类别定义都在里面。. ├── images/ # 1000张葡萄叶片原图 ├── Annotations/ # VOC格式的xml标注 ├── labels/ # YOLO格式的txt标注 ├── annotations/ # COCO格式的json标注 ├── classes.txt # 类别列表顺序即类别id ├── scripts/ │ └── split_dataset.py # 数据集划分脚本 └── README.md # 使用说明与数据约定提示如果解压后发现images目录下还有train/val/test子目录说明原发布者已经划分过了你直接用就行。如果没有子目录才轮到划分脚本上场。这里有个容易忽略的细节VOC的XML和YOLO的TXT都是“一图一文件”文件名必须和图片完全一致COCO的JSON则是一条记录带file_name字段训练时靠这个字段去找图。所以检查数据完整性时前两种看文件数量COCO看JSON里的images列表长度三者对不上就要警惕。2.2 VOC转YOLO的换算公式归一化坐标的四个边界坑VOC、COCO、YOLO三种格式描述同一个目标框用的坐标系和存储方式都不一样。格式存储形态坐标表示宽高表示适配框架VOCXML文件一图一个左上角和右下角绝对像素xmin ymin xmax ymax通过坐标相减得到老版SSD、Faster R-CNN、mmdetectionCOCOJSON文件整个数据集一个左上角绝对像素x y绝对宽高w hDetectron2、mmdetection、Mask R-CNN系列YOLOTXT文件一图一个归一化中心点x_center y_center归一化宽高w hYOLO全系列、ultralytics从VOC转YOLO是最常见也最容易算错的转换import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_path, class_map): 把VOC的xml标注转成YOLO的txt标注 class_map: 类别名到类别id的映射顺序必须和训练配置完全一致 tree ET.parse(xml_path) root tree.getroot() # 注意宽高必须从size节点读取绝不能自己猜 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: # 遇到不在类别名单里的目标跳过比报错更安全 continue cls_id class_map[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 中心坐标和宽高全部归一化到0~1 x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) Path(out_path).write_text(\n.join(lines), encodingutf-8)这段逻辑本身不复杂但四个边界坑我挨个说第一宽高信息一定要从XML的size节点取。有的数据集标注时图片被压缩过XML里的width和height和文件实际尺寸不一致你按文件字节流去猜分辨率转出来的框全部错位。第二中心点是(xmin xmax) / 2不是(xmin xmax) / 2写成(xmax - xmin) / 2后者是宽。这个笔误我在代码评审里见过不止一次错得毫无征兆。第三YOLO要求框必须落在0~1区间内。如果标注本身有越界比如xmax超出了图片宽度归一化后会出现大于1的值训练时yolo会提示坐标越界甚至静默丢弃样本。转换后最好加一行检查所有值都在[0, 1]区间否则剪裁。第四类别编号顺序以训练配置为准。VOC标注里存的是类名YOLO里存的是数字这个数字就是你的数据集那个.txt文件里的行号从0开始。不要自己重新排序后面训练配置文件里的names列表必须和这里一一对应。COCO转YOLO同理不过COCO的格式是[x, y, w, h]转换时中心点就是x w/2和y h/2宽高已经是绝对值除以图片宽高即可。COCO的JSON因为整体打包如果你只需要YOLO格式直接写个脚本遍历annotations列表按image_id关联到图片尺寸逐条生成TXT也很快。2.3 训练框架决定格式别让格式迁就模型我见过不少新手一上来就问“哪个格式最好”这问题本身没意义。格式只是中间表达真正该问的是“我打算用什么框架训练”。如果用的是ultralytics的YOLO系列直接用YOLO格式省去一切转换。数据配置文件里写清楚train、val路径和names列表就能跑。这类教程最多资料最好找。如果做目标检测的同时还要跑实例分割或者打算用Mask R-CNN、Detectron2那就用COCO格式。COCO的JSON结构里annotations带segmentation字段VOC和YOLO都存不了多边形掩膜。如果用的是mmdetectionVOC和COCO都能直接吃但VOC要额外指定data_root下的ImageSets/Main目录里面是train.txt、val.txt这些划分文件。所以我给你的建议是框架定在YOLO就只用labels/里的txt不要去动VOC和COCO那两份。它们留着当备份和校验基准就好。3. 划分脚本用Python把数据集按8:1:1切出训练、验证和测试集3.1 为什么要划分验证集决定调参测试集决定最终效果很多拿到数据集的第一反应是全量投入训练这基本是给自己埋雷。你不留验证集就没办法判断每一轮epoch结束模型到底变好了还是变坏了只能干瞪眼等训练完再拿图片去试一旦效果差你根本不知道是数据问题、参数问题还是模型选型问题。标准的做法是把数据集切成三份train训练集模型学习特征的样本占比通常70%~80%。val验证集每个epoch结束用来评估模型决定是否保留当前权重、是否触发早停占比10%~15%。调优学习率、置信度门限都看它。test测试集整个训练完全结束后最后评估一次泛化能力占比10%~15%。测试集绝不能参与训练和调参否则它的成绩就是自欺欺人。1000张图的规模我用8:1:1偏多因为训练集多一张是一张。如果病害类别多且不均衡我会改成8:1:1不变但划分时按类别做分层抽样保证每个子集里各类别比例接近原始分布。这个后文脚本里会体现。3.2 最小可用的划分脚本图片列表与标签同步切这个数据集自带划分脚本但无论它给不给你自己也得会写一个。因为后续你自己拍图、打标、扩充数据还是要重新划分。下面这个脚本是通用做法直接放在数据集根目录运行import os import random from collections import defaultdict random.seed(42) # 固定随机种子多次运行结果一致 img_dir images # 只统计图片文件扩展名按实际数据情况补充 imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(imgs) n len(imgs) n_val int(n * 0.1) n_test int(n * 0.1) n_train n - n_val - n_test splits {} for i, img in enumerate(imgs): name os.path.splitext(img)[0] if i n_train: splits[name] train elif i n_train n_val: splits[name] val else: splits[name] test # 生成train.txt/val.txt/test.txt内容为图片绝对路径 for split in (train, val, test): names [name for name, s in splits.items() if s split] out_path f{split}.txt with open(out_path, w, encodingutf-8) as f: for name in names: f.write(os.path.join(os.path.abspath(img_dir), name .jpg) \n) print(f{split}: {len(names)} images - {out_path})这段脚本做的事情把所有图片加载进内存洗牌按8:1:1切成三份最后生成三个txt文件每行是图片的绝对路径。为什么不直接移动图片因为移动一次破坏原始目录结构换一种划分比例或者想重新切原数据就乱了。生成路径列表的方式可以反复重来random.seed(42)保证你每次运行结果完全一致这也方便别人复现你的实验。关键参数就两个n_val和n_test的比例。默认各取10%如果你的数据集比较小可以改成5%、5%甚至2%、2%但下限是不建议比极端类别样本数还小否则验证集里某个类别只有一两张评估结果波动会非常大。使用ultralytics训练时train.txt和val.txt可以直接填到数据配置文件的train、val字段框架会自动按路径列表加载图片并且会去同名目录下找标签文件。3.3 划分后必做的验证图片数、标签数与类别分布脚本跑完先别急着训练。划分本身不会出错但你的数据可能本来就有问题。三行命令查清楚wc -l train.txt val.txt test.txt ls labels | wc -l ls Annotations | wc -lwc -l对比三个txt的行数总和应该等于1000labels目录下的txt数量也应该等于1000。数量对不上就要小心图片存在但没有标签训练的时候YOLO会打印警告并跳过这些图你稀里糊涂地以为用了1000张实际可能只训练了900张。然后再检查每个集合里的类别分布是否均衡from collections import Counter, defaultdict def count_classes(label_file_list): counter Counter() for txt in label_file_list: with open(txt, encodingutf-8) as f: for line in f: cls_id int(line.split()[0]) counter[cls_id] 1 return counter # 按划分结果统计train子集的类别分布 label_base labels train_counts count_classes([ os.path.join(label_base, name.split(/)[-1].replace(.jpg, .txt)) for name in open(train.txt).read().splitlines() ]) print(train_counts)统计完你会看到每个类别在训练集里有几个目标框。如果某个类在训练集有800个框、验证集却只有20个那这类别的验证成绩基本靠运气后面训练出来的mAP会有明显的虚高或虚低。解决办法是重跑划分脚本在洗牌前按图片主类别分组再对每组分别切出8:1:1这叫分层划分1000张级别的小数据集很值得做。4. YOLO训练教程从环境配置到跑出best.pt的完整路径4.1 环境配置conda建环境、装ultralyticsGPU先确认先别管训练命令怎么写环境装不对后面全是玄学。我一般建议用Anaconda管理Python环境专门给YOLO开一个独立环境别和日常开发环境混在一起。yolo v8 anaconda环境配置要求其实很朴素conda create -n yolo python3.10 -y conda activate yolo pip install ultralyticspython3.10是目前兼容性最稳的选择3.8太老、3.12太新有些依赖还没跟上。pip install ultralytics会连带装好torch、torchvision等核心依赖CPU版本也会装但训练速度差距感人。如果你有NVIDIA显卡建议先确认驱动支持到的CUDA版本nvidia-smi输出右上角的CUDA Version是驱动支持的最高版本不代表当前环境已安装。接下来装GPU版PyTorch时去PyTorch官网按你的CUDA版本复制对应安装命令。这一步我吃过大亏直接用默认的pip安装结果装回CPU版训练时用device0直接报CUDA不可用。装完验证一下python -c import torch; print(torch.cuda.is_available(), torch.__version__)打印True说明GPU可用后面训练命令里的device0才会生效。如果这里是False先别往下走回退到CUDA版本检查或者确认显卡驱动和PyTorch版本配对。4.2 数据文件配置yaml里的路径和names决定成败环境就绪后第二步是写数据配置文件。在数据集根目录新建一个grape.yaml内容如下# 葡萄叶片病害检测的数据配置 path: /home/user/grape_dataset # 换成你的数据集绝对路径 train: train.txt # 划分脚本生成的图片路径列表 val: val.txt test: test.txt # 可省略推理时用不到 # 类别列表顺序必须和labels目录里的txt标注编号一致 names: 0: black_rot # 黑腐病 1: esca # 轮斑病 2: leaf_blight # 叶枯病 3: leaf_spot # 叶斑病path用绝对路径最省事避免相对路径在训练工作目录不同时乱套。train和val两个字段可以写目录也可以写txt列表文件路径用划分脚本生成的train.txt和val.txt最灵活。names这个字段是翻车重灾区。你的TXT标注文件里写的是0 0.5 0.5 0.2 0.2这种第一个数字是类别id模型输出也会按这个id来而names里第0行是你定义的类名。顺序一旦和classes.txt不一致推理时明明识别对了显示出来的标签却是另一个病害名字。所以这里有且只有一个原则以数据集自带的classes.txt为准逐行抄进yaml不要重新排序、不要自己建字典。4.3 训练命令与关键参数batch、imgsz、epochs怎么调数据文件就绪启动训练yolo detect train \ datagrape.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ seed42这条命令里的每个参数都值得你把默认值弄明白modelyolov8n.pt预训练模型权重n是nano版最小最快显存占用也最低。如果你显卡好、想把精度拉高可以换yolov8s.pt或yolov8m.pt不同级别的预训练权重大小和推理速度差好几倍文件会自动从官方仓库下载。第一次跑如果网络卡在下载可以把权重文件放到当前目录手动指定路径。epochs100100轮对1000张图的小数据集足够通常50轮后mAP已经趋稳再往后提升很小。如果你看每轮日志发现val精度60轮还在明显上升就加50轮。imgsz640训练时输入图片会被缩放到640x640。叶片病害的斑点是细粒度特征如果你发现小病斑漏检严重可以提到960甚至1280显存会飙升收益不一定大从640开始最稳。batch16一次喂给GPU的图片数量。显存8G以下建议从8起步显存不足就降训练速度慢一点总比OOM中断好。device0使用第一块GPUCPU训练改成devicecpu但你要有等好几个小时的心理准备。seed42和我划分脚本里的随机种子保持一致保证结果可复现。不定种子的话哪怕数据一样两次训练出来的mAP都可能差1个点以上。训练启动后终端会滚动显示每个epoch的完整指标日志写到runs/detect/train/目录训练结束后这个目录里会出现best.pt和last.pt两个权重文件。best.pt是验证集上表现最好的权重部署和后续测试都拿它说话。4.4 训练日志里看什么box_loss、cls_loss、dfl_loss训练日志里一行一行的loss值很多人看一眼就划过去其实这些数字能直接告诉你训练有没有跑偏。YOLO的损失函数由三部分组成box_loss预测框和真实框的位置误差衡量框得准不准。cls_loss分类误差衡量类别判断对不对。dfl_loss分布焦点损失YOLOv8用来让框的回归更精确的辅助损失。三者的绝对值大小受模型规模和数据集复杂度影响没法跨项目比大小你要看的是趋势。正常情况是三个loss在前20轮快速下降之后缓慢走低曲线平滑验证集的指标mAP50和mAP50-95一路上涨然后趋平。翻车情况是这样box_loss清零了但cls_loss还在0.5以上下不来大概率某个类别的样本数太少模型还没学会反过来cls_loss很低、box_loss居高不下说明目标大小差异过大小病斑学不好最吓人的是loss突然变成nan这是梯度爆炸了调低学习率或切到optimizerAdamW往往能救回来。训练完看验证集指标重点看mAP50-95它对框的精确位置要求更严苛比mAP50更能反映模型真实水平。叶片病害这个场景0.5以上的mAP50-95已经能应付大部分检测需求如果你跑出来只有0.2左右优先怀疑数据和配置其次才是模型。5. 常见问题排查数据、标注与训练全过程的翻车点5.1 样本数对不上图片和标签文件不同名现象训练日志里出现类似found 1000 images in train, 900 labels的提示或者跑着跑着发现val上的mAP波动剧烈明显是某些类没学会。原因图片和标签文件名不一致。最常见的是图片是0001.jpg标签是0001.txt但混进了别的后缀比如0001.txt.txt还有的原来是PNG图片标注时按jpg存的txt图片改名后标签没跟上。解决写一个脚本遍历训练和验证集里的所有图片名检查同名txt是否存在missing [] for line in open(train.txt, encodingutf-8): img_path line.strip() label_path img_path.replace(images, labels).rsplit(., 1)[0] .txt if not os.path.exists(label_path): missing.append(img_path) print(fmissing {len(missing)} labels) # 输出缺失列表按文件名反查是改名还是漏标 for p in missing[:20]: print(p)找出缺失列表后回到原始图片目录确认是漏标还是命名错误。漏标的补标没有捷径自己用labelimg或vscode的yolo labeling插件重打一遍命名错误的写个批量rename脚本一起改掉。这类问题在现成数据集里不常见但自己扩充数据时几乎必遇。5.2 推理出来类别全乱classes.txt顺序踩坑现象训练loss正常mAP也不算差但拿模型去测试图片明明检测到的是霜霉病输出的框上却写着叶斑病而且错误类别有固定的错位规律。原因训练配置里的names顺序和标注txt里的类别id不对应。比如数据集classes.txt里第0行是霜霉病你配置yaml时却把第0行写成了叶斑病模型输出的id 0就对应了你的叶斑病名字整条类别链条全部错位。解决训练前把grape.yaml里的names和数据集自带的classes.txt逐行对照cat classes.txt cat grape.yaml两边行号相同的类名必须完全一致。自己从头打标的数据没有classes.txt也没关系但前提是你用labelimg打标时填写的类别名顺序和你写进yaml的顺序是同一个。labelimg里第一次输入类别名的顺序就会固定成class id中途加新类别要慎重因为新类别会追加到列表末尾历史标注里的id会整体错位。5.3 loss为nan或直接不收敛现象训练从第一个epoch开始loss就显示nan或者loss卡在某个值死活不降验证集mAP在0.1附近横盘。原因最常见的是学习率设置和模型不匹配。默认学习率0.01配SGD优化器在YOLO官方配置下没问题但小批量、小数据集上很容易梯度爆炸出nan。另一类原因是数据里有空标注文件也就是labels目录下某个txt是0字节模型计算loss时遇到空目标数值发散。解决先排查空标签文件文件大小为0的一律删掉或重新标注find labels -size 0 -name *.txt | wc -l如果数量为0再调学习率yolo detect train \ datagrape.yaml \ modelyolov8n.pt \ epochs100 \ lr00.001 \ optimizerAdamWlr0从0.01降到0.001SGD换成AdamW这两个改动组合对不稳定训练有奇效。乳酸率降到0.0005也试过能收敛就是慢一点总比发散好。5.4 显存爆掉batch、imgsz与缓存现象训练刚开始就报CUDA out of memory进程直接被杀死之前跑过的epoch全部作废。原因imgsz640加上batch16在8G显存的显卡上本来就勉强叶片数据如果原图是4000x3000的大图加载时内存和显存压力都会上一档。解决按这个顺序试改到不报错为止# 第一步batch降到4 yolo detect train ... batch4 ... # 第二步imgsz降到512 yolo detect train ... imgsz512 ... # 第三步不开缓存 yolo detect train ... cacheFalse ...cache参数默认是False但如果之前设置过cacheTrue想把数据缓存进显存遇到OOM先把它关掉。降imgsz对显存的缓解比降batch更直接因为显存占用跟输入尺寸是平方关系。副作用是病斑这类小目标在512分辨率下更容易丢所以能保持640还是尽量640用batch4来换。5.5 mAP虚高但实际效果差数据泄漏的隐蔽来源现象val上的mAP达到0.8看着很漂亮但拿着训练好的模型去拍真实的田间照片漏检和误检一堆完全不是那个成绩。原因数据泄漏。最常见的是数据划分不干净——同一株葡萄的多张照片被吃了不同角度划分时随机切同一株的图片散布在train和val里val里都是train的“亲戚”评估成绩自然虚高。还有一种是数据集本身有重复图扩充数据时不小心把同一张图复制多次改名训练集和测试集就出现了同图不同名的副本。解决划分后做一遍去重用图片内容的md5哈希或感知哈希比对md5sum images/*.jpg | sort | awk {print $1} | uniq -d输出重复哈希的话只保留其中一张别偷懒删同名不删内容。更彻底的做法是重跑划分脚本在洗牌前把来自同一采集点的图片按来源分组让一个分组整体进train或整体进val防止相近样本穿帮。这种问题从训练日志上看不出任何异常只能靠检查数据血缘来挡。6. 进阶用训练好的模型做批量推理并调置信度门限6.1 批量推理命令与输出目录训练完拿测试图片验证效果是本地部署训练这套流程最出成果的一步。用best.pt对新照片批量推理非常简单yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest_images/ \ conf0.25 \ iou0.7 \ saveTruesource可以指向一个目录也可以指向单张图片。预测结果会写到runs/detect/predict/每张图片会自动画上检测框和类别名你翻一遍图片就能直观看出模型哪些病害认得好、哪些认不清。6.2 置信度门限调整误检高就调高confconf就是置信度门限模型只有预测概率超过这个值的框才会输出。默认0.25但实际部署不能一路默认走到底。如果你看到推理结果里误检框特别多比如把正常叶片也框出来了把露珠当病斑把反光的区域也标了病害那就把conf从0.25往上调0.35、0.45逐档试。调高后面板干净很多代价是真正的小病斑可能会跟着被过滤掉。反过来如果漏检严重——该框的病害没框出来说明门限太高往0.15、0.1降。降低门限后输出框会变多、重叠框也多这时iou参数开始起作用它控制NMS阶段两个重叠框的保留策略值越小抑制越狠、输出的框越少。我自己用的习惯是原生测试用conf 0.25现场部署用conf 0.4起步因为误检在农业场景里比漏检更难接受——给农户的报告里指错病害比少指一处的信任成本高得多。推理效果稳定后真正让模型变强的是把测试集里那些错检、漏检的图片收集起来手动标注后加入训练集重训一版。这比调任何参数都有效。我自己训练葡萄叶片病害这类细粒度检测模型血的教训就是不要迷信第一版best.pt的mAP把它当基线后续靠硬样本迭代才有真正的精度提升。希望这个思路对你也有用帮你在自己的数据集上少走我走过的弯路。本文还有配套的精品资源点击获取