ARTICLE DETAIL

资讯详情

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

柑橘病害检测数据集实战:VOC+YOLO双格式与YOLOv8训练调优

柑橘病害检测数据集实战:VOC+YOLO双格式与YOLOv8训练调优 简介本资源为橙子、橘子、桔子果实病害检测数据集采用Pascal VOC与YOLO双格式标注面向从事农业视觉检测、目标检测模型训练的学生与算法工程师可用于果实病害识别、分类与定位等任务。数据集共2814张jpg图片每张均配有对应的VOC格式xml标注文件与YOLO格式txt标注文件标注类别为blackspot、canker、fresh、grenning四类需注意其标注对象为果实病害而非叶片病害。压缩包内共2000个文件以1999个xml标注文件为主另含1个使用前必读的txt说明文件整体约82.45MB目录结构清晰便于直接接入YOLO或VOC训练流程。目前已有504人学习下载适合需要快速获取标注数据、验证检测模型效果或开展病害识别实验的读者参考使用。1. 橙子橘子桔子病害检测数据集2814 张 VOCYOLO 双格式到底怎么用果园里最怕的不是虫是叶子上的斑。橙子、橘子、桔子这三样东西在植物学上同属柑橘属叶片形态接近、病害表征也高度相似但不同病害的处置方式完全不同——黄龙病要拔树溃疡病要剪枝喷药炭疽病要清园。你拿手机拍一张病叶人眼都未必分得清更别说让一个刚入门的目标检测模型去区分。这个数据集要解决的就是这件事2814 张标注好的柑橘叶片病害图像同时提供 VOC 和 YOLO 两种格式4 个类别拿来就能训。它适合谁如果你手头有果园巡检的落地需求或者正在做农业视觉方向的课程设计、竞赛项目又或者你只是想找一个类别少、体量适中、标注质量可控的数据集来跑通 YOLO 全流程这个数据集的门槛刚好。2814 张不算大单卡就能训4 个类别不算多但足够让你把数据清洗、格式转换、增强策略、置信度调优这一整套链路走一遍。下面我从数据本身讲到训练落地再到踩过的坑按能复现的顺序展开。2. VOC 与 YOLO 双格式拆解目录结构、标注差异与选型判断拿到一个压缩包第一件事不是急着解压跑训练而是先搞清楚里面装了什么。这个数据集标称 VOCYOLO 双格式意味着同一批图像有两套标注体系。VOC 格式用 XML 存标注YOLO 格式用 TXT 存归一化坐标。两者指向同一批图但读取逻辑完全不同。你得先判断自己该用哪套再决定后续流程。2.1 VOC 格式的目录长什么样XML 里哪些字段真正有用VOC 格式的标准目录结构是Annotations、JPEGImages、ImageSets三件套。Annotations放 XMLJPEGImages放原图ImageSets/Main放训练验证划分文件。XML 里字段很多但做病害检测真正要关心的只有几个filename对应图像文件名size里的width和height用于校验坐标是否越界object下的name是类别名bndbox里的xmin/ymin/xmax/ymax是绝对像素坐标。import xml.etree.ElementTree as ET import os def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() # 图像尺寸用于后续坐标校验 size root.find(size) w int(size.find(width).text) h int(size.find(height).text) objects [] for obj in root.findall(object): name obj.find(name).text # 类别名 bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 越界检查VOC 标注常见问题是坐标超出图像边界 if xmin 0 or ymin 0 or xmax w or ymax h: print(f越界: {xml_path} - {name} ({xmin},{ymin},{xmax},{ymax})) objects.append((name, xmin, ymin, xmax, ymax)) return w, h, objects这段代码的逻辑很直白解析 XML提取尺寸和每个目标框的绝对坐标同时做一次越界检查。参数上唯一需要注意的是xmax和ymax是否包含边界像素——VOC 惯例是包含的但不同标注工具产出的 XML 可能有 1 像素偏差。如果你后续要转 YOLO 格式这个偏差会被归一化放大值得在转换前先跑一遍全量校验。2.2 YOLO 格式的 TXT 怎么读归一化坐标的换算与校验YOLO 格式每张图对应一个 TXT每行一个目标格式是class_id x_center y_center width height全部归一化到 0~1。类别 ID 从 0 开始具体哪个 ID 对应哪个病害名得看数据集附带的classes.txt或data.yaml。这个数据集有 4 个类别ID 大概率是 0 到 3但你不能假设必须打开配置文件确认。def parse_yolo_txt(txt_path, img_w, img_h): with open(txt_path, r) as f: lines f.readlines() boxes [] for line in lines: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {txt_path} - {line}) continue cls_id int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) # 反归一化回像素坐标方便和 VOC 标注做交叉验证 xmin int((xc - bw / 2) * img_w) ymin int((yc - bh / 2) * img_h) xmax int((xc bw / 2) * img_w) ymax int((yc bh / 2) * img_h) boxes.append((cls_id, xmin, ymin, xmax, ymax)) return boxes反归一化这步很关键。很多人直接拿 YOLO 格式训练不回头看像素坐标结果标注框偏了都不知道。我的习惯是随机抽 20 张图把 YOLO 反算出来的框和 VOC 的框做对比偏差超过 2 像素的就说明两套标注不是同一批人做的或者转换脚本有 bug。这个数据集标称双格式理论上应该一致但血泪经验告诉我抽查这一步不能省。2.3 该用 VOC 还是 YOLO从训练框架反推格式选择选哪个格式取决于你用什么框架。Ultralytics 系的 YOLOv5/v8/v11 直接吃 YOLO 格式目录结构是images/train、labels/train加一个data.yaml。如果你用 MMDetection 或 Detectron2VOC 格式转 COCO 更顺。但如果你只是想把流程跑通我建议直接用 YOLO 格式省掉一次转换。对比项VOC 格式YOLO 格式标注文件XML字段多TXT每行一个目标坐标类型绝对像素归一化 0~1类别映射直接写类别名需要 classes.txt 或 data.yaml适用框架MMDetection、Detectron2Ultralytics YOLO 系列转换难度转 YOLO 需写脚本直接用无需转换如果你拿到的压缩包里两套格式都有先确认它们是否一一对应。常见做法是图像文件夹共用标注文件夹分开。你只需要把 YOLO 那套的images和labels按训练集/验证集切好写一个data.yaml指向类别名就能开训。3. 从压缩包到可训练2814 张图的清洗、划分与 data.yaml 配置数据清洗这步很多人跳过然后训练时 loss 不降反升回头查半天才发现是标注有问题。2814 张图不算多全量跑一遍校验脚本也就几分钟但这几分钟能帮你省掉几小时的排查。下面按清洗、划分、配置三步走。3.1 用脚本做全量标注校验空文件、越界框、类别名拼写清洗的核心是三个检查标注文件是否为空、坐标是否越界、类别名是否有拼写不一致。空文件意味着这张图没有目标如果这种图大量存在模型会学到“什么都检测不到”的退化解。越界框在训练时会被裁剪但裁剪后的框可能变得极小反而引入噪声。类别名拼写不一致更隐蔽比如“溃疡病”和“溃疡病 ”带了个空格会被当成两个类。import os from collections import Counter def validate_dataset(images_dir, labels_dir, class_names): empty_labels [] invalid_boxes [] class_counter Counter() for label_file in os.listdir(labels_dir): if not label_file.endswith(.txt): continue path os.path.join(labels_dir, label_file) if os.path.getsize(path) 0: empty_labels.append(label_file) continue with open(path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: invalid_boxes.append((label_file, 字段数不对)) continue cls_id int(parts[0]) vals list(map(float, parts[1:])) # 归一化坐标必须在 0~1 之间 if any(v 0 or v 1 for v in vals): invalid_boxes.append((label_file, 坐标越界)) class_counter[cls_id] 1 print(f空标注文件: {len(empty_labels)}) print(f异常框: {len(invalid_boxes)}) print(f类别分布: {dict(class_counter)}) return empty_labels, invalid_boxes跑完这个脚本你会得到三个数字空标注数量、异常框数量、每个类别的框数。如果某个类别的框数特别少比如不到总数的 5%训练时这个类别大概率学不好需要考虑过采样或加权损失。空标注如果超过总数的 10%建议直接剔除这些图否则模型会偏向于预测背景。3.2 训练集/验证集怎么切8:2 还是 7:3随机种子要不要固定2814 张图我一般按 8:2 切训练集 2251 张验证集 563 张。如果类别极不平衡比如某个病害只有几十个框那就按类别分层采样保证验证集里每个类别都有足够样本。随机种子必须固定否则每次切分结果不同实验没法复现。import random import os import shutil def split_dataset(images_dir, labels_dir, output_dir, val_ratio0.2, seed42): random.seed(seed) images [f for f in os.listdir(images_dir) if f.endswith((.jpg, .png))] random.shuffle(images) val_count int(len(images) * val_ratio) val_images images[:val_count] train_images images[val_count:] for split, files in [(train, train_images), (val, val_images)]: 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 in files: shutil.copy(os.path.join(images_dir, img), os.path.join(img_out, img)) label os.path.splitext(img)[0] .txt src_label os.path.join(labels_dir, label) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(lbl_out, label)) print(f训练集: {len(train_images)}, 验证集: {len(val_images)})参数上val_ratio设 0.2 是常规做法数据量小于 2000 时可以调到 0.3 让验证更稳。seed固定成 42 只是我的习惯你换成任何固定值都行关键是别每次跑都变。切分完成后检查一下labels/val里是不是每个类别都有样本如果某个类别在验证集里一个框都没有那验证指标就没意义了。3.3 data.yaml 的四个必填字段与路径写法YOLO 训练靠data.yaml找数据。这个文件必须包含path、train、val、names四个字段。path是数据集根目录train和val是相对路径names是类别名列表。路径写法有个坑Windows 下反斜杠要转义或改用正斜杠Linux 下相对路径的基准是path字段。path: /data/citrus_disease train: images/train val: images/val nc: 4 names: 0: huanglongbing 1: kuiyangbing 2: tanjubing 3: healthync是类别数必须和names的长度一致。names的顺序就是类别 ID 的顺序写反了模型会把黄龙病认成健康叶。如果你不确定原始数据集的类别顺序打开一个 YOLO TXT 看class_id的分布再对照classes.txt确认。这个数据集有 4 个类别具体名称以压缩包内附带的说明为准我上面写的只是占位示例。4. YOLOv8 训练柑橘病害检测环境配置、关键参数与置信度门限调优环境配置这步Anaconda 建虚拟环境是最稳的。YOLOv8 对 PyTorch 版本有要求CUDA 版本和显卡驱动也要对得上。我见过太多人卡在torch.cuda.is_available()返回 False 上然后开始怀疑人生。下面按环境、训练、调参三步走每步都给可复现的命令。4.1 Anaconda 环境配置与 YOLOv8 安装的版本对应关系先建环境Python 版本选 3.9 或 3.10这两个版本和 PyTorch 的兼容性最好。然后装 PyTorch再装 ultralytics。顺序不能反否则 ultralytics 可能拉一个不匹配的 torch 版本。conda create -n citrus_yolo python3.10 -y conda activate citrus_yolo # 根据你的 CUDA 版本选对应的 PyTorch 安装命令 # CUDA 11.8 用下面这行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 装 ultralytics pip install ultralytics # 验证 python -c import torch; print(torch.cuda.is_available())最后那行验证必须返回 True。如果返回 False先查显卡驱动版本再查 CUDA 版本是否和 PyTorch 匹配。V100 和消费级卡在驱动要求上略有差异但只要是 CUDA 11.8 以上一般都能跑。环境装好后yolo命令应该可以直接用yolo version能输出版本号就说明安装成功。4.2 启动训练命令行参数逐项拆解与显存占用估算YOLOv8 的训练命令很简洁但每个参数都影响结果。下面这条命令是我在单卡 16G 显存上跑 2814 张图的常用配置。yolo detect train \ data/data/citrus_disease/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ device0 \ projectcitrus_runs \ nameexp01逐项说model选yolov8s.pt是因为 2814 张图用 nano 可能欠拟合用 medium 又容易过拟合small 是折中。epochs150配合patience30意思是 30 轮验证指标不提升就早停避免浪费时间。batch16在 16G 显存上跑 640 分辨率刚好如果显存不够就降到 8同时把lr0从 0.01 降到 0.005。imgsz640是 YOLO 的默认值病害斑点通常不大640 够用想更精细可以上 1024但显存翻倍。训练启动后看第一轮的box_loss和cls_loss。如果box_loss在 2.0 以上且不降大概率是标注有问题回去跑第 3 章的校验脚本。如果cls_loss震荡剧烈可能是类别不平衡考虑加cls权重或过采样。4.3 置信度门限怎么调从默认 0.25 到病害场景的实测值YOLO 默认的置信度门限是 0.25NMS 的 IoU 门限是 0.45。这两个值在通用目标检测上没问题但病害检测场景不一样。病斑小、边缘模糊、同类病斑密集默认门限要么漏检要么误检。我的做法是训练完后在验证集上跑一遍预测把置信度从 0.1 到 0.9 按 0.05 步长扫一遍画 P-R 曲线找 F1 最高的点。from ultralytics import YOLO import numpy as np model YOLO(citrus_runs/exp01/weights/best.pt) results model.val(data/data/citrus_disease/data.yaml, conf0.001, iou0.45) # 从 val 结果里提取不同置信度下的 P/R # 实际调参时用 predict 模式在验证集上逐张跑记录每个门限的 TP/FP/FN实操上我一般把最终部署的置信度门限设在 0.35 到 0.45 之间。低于 0.35背景被误检成病斑的概率明显上升高于 0.45小病斑开始漏检。这个区间不是绝对的取决于你的验证集质量和病害类型。溃疡病的斑块边界清晰门限可以高一点炭疽病的斑块弥散门限要低一点。调完门限后别忘了同步调 NMS 的 IoU 门限密集病斑场景下 0.45 可能偏低调到 0.5 到 0.6 能减少漏检。5. 训练柑橘病害模型踩过的坑从 loss 不降到验证集指标虚高这一章是我自己踩过的坑每条都按现象、原因、解决来写。你如果正在训这个数据集或者训任何农业病害数据集这些坑大概率会碰到。5.1 现象loss 降到 0.5 就不动了验证 mAP 也卡在 0.4原因最常见的是标注框和图像尺寸不匹配。VOC 转 YOLO 时如果转换脚本用的图像尺寸和实际图像尺寸不一致归一化坐标就会整体偏移。另一个原因是学习率设太大模型在局部最优附近震荡。解决先跑第 3 章的校验脚本确认所有归一化坐标在 0~1 之间。然后抽 10 张图把 YOLO 标注画到原图上肉眼检查框是否贴合病斑。如果框偏了回去查转换脚本里的img_w和img_h是不是从 XML 的size字段读的。学习率方面把lr0从 0.01 降到 0.001加cos_lrTrue让学习率余弦衰减通常能突破平台期。5.2 现象验证集 mAP 0.85但拿果园实拍图一测全错原因验证集和训练集同分布但和真实场景不同分布。这个数据集的 2814 张图大概率是在固定光照、固定背景下拍的而果园实拍有逆光、有遮挡、有不同生长阶段。模型学到了背景特征而不是病斑特征。解决在训练时加强增强特别是hsv_h、hsv_s、hsv_v这三个颜色增强参数分别调到 0.015、0.7、0.4。再加mosaic1.0和mixup0.1让模型见过更多组合场景。如果条件允许拿几十张果园实拍图做微调哪怕只标 50 张也能显著提升泛化。5.3 现象某个类别始终检测不到其他类别正常原因类别不平衡。4 个类别里健康叶的样本可能占了一半而某种病害只有几十个框。模型倾向于预测多数类少数类被忽略。解决先统计每个类别的框数。如果最少类别不到最多类别的 1/10用过采样把少数类复制到和多数类相当。或者在data.yaml里加cls_pw参数给少数类更高的损失权重。YOLOv8 支持在训练时用cls0.8调分类损失的权重但更直接的办法是过采样。5.4 现象训练到一半突然 CUDA out of memory原因YOLOv8 在训练后期会做更复杂的增强显存占用会上升。如果一开始就贴着显存上限跑后期必然爆。解决把batch降到 8同时开ampTrue混合精度训练。如果还爆把imgsz从 640 降到 512。另一个技巧是设cacheFalse不把图像缓存到内存虽然慢一点但显存稳。V100 有 16G 和 32G 两个版本16G 跑 640 分辨率 batch 16 是极限建议留 2G 余量。5.5 现象验证集指标波动大每次跑结果差很多原因随机种子没固定或者验证集太小。563 张验证集如果类别分布不均每次采样的难度不同指标自然波动。解决固定seed42并且在data.yaml里确保验证集覆盖所有类别。如果验证集某个类别样本少于 30 个指标波动会很大考虑用交叉验证或者扩大验证集比例到 0.3。另外deterministicTrue能让 CUDA 卷积结果可复现但会牺牲一点速度。6. 把 2814 张图用透小数据集上的增强策略与模型选型技巧2814 张图在目标检测里算小数据集。小数据集的核心矛盾是模型容量大了过拟合容量小了欠拟合。我的经验是与其换更大的模型不如把增强做透。YOLOv8 自带的增强参数有十几个但真正对病害检测有效的就那么几个。先说颜色增强。病害检测最怕模型学颜色而不是学形状。健康叶是绿的病斑是黄的褐的模型很容易走捷径看到黄色就判病。但不同光照下健康叶也可能偏黄。所以hsv_h要调大0.015 到 0.03 之间让色调随机偏移。hsv_s和hsv_v分别控制饱和度和亮度0.7 和 0.4 是常用值能模拟不同天气和拍摄角度。再说几何增强。degrees旋转我一般设 10 到 15因为叶片在自然状态下不会大角度旋转。translate设 0.1scale设 0.5让病斑在画面中的位置和大小有变化。flipud和fliplr都设 0.5叶片上下左右翻转不影响病害判断。mosaic设 1.0这是 YOLO 的招牌增强把四张图拼成一张能显著提升小数据集的泛化。mixup设 0.1 到 0.2把两张图按透明度叠加对密集病斑场景有帮助。模型选型上2814 张图我建议从yolov8s.pt开始。如果验证集 mAP 能到 0.7 以上说明数据质量没问题可以试yolov8m.pt看能不能再涨。如果yolov8s就过拟合训练 mAP 0.9验证 mAP 0.5那就退回yolov8n.pt同时加大增强。预训练模型一定要用yolov8s.pt在 COCO 上训过底层特征提取器已经学会了边缘和纹理比你从零训快得多。最后一个技巧用yolo export把训练好的模型导出成 ONNX 或 TensorRT推理速度能提升 2 到 3 倍。果园巡检如果是边缘设备部署TensorRT 是必选项。导出命令很简单yolo export modelcitrus_runs/exp01/weights/best.pt formatengine halfTrue device0halfTrue是 FP16 量化精度损失很小速度提升明显。导出后拿几张验证集图片跑一下确认检测结果和 PyTorch 版一致。如果不一致检查导出时的imgsz和训练时是否相同。我自己训这个数据集最大的教训是别急着调模型先把标注校验跑三遍。我第一版模型 mAP 只有 0.3查了两天才发现是转换脚本把xmax和ymax多减了 1 像素导致所有框偏小一圈。改完重新训mAP 直接到 0.72。小数据集上数据质量比模型结构重要十倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表