多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

茶叶病害数据集VOC+YOLO格式883张8类别:从标注转换到YOLO训练全流程

茶叶病害数据集VOC+YOLO格式883张8类别:从标注转换到YOLO训练全流程 简介这份茶叶病害数据集面向农业图像识别、植物保护研究及深度学习目标检测方向的开发者与学习者提供VOC与YOLO双格式标注可直接用于训练和验证叶片病害检测模型。压缩包共约2000个文件包含883张jpg图片、883个VOC格式xml标注、883个YOLO格式txt标注及少量说明文件整体约200.5MB图片与标注一一对应省去格式转换环节。数据集覆盖8个类别包括藻斑病、炭疽病、鸟眼斑、褐枯病、灰枯病、健康叶、红叶斑和白斑总标注框数884个其中红叶斑与白斑样本较多健康叶相对较少便于分析类别分布。所有图片均为单叶拍摄每图仅含一片叶子标注由labelImg完成采用矩形框规则适合单目标检测任务。目前已有887人学习下载可作为茶叶病害识别项目、课程实验或算法对比的实用数据基础。1. 茶叶病害数据集 VOCYOLO 格式 883 张 8 类别从拿到压缩包到跑通第一轮训练茶叶病害检测这件事真正卡住大多数人的从来不是模型结构而是数据。你手上有一批茶园拍的病叶照片想用 YOLO 训一个能识别赤叶斑、轮斑、白星、灰枯、云纹、炭疽、茶饼、健康叶这八类的检测器结果打开标注文件发现是 VOC 的 XML而 YOLO 只认归一化的 txt。这个数据集标题里的「VOCYOLO 格式 883 张 8 类别」说的就是这件事它同时给了两套标注省掉你自己写转换脚本的功夫。883 张不算多8 类均下来每类一百出头属于典型的小样本检测任务适合入门练手、也适合验证某个数据增强策略到底有没有用。这篇笔记按我实际处理这类数据集的顺序走一遍先看清目录和标注长什么样再决定用哪套格式、怎么切分、怎么配训练参数最后把几个必踩的坑摊开讲。2. 先摸清 VOC 与 YOLO 两套标注的差异再决定用哪套2.1 VOC 的 XML 和 YOLO 的 txt 到底差在哪VOC 格式的核心是一个 XML 文件对应一张图里面记录了图片尺寸、每个目标的类别名和左上角、右下角两个坐标点。YOLO 格式则是一个 txt 对应一张图每行一个目标格式是「类别索引 中心x 中心y 宽 高」四个数值全部除以图片宽高归一化到 0 到 1 之间。差别看着只是坐标换算实际影响的是三件事类别是字符串还是整数索引、坐标是绝对像素还是归一化比例、一个文件装一张图还是多张图。数据集同时给两套意味着你可以直接用 YOLO 那套喂给 ultralytics 系的训练脚本也可以拿 VOC 那套去做别的框架或者自己校验标注质量。我一般会先做一次交叉校验随机抽十张图把 VOC 的框按公式换算成 YOLO 坐标和数据集自带的 YOLO txt 对一遍。对得上说明两套标注是同一批人同一轮标的可以放心用对不上就得查是哪套更新过。换算公式不复杂但边界情况多下面这段脚本就是干这个的。import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, img_w, img_h, class_map): 把单个 VOC XML 转成 YOLO 行列表class_map 是 类别名-索引 tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: continue # 类别名对不上就跳过别硬塞 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化中心点 宽高全部除以图片尺寸 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪到 [0,1]防止标注越界导致训练报错 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines这段代码里class_map必须和数据集自带的classes.txt或data.yaml里的顺序完全一致顺序错了模型学出来的类别就是乱的。img_w和img_h要从 XML 的size节点读不要用 PIL 重新打开图片去读因为有些数据集标注时用的尺寸和实际图片尺寸不一致以 XML 里写的为准才能和原始标注对齐。最后那个裁剪到 0 到 1 的操作是后悔药标注员手抖把框拖出图片边界是常事不裁的话训练时归一化坐标超过 1 会直接抛异常。2.2 883 张 8 类别先算清楚每类有多少拿到数据集第一件事不是急着训是统计类别分布。8 个类别如果严重不均衡比如健康叶有三百张、茶饼只有二十张那训练时就得考虑过采样或者调损失权重。用下面这段脚本把每类的框数量数出来心里有底再往下走。from pathlib import Path from collections import Counter label_dir Path(labels) # YOLO txt 所在目录 counter Counter() for txt in label_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): if not line: continue cls_id int(line.split()[0]) counter[cls_id] 1 for cls_id, num in sorted(counter.items()): print(f类别 {cls_id}: {num} 个框)跑完你会得到类似「类别 0: 156 个框、类别 1: 98 个框」这样的分布。注意统计的是框数量不是图片数量一张图可能有多个病斑。如果某个类别框数低于 50小样本检测里它大概率训不好要么补数据要么在增强上给它单独加权重。这一步花两分钟能省掉后面调半天参还找不到原因的功夫。2.3 目录结构怎么摆才能被训练脚本认出来ultralytics 系的 YOLO 对目录结构有固定要求根目录下images和labels两个文件夹各自再分train、val、test。图片和标注文件名必须一一对应只是扩展名不同。数据集如果给的是扁平的JPEGImages加Annotations你得自己建这套结构再按比例拷贝过去。常见做法是 8:1:1 切分883 张大概 706 训练、88 验证、89 测试。切分时要注意同一片茶园、同一株茶树的照片别同时出现在训练和验证集里否则验证指标虚高实际部署就翻车。# 建目录 mkdir -p dataset/images/train dataset/images/val dataset/images/test mkdir -p dataset/labels/train dataset/labels/val dataset/labels/test # 假设图片在 JPEGImages标注在 labels_yolo # 用 python 做随机切分并拷贝这里给个思路实际按你的文件名规则来 python -c import random, shutil from pathlib import Path imgs sorted(Path(JPEGImages).glob(*.jpg)) random.seed(42); random.shuffle(imgs) n len(imgs); n_train int(n*0.8); n_val int(n*0.1) splits {train: imgs[:n_train], val: imgs[n_train:n_trainn_val], test: imgs[n_trainn_val:]} for split, files in splits.items(): for f in files: shutil.copy(f, fdataset/images/{split}/{f.name}) lbl Path(labels_yolo) / (f.stem .txt) if lbl.exists(): shutil.copy(lbl, fdataset/labels/{split}/{lbl.name}) random.seed(42)是为了切分可复现团队协作时大家拿到同一份划分。拷贝而不是移动保留原始数据不动后面发现切分有问题还能重来。如果标注文件名和图片名不是简单换扩展名得先写个映射表别硬套。3. 用 YOLOv8 跑通第一轮训练配置、命令与参数3.1 data.yaml 里每个字段的含义和填法训练脚本靠一个 yaml 文件找到数据和类别名。这个文件写错一个字段训练直接起不来。典型内容长这样path: /home/user/dataset # 数据集根目录绝对路径最稳 train: images/train # 相对 path 的训练图片目录 val: images/val test: images/test names: 0: 赤叶斑 1: 轮斑 2: 白星 3: 灰枯 4: 云纹 5: 炭疽 6: 茶饼 7: 健康叶path用绝对路径相对路径在不同工作目录下跑会找不到文件这是新手最常见的报错来源。names的索引必须和 txt 里的类别索引严格对应顺序错了模型输出的类别名就全乱。类别名用中文没问题但如果你后面要导出到某些只认英文的部署框架建议这里就用英文显示层再做映射。3.2 从命令行启动训练的最小配置ultralytics 装好后一条命令就能起训。第一次跑建议先用小模型和少轮次验证流程通不通别一上来就上大模型跑三百轮。yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/tea \ namebaselinemodelyolov8n.pt是最小的 nano 模型883 张数据用 nano 或 small 就够大模型在这个数据量上只会过拟合。imgsz640是检测任务的常用输入尺寸茶叶病斑有的很小如果发现小目标漏检多可以提到 800 或 1024但显存和速度要重新算。batch16在 8G 显存上跑 640 尺寸基本安全爆显存就降到 8。device0指定第一块 GPU没有 GPU 就写cpu但 883 张用 CPU 训一百轮会等到怀疑人生。3.3 训练过程中该盯哪几个指标起训后终端会刷一堆指标别只看 loss。重点盯三个mAP50、mAP50-95和每类的instances。mAP50 是 IoU 阈值 0.5 时的平均精度小样本任务里它涨到 0.6 以上算及格0.8 以上算不错。mAP50-95 更严格通常比 mAP50 低一截它更能反映框的定位准不准。如果 mAP50 高但 mAP50-95 很低说明类别分对了但框画得歪得检查标注质量。每类的 instances 告诉你验证集里每类有多少个目标某类 instances 是 0 说明验证集里根本没这类样本那它的指标没有参考意义。训练完在runs/tea/baseline下会生成results.csv和一堆曲线图。confusion_matrix.png特别值得看能直接看出哪两类在互相混淆。茶叶病害里轮斑和云纹纹理接近混淆矩阵上它们俩交叉位置数值高是正常的这时候要么补这两类的区分性样本要么在推理时调低置信度阈值观察。4. 小样本茶叶病害检测的避坑与排查4.1 训练 loss 不降反升先查标注而不是调学习率现象起训后 box_loss 和 cls_loss 震荡上升mAP 一直是 0。原因八成是标注有问题最常见的是类别索引越界或者坐标没归一化。YOLO 的 txt 里如果混进了 VOC 那种绝对像素坐标数值几百上千训练时归一化后框全挤在角落loss 自然不降。解决跑一遍校验脚本检查每行第一个数是否在 0 到类别数减一之间后四个数是否都在 0 到 1 之间。发现越界就回到第 2 章那个转换脚本重新生成。4.2 验证集 mAP 很高但实际图片检测不出来现象训练日志里 mAP50 到 0.9 了拿几张新拍的茶叶照片一测框都画不出来。原因验证集和训练集来自同一批拍摄条件光照、背景、角度都相似模型记住了背景而不是病斑特征。883 张如果全是一个茶园一个季节拍的泛化能力必然差。解决切分时按拍摄批次分别随机分训练时开强增强mosaic、mixup、随机亮度对比度都加上有条件的补拍不同天气不同时段的数据。4.3 小病斑漏检严重调 imgsz 比换模型管用现象大块病斑能检出早期小斑点全漏。原因640 输入下原图里几十像素的病斑缩到几像素特征在下采样中丢了。解决先把imgsz提到 1024 试一轮通常小目标召回会明显改善。如果显存不够改用切片推理把大图切成重叠的小块分别检测再合并。换更大的模型反而未必有用因为问题出在输入分辨率不在模型容量。4.4 类别不均衡导致稀有类几乎检不出现象健康叶和赤叶斑检出率很高茶饼、炭疽这类样本少的类别几乎不出框。原因损失函数被多数类主导少数类的梯度被淹没。解决在 data.yaml 同级加一个cls_pw权重或者用focal lossultralytics 里可以通过自定义损失实现。更直接的办法是对少数类做离线增强把它的图片复制多份加不同变换让每类框数拉到同一量级。注意增强别只做翻转茶叶病斑翻转后语义不变但旋转和色彩抖动更贴近真实拍摄变化。4.5 推理时置信度阈值设 0.25 漏检、设 0.05 误检爆炸现象默认 conf 0.25 时漏检多调到 0.05 后满屏假框。原因模型对某些类的置信度分布本身就不集中单一阈值切不干净。解决别用全局阈值按类别分别设。先跑一批验证图导出每类的置信度分布取能让该类 F1 最高的阈值。ultralytics 推理时可以用classes参数分次跑也可以后处理时按类别过滤。这一步没有银弹得拿验证集实际调。5. 把 883 张用到极致增强策略与验证技巧数据量小的时候增强策略的选择比模型结构重要得多。我一般会分两阶段前 50 轮开强增强让模型学鲁棒特征后 50 轮关掉 mosaic 和 mixup 让模型在真实分布上收敛。ultralytics 里可以用close_mosaic参数控制在第几轮关闭设成close_mosaic50就是第 50 轮后关掉。这个技巧在 883 张这种量级上效果很明显mAP50-95 通常能涨两三个点。验证的时候别只看一个 mAP 数字。把验证集按类别拆开算每类的 precision 和 recall做成表格对比。下面是我处理这类数据集时会记录的一张表类别框数PrecisionRecall主要混淆对象赤叶斑1560.820.79轮斑轮斑980.710.68云纹白星1120.850.81健康叶灰枯760.690.64炭疽云纹880.660.61轮斑炭疽540.580.52灰枯茶饼430.610.55健康叶健康叶2010.910.93白星看这张表就知道下一步该干什么炭疽和茶饼的 recall 最低优先补这两类的样本轮斑和云纹互相混淆考虑加一个专门区分它们的二分类头或者补边界样本。这种按类拆解的习惯比盯着一个总 mAP 调参有用得多。还有一个容易被忽略的点推理速度。883 张训出来的模型最终是要上边缘设备或者服务端的导出 ONNX 或 TensorRT 后测一下实际帧率。茶叶病害检测如果是无人机巡检场景帧率要求不高但如果是手持设备实时检测就得在模型大小和精度之间做取舍。我一般会同时训 nano 和 small 两个版本nano 上设备、small 做服务端用同一份数据同一套增强对比着看哪个更划算。最后说个我自己的习惯每次训完模型把results.csv、confusion_matrix.png和那张按类拆解的表一起归档文件名带上日期和关键参数。883 张数据集的实验迭代很快今天调的参数下周就忘了没有归档等于白跑。这个习惯帮我省过很多次重复劳动也希望帮到你。本文还有配套的精品资源点击获取
返回列表