甘蔗病害目标检测数据与YOLO标注格式全解析:从3300张图到模型落地
简介面向甘蔗病害识别场景的 YOLO 格式目标检测数据集覆盖健康、黄叶病、锈病等共 4 个类别已划分训练集、验证集与测试集可直接用于 YOLOv5/YOLOv8 等模型的训练与验证。整个资源包共包含 2000 个文件以 1679 个 txt 标注文件与 320 张 jpg 图像为主体另附 1 个数据可视化 Python 脚本压缩后大小约 68.58MB。txt 标注文件负责保存类别编号与边界框坐标jpg 图像为原始叶片样本配合 show 脚本可快速查看检测标签与图像的匹配情况便于评估标注质量并调整训练策略。目前该数据集已有 148 人浏览学习适合计算机视觉初学者、农业信息化研究人员以及关注目标检测模型改进的开发者参考使用。通过它可完成从数据读取、类别映射、模型训练到结果评估的完整流程为甘蔗病害智能化监测提供基础数据支撑。1. 甘蔗病害目标检测数据3,300 张标注图到底能帮你解决什么问题做农业视觉落地的同行应该都有体会甘蔗病害识别难的不是模型是数据。病斑形状不规则早期和后期外观差异大叶鞘、枯叶、阴影又特别容易干扰判断。这份甘蔗病害图像目标检测数据约 3,300 张每张图配 YOLO 标注格式的标签文件直接把从零拍照 手动标注最耗时的环节砍掉了。拿到手你要做的事很清楚检查标注质量、按 YOLO 目录规范整理、训练一个能区分病害种类的目标检测模型。适合想快速验证甘蔗植保检测方案的团队也适合拿来做目标检测项目练手的个人学习者。数据是好数据但能不能训练出能落地的模型取决于你怎么对待它下面从标注格式开始拆。2. 读懂 YOLO 标注格式txt 标签里那五列数字的来龙去脉拿到一份标注数据第一步不是开训练而是确认手里的标签到底长什么样。YOLO 标注格式这几年已经成了目标检测领域的事实标准YOLOv5、YOLOv8、YOLOv11 以及大量端侧推理框架都直接读这套格式。它的形式极简每张图片对应一个同名 .txt 文件文件里每一行代表一个标注框。搞清楚这五列数字的含义和边界后面所有操作才不会翻车。2.1 为什么用归一化坐标YOLO 格式的数学逻辑YOLO 的 txt 每一行固定五列顺序是类别 id、中心点 x、中心点 y、框宽 w、框高 h。这里有两个反直觉的点。第一类别 id 是整数从 0 开始计数和病害名称没有直接关系映射关系完全由训练配置里的 names 列表决定这份数据里具体标的是哪几类、每类叫什么要以你手上的类别说明为准。第二四个坐标值全部是归一化之后的相对值正常范围在 0 到 1 之间。所谓归一化就是把像素坐标除以图片宽高cx x_pixel / img_widthbw box_width / img_widthy 方向同理。这套设计的核心动机是尺度无关性。训练时输入图片会被统一缩放比如原图 3000×2000 压到 640×640 输入如果用像素绝对值标注缩放后框的位置和大小全变了用归一化值缩放只是等比例变换模型读到的始终是相对位置。这也是 YOLO 系列多年保持这套格式的原因。另外目标检测模型里中心点回归和宽高回归是分开算损失的归一化之后两个方向的误差量纲一致训练过程更平稳。理解了这个逻辑你就明白为什么坐标越界是 YOLO 标注里最致命的问题之一。任何一列大于 1 或小于 0都说明标注或转换过程出了问题这种脏数据混进训练集模型会学到根本不存在的框。后面避坑章节会给具体的排查方法。2.2 把标签读回来画框一个检查标注质量的脚本拿到数据的第一时间先用脚本把标注框画回原图上肉眼抽查一批。这是验证标签真实性的最直接方式比任何统计数据都可靠。import os import cv2 IMG_DIR images # 存放图片的目录 LABEL_DIR labels # 存放同名的 txt 标签目录 def draw_boxes(img_path, txt_path): img cv2.imread(img_path) if img is None: print(f图片读取失败: {img_path}) return h, w img.shape[:2] with open(txt_path, r, encodingutf-8) as f: lines f.read().strip().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(f格式异常 {txt_path}: {line}) continue cls_id, cx, cy, bw, bh parts cls_id int(cls_id) cx, cy, bw, bh map(float, (cx, cy, bw, bh)) # 归一化坐标必须在 [0,1]越界说明标注或转换有问题 if not (0 cx 1 and 0 cy 1 and 0 bw 1 and 0 bh 1): print(f坐标越界 {txt_path}: {line}) # 归一化坐标乘回图片宽高得到像素级框 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(check os.path.basename(img_path), img) cv2.waitKey(0) cv2.destroyAllWindows() # 抽样前 30 张跑完再换一批 for name in os.listdir(IMG_DIR)[:30]: if not name.lower().endswith((.jpg, .jpeg, .png)): continue txt_path os.path.join(LABEL_DIR, name.rsplit(., 1)[0] .txt) if os.path.exists(txt_path): draw_boxes(os.path.join(IMG_DIR, name), txt_path) else: print(f缺少标签: {name})脚本逻辑不复杂读图、读同名 txt、把归一化坐标乘回宽高、画框、顺手检查越界。实际使用时我会把 waitKey 逻辑改掉改成保存标记图到临时目录批量处理完再统一翻看不用一张张按键盘。关键是你得真看不是跑完脚本就算检查过了。抽样技巧是分三批前 20 张、中间 20 张、最后 20 张。很多数据集按拍摄批次排序分布往往不均匀只看开头会漏掉后面的脏数据。对甘蔗这种叶片密集的场景重点看叶鞘附近和叶片相互遮挡的区域那里最容易出现框不准、框住背景的情况。顺手还可以统计一下各类别的框数量分布如果某类只有几十个框说明类别严重不平衡后面训练时要单独处理。2.3 从其他格式转 YOLO 的四个转换要点如果你的这份数据将来要和其他来源合并或者手上还有一批 VOC、COCO 格式的历史数据转换时注意四个高频坑。第一类别顺序必须统一VOC 的 classes.txt 或 COCO 的 categories 顺序决定了转换后的 id两批数据合并时同一种病害在两个源里 id 不同是灾难。第二归一化的分母必须是每张图自己的宽高用整个数据集平均宽高来归一化等于给所有框加了一个随机扰动。第三坐标系不要搞混。COCO 的 bbox 是左上角坐标加宽高转成 center 格式要算 (x w/2) / img_wOpenCV 读图出来的 shape 顺序是 (h, w)PIL 是 (w, h)这两套混用会让框整体偏移。第四转完必须做一次回画检查和 2.2 的脚本一样别只对比数字。如果你手上的标注是 LabelImg、LabelStudio 这类常见标注工具导出的它们默认输出 VOC 格式居多转过来之后同样要走一遍上面的检查流程。3. 训练前先调整目录把 3,300 张图组织成 YOLOv8 的标准结构标注格式读懂了下一步是把数据组织成框架能直接吃的结构。这一步最枯燥但省掉的返工时间最多。YOLO 系框架对目录结构要求很严格一旦路径或文件名对不上训练时报的错五花八门新手最容易在这里卡住。3.1 标准目录结构images 与 labels 必须一一对应YOLOv8 的做法是images 目录下放图片labels 目录下放同名 txt训练时框架只扫描 images再按同名规则去 labels 里找标签。目录结构是固定套路dataset/ ├── images/ │ ├── train/ │ │ ├── canefield_001.jpg │ │ ├── canefield_002.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── canefield_001.txt │ │ ├── canefield_002.txt │ │ └── ... │ ├── val/ │ └── test/ └── data.yaml这套规范里最容易被忽略的两条。其一images 和 labels 下同一分区的文件名必须完全一致只能扩展名不同框架不会帮你做模糊匹配。其二train 和 val 两个分区里图片和标签必须一起划分不能图片分了、标签没分。还有纯背景图没有目标的图片建议单独放一个目录训练时要么排除要么删掉。背景图混进 train 会让模型学到错误的无目标惩罚混进 val 又会影响 mAP 统计口径后面避坑章节再展开。3.2 数据划分按拍摄批次拆别只靠随机 shuffle3,300 张图怎么切成 train 和 val最常见的做法是随机划分80% 训练、20% 验证。但甘蔗田间数据有个典型问题同一块地的照片角度、光线、品种高度相似如果同一拍摄批次的照片同时进了 train 和 val验证集就等于开卷考试mAP 虚高。实战中我会在随机基础上多做一件事按文件名前缀或拍摄批次去重后再切。import os import random import shutil random.seed(2024) # 固定种子保证结果可复现 IMG_SRC images_all # 原始图片目录 LBL_SRC labels_all # 原始标签目录 # 收集图片和标签成对的数据 pairs [] for name in os.listdir(IMG_SRC): if not name.lower().endswith((.jpg, .jpeg, .png)): continue txt_name name.rsplit(., 1)[0] .txt if os.path.exists(os.path.join(LBL_SRC, txt_name)): pairs.append((name, txt_name)) else: print(f缺少标签: {name}) random.shuffle(pairs) val_ratio 0.2 n_val int(len(pairs) * val_ratio) n_train len(pairs) - n_val for split_name, files in [(train, pairs[:n_train]), (val, pairs[n_train:])]: os.makedirs(fimages/{split_name}, exist_okTrue) os.makedirs(flabels/{split_name}, exist_okTrue) for img_name, txt_name in files: shutil.copy(os.path.join(IMG_SRC, img_name), fimages/{split_name}/{img_name}) shutil.copy(os.path.join(LBL_SRC, txt_name), flabels/{split_name}/{txt_name}) print(ftrain: {n_train}, val: {n_val})这里用 copy 而不是 move是为了保住原始数据这份后悔药。训练集和验证集的划分方式直接决定后面所有评估指标的可信度。按批次切的具体做法是先看文件名里有没有日期或地块编号这类批次信息再按这个维度分组把整组划入 train 或 val。随机切只能作为没有批次信息时的兜底方案切完务必保留种子文件记录划分逻辑方便别人复现你的结果。3.3 data.yaml 怎么写类别顺序错了全线白练data.yaml 是训练时的数据描述文件names 的顺序定义必须和标签里的 id 对齐。假设这份数据标了 3 类病害示例配置如下# data.yaml path: ../dataset # 数据集根目录 train: images/train # 相对 path 的训练图片目录 val: images/val # 相对 path 的验证图片目录 nc: 3 # 类别数 names: # id 从 0 开始 0: cane_red_rot # 具体类别名以你手里的类别定义为准 1: cane_smut 2: cane_leaf_spot写这个文件最容易踩的坑是把 names 顺序写错。如果标签里 id 0 是某个病害配置里 id 0 写成了别的训练不会报错但模型学到的语义全错位了。每次写完 data.yaml我都会先确认一遍标签里的 id 分布再让框架打印类别统计。YOLOv8 里最简单的办法是直接启动一次训练看第一屏的类别统计不放心的话可以先用一小段代码统计 labels 目录里所有 txt 的类别 id 出现次数。另外注意 nc 的值。它必须等于标签里最大类别 id 加 1。比如标签只出现 0、1、2nc 就写 3写 4 也能跑但多出一个空类会影响类别损失的取值没必要。标签里如果出现 id 5 而 nc 只写了 3训练会直接报错这也是排查方向之一。4. 用 3,300 张数据跑通 YOLOv8 训练模型选型、超参数与命令数据整理完进入大多数人最关心的环节训练。这里给出一套针对 3,300 张中小规模数据集的保守方案先跑通再优化。4.1 选 YOLOv8s 还是 YOLOv8n三秒定方案模型选型的逻辑很简单先小后大。第一次训练的目的是验证数据和标签没问题不是刷指标。YOLOv8n 是最轻量的模型训练快但甘蔗病斑这种小目标场景下检出率偏低YOLOv8s 是平衡点精度比 n 高一截速度慢不了太多YOLOv8m 及以上适合数据量大、且已经把小模型调明白之后再上。3,300 张图跑 YOLOv8s 是稳妥的起点。对比一下实际影响在单张 1080 级别显卡上YOLOv8s 配 640 分辨率、16 batch一个 epoch 大约一两分钟150 个 epoch 三小时上下同配置下 YOLOv8m 时间接近翻倍。如果你的最终场景是无人机巡田模型要部署到边缘设备YOLOv8n 经过量化裁剪也许更合适但那是后话先拿 s 把指标跑出来再说。YOLO 系列不同版本的差异主要在训练策略和模块结构上但数据格式、训练命令大同小异按你熟悉的版本来即可。4.2 训练命令与三个必调参数训练前先装框架pip install ultralytics然后启动训练以数据配置在 sugarcane.yaml 为例yolo detect train \ datasugarcane.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ projectruns/sugarcane \ nameexp1这条命令里最有调整价值的三个参数是 imgsz、batch、patience。imgsz 是输入尺寸甘蔗病斑很多是小目标imgsz 设 640 是底线病斑特别小或者原图分辨率很高的话可以试 960但显存占用和训练时间明显上升。batch 受显卡显存限制16 在多数 8GB 显存的卡上能跑 640 分辨率显存不足就降到 8 或 4注意也别太低否则 BN 层统计不稳定。patience 是早停轮数连续 30 个 epoch 验证指标没提升就自动停既省电也避免过拟合。两个容易忽略的细节。其一modelyolov8s.pt 这个写法会从官方源下载 COCO 预训练权重数据量小的时候迁移学习收益很大模型已经会提取通用特征你相当于在它基础上做领域适配。离线环境就先把权重文件下好放到同目录。其二load 参数不要乱给如果你手里有两个训练好的权重想融合或续跑用 modelbest.pt 再加上 resumeTrue 是更稳的做法而不是把模型文件当预训练权重硬接。第一次训练保持默认优化器SGD和学习率即可跑通后再调。4.3 训练过程中看什么loss 不降比 mAP 低更值得警惕训练启动后终端会实时打印 box_loss、cls_loss、dfl_loss 和验证集 mAP。很多新手只盯着 mAP 看实际上 loss 曲线更能反映问题。前 20 个 epoch三个 loss 应该稳步下降如果 loss 一直横盘不动先怀疑学习率或数据格式而不是继续加 epoch。cls_loss 不降说明类别区分有问题大概率是类别不平衡或标注噪声大box_loss 不降说明框不收敛优先检查标注框质量。训练结束后runs/sugarcane/exp1/ 目录下会生成 weights/best.pt 和 weights/last.pt以及一组曲线图。best.pt 是按验证集指标选出的最优权重后面部署用它last.pt 是最后一个 epoch 的权重一般不用。results.png 里能看到 loss 和 mAP 两条曲线配合着看能判断模型是健康收敛还是过拟合——训练 loss 持续下降但验证 loss 掉头上升就是过拟合信号。我见过太多人训练完只拷贝一个 best.pt 就走完全没看曲线浪费了大量可用的排查信息。5. 标注数据避坑清单五个最容易让模型翻车的细节无论数据是买来的、下载的还是自己标的标注环节的脏数据问题都会在训练后集中爆发。这一章把高频问题按现象、原因、解决的套路写成清单遇到类似情况可以对着排查。5.1 标签和图片数量对不上训练时莫名报找不到标签现象训练刚开始就出现大量 No labels found in ... 提示或者验证时发现参与计算的图片数量只有预期的一半。原因数据在拷贝和解压过程中丢了部分 txt 文件或者原始标注里部分图片没有目标空的 txt 没有被生成导致图片和标签不完全一一对应。解决写一个文件对比脚本遍历 images 目录下每张图检查同名 txt 是否存在把缺的列出来。确定是空标签的还是丢文件的空标签的图片要么补一个空 txt要么直接从数据里移走。我的习惯是宁可图片数少一点也不要让数据管道在训练中途出问题。import os img_dir images/train label_dir labels/train missing [] for name in sorted(os.listdir(img_dir)): if not name.lower().endswith((.jpg, .jpeg, .png)): continue txt os.path.join(label_dir, name.rsplit(., 1)[0] .txt) if not os.path.exists(txt): missing.append(name) print(f共 {len(missing)} 张图缺标签) for name in missing[:50]: print(缺失:, name)5.2 归一化坐标越界框画到了图片外面现象用检查脚本跑数据时发现部分 txt 里的坐标值大于 1 或小于 0训练出的模型经常预测出跑到图像边缘的框。原因坐标转换时没有除以当前图片的宽高或者把左上角坐标当成了中心点坐标或者原图经过裁剪缩放后标签没有同步更新。解决先全局扫一遍越界文件确认范围。比例很低就直接丢弃这些框比例高就要修根因——如果转换脚本写错了改正后重新转换整批数据别在脏数据上手工改。越界框里有一种情况值得单独看center 在图像里但框宽高超出图像范围这种一般是目标在图像边缘被截断标注员把可见部分框进来了模型也能学但验证时边界框质量会偏低建议把这些框按图像边界裁剪。5.3 小病斑漏标mAP 不低田间就是漏检现象验证集 mAP 看着有 0.8 上下但拿田间真实照片测试早期病斑几乎全漏。原因早期病斑在图上只有十几个到几十个像素标注时容易漏而且 640 分辨率输入下小目标经过多次下采样特征几乎丢失。3,300 张里如果小目标占比不高模型根本没有足够样本去学。解决先统计验证集里目标框的尺寸分布看看宽高小于 32 像素的目标占比。占比高就考虑 imgsz960 或 1280 训练或者对图像做切片处理后再训练。另一个容易忽略的问题是标注时把一个包含多个小病斑的区域直接框成一个大框模型学到的就是一个有四个病斑的大框这在检测任务里是典型的漏标。正确做法是把每个病斑单独框出来哪怕框只有 8×8 像素。5.4 类别边界模糊相似病害互相误检现象混淆矩阵里两个类别的互相误检率明显偏高这两个类的单类 AP 都低。原因标注标准不统一。甘蔗不同病害在早期外观高度相似不同标注员对什么程度算这个病、什么程度算那个病的理解不一致同类样本被分到两个类里模型被迫在模糊边界上反复横跳。解决回到标注层面统一标准。写一份一页纸的标注规范用典型图把每个类别的判定特征写清楚比如病斑颜色、边界形状、有没有发黑坏死点。如果误检集中在某两个类单独抽这两类的验证集样本逐张看你会发现不少标签本身就标错了。这类问题调模型参数解决不了只能修数据。尤其中间类和极端类要分开早期轻微症状单独标注不要和典型症状混在一个类里。5.5 把背景当目标阴影、枯叶、土块都被框了现象模型的检出框大量落在叶鞘边缘、阴影交界、枯黄叶片上检出的病害在视觉上根本看不出病灶。原因采集环境复杂标注员在光线不好或图片模糊时把反光、阴影、机械损伤当成病斑框了。这类噪声在训练集里占比不高但会让模型学到错误的纹理特征。解决宁可没有框也不要错框。用检查脚本逐张过可疑样本把明显不是病灶的标签删掉。如果这类噪声集中在某几批图里直接检查是不是拍摄设备或时间导致的系统性误差。实际处理时我会把删除的标签单独备份后面发现删错了还有后悔药。这句话也适用于整个数据清洗流程所有修改都留备份别在原目录上直接覆盖。6. 验证与落地用混淆矩阵和置信度阈值把 mAP 变成可用指标6.1 看混淆矩阵别只看 mAP训练完的 runs/sugarcane/exp1/ 目录里confusion_matrix.png 比 mAP 数字更有价值。它展示每个真实类别被预测成什么对角线越亮越健康。如果某两类长期互串问题几乎出在标注语义上回到 5.4 的排查流程。另一个必看的是每个类别的单类 APYOLOv8 的结果里有 per-class PR 曲线单类 AP 明显偏低的类别就是下一步要重点补数据的类别。农业病害检测里最难提的就是小目标和相似类这类问题靠调阈值只是缓解根源还是在数据。6.2 置信度阈值跟着 PR 曲线定不拍脑袋部署阶段的 conf 参数默认 0.25但实际落地时这个值要按 PR 曲线的拐点定。做法是拿一批没参与训练的田间照片跑一遍把 conf 从 0.5 往下调观察漏检和误检的平衡点在哪from ultralytics import YOLO model YOLO(runs/sugarcane/exp1/weights/best.pt) results model(field_photo.jpg, conf0.35, iou0.5) for r in results: print(r.boxes.cls, r.boxes.conf, r.boxes.xyxy)我现在的习惯是模型训练完先看混淆矩阵和 per-class PR 曲线再拿 100 张未参与训练的田拍照片做一次批次推理把三个数打出来对比漏检率、误检率、单张推理耗时。三个数都满足项目要求之后再决定阈值和部署方案。这套流程走完才算真正把这份数据用成了能交付的检测方案而不是停在训练出一个模型这一步。希望帮到你。本文还有配套的精品资源点击获取