首页 / 资讯中心 / 文章详情

甘蔗叶片病害检测数据集实战:从YOLO标注到切片推理

甘蔗叶片病害检测数据集实战:从YOLO标注到切片推理 ★ FEATURED ARTICLE
简介这份资源面向从事农业图像识别与目标检测的开发者、学生及科研人员提供甘蔗叶片病害检测的YOLO格式数据集可用于训练和评估病害识别模型解决农业场景下病害样本获取与标注困难的问题。压缩包共约2000个文件以1586个txt标注文件、413张jpg图像和1个py脚本为主整体约64.06MB标注文件与图像一一对应脚本可用于数据可视化查看。数据集包含健康、黄叶病、锈病等4个类别已完成训练集、验证集与测试集划分方便直接投入YOLO系列模型训练。目前已有77人学习关注适合希望快速上手目标检测实战、验证改进网络效果的读者参考使用。1. 甘蔗叶片病害检测数据集从田间到 YOLO 的第一公里甘蔗叶片病害检测这件事真正卡住大多数人的从来不是模型结构而是数据。你拿到一份标注好的甘蔗叶片病害数据集第一反应往往是「直接丢进 YOLOv8 开训」结果 mAP 卡在 0.4 上不去回头一看标注框全是整片叶子病害区域根本没框住。这份资源解决的就是这个第一公里问题它提供的是面向目标检测任务的甘蔗叶片病害图像与标注覆盖常见叶部病斑类别适合做农业视觉检测的从业者、做课程设计的学生以及想把 YOLO 落到真实作物场景的工程师。它不能让你一步做出产品级系统但能让你把「数据加载 → 训练 → 推理 → 可视化」这条链路完整跑通并且看清农业数据集和 COCO、VOC 那类通用数据集在标注逻辑上的根本差异。2. 拆开数据集目录结构、标注格式与类别分布2.1 先看清目录长什么样农业类检测数据集常见的组织方式有两种一种是按images/和labels/平行存放另一种是按train/val/test分好再各自带图和标注。拿到手第一件事不是写训练脚本而是把目录树打出来确认图像和标注是否一一对应。# 打印两层目录结构先看整体组织 find ./sugarcane_leaf -maxdepth 2 -type d | sort # 统计图像数量确认 train/val 划分是否合理 find ./sugarcane_leaf -name *.jpg -o -name *.png | wc -l # 检查是否存在没有对应标注的孤儿图像 for img in $(find ./sugarcane_leaf/images -name *.jpg); do base$(basename $img .jpg) label$(find ./sugarcane_leaf/labels -name $base.txt) [ -z $label ] echo 缺标注: $img done第一段find用来确认目录层级避免后面写路径时猜。第二段统计图像总数如果 train 和 val 比例严重失衡比如 95:5说明这份数据集的划分需要你自己重做。第三段是血泪经验农业数据集经常出现图像有、标注丢的情况训练时这些图会被当成纯背景直接拉低召回。跑完这三条你对数据集的健康状况就有底了。2.2 YOLO 标注格式与甘蔗病害的适配问题YOLO 系列的标注是每张图一个.txt每行class_id x_center y_center width height全部归一化到 0~1。甘蔗叶片病害的难点在于病斑往往很小、边界模糊标注时容易出现两类偏差框太大把健康组织也包进去或者框太小只框了病斑核心。这两种都会让模型学到一个模糊的边界。import os import glob # 统计每个类别的实例数和框面积分布判断标注质量 label_dir ./sugarcane_leaf/labels/train class_count {} area_bins {tiny(0.01): 0, small(0.01-0.05): 0, medium(0.05-0.2): 0, large(0.2): 0} for txt in glob.glob(os.path.join(label_dir, *.txt)): with open(txt) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cid, x, y, w, h int(parts[0]), *map(float, parts[1:]) class_count[cid] class_count.get(cid, 0) 1 area w * h if area 0.01: area_bins[tiny(0.01)] 1 elif area 0.05: area_bins[small(0.01-0.05)] 1 elif area 0.2: area_bins[medium(0.05-0.2)] 1 else: area_bins[large(0.2)] 1 print(类别实例数:, class_count) print(框面积分布:, area_bins)这段脚本做两件事统计类别实例数看是否存在长尾类别统计归一化框面积分布判断小目标占比。如果tiny和small加起来超过 60%那默认的imgsz640很可能不够需要上到 1024 甚至切片推理。class_id从 0 开始连续编号如果打印出来发现跳号比如只有 0、2、5说明类别映射表有问题得回去核对data.yaml。2.3 类别不平衡与划分策略甘蔗叶部病害通常有几类高发、几类罕见直接随机划分会导致 val 集里某些类别一个样本都没有。常见做法是按类别分层抽样保证每个类别在 train 和 val 里都有出现。import random from collections import defaultdict import shutil # 按类别分层划分保证 val 集覆盖所有类别 random.seed(42) img_dir ./sugarcane_leaf/images/all lbl_dir ./sugarcane_leaf/labels/all val_ratio 0.2 # 建立 类别 - 图像列表 的映射 cls_to_imgs defaultdict(list) for txt in glob.glob(os.path.join(lbl_dir, *.txt)): base os.path.splitext(os.path.basename(txt))[0] with open(txt) as f: classes {line.split()[0] for line in f if line.strip()} for c in classes: cls_to_imgs[c].append(base) val_set set() for c, imgs in cls_to_imgs.items(): random.shuffle(imgs) n_val max(1, int(len(imgs) * val_ratio)) val_set.update(imgs[:n_val]) # 按划分结果拷贝文件 for split, names in [(val, val_set), (train, set(os.path.splitext(os.path.basename(p))[0] for p in glob.glob(os.path.join(lbl_dir, *.txt))) - val_set)]: for n in names: for ext in [.jpg, .png]: src os.path.join(img_dir, n ext) if os.path.exists(src): shutil.copy(src, f./sugarcane_leaf/images/{split}/{n}{ext}) shutil.copy(os.path.join(lbl_dir, n .txt), f./sugarcane_leaf/labels/{split}/{n}.txt)random.seed(42)保证划分可复现这点在做对比实验时很关键。max(1, ...)是为了防止某个类别样本太少导致 val 里一个都没有。分层之后val 集的 mAP 才有参考意义否则你看到的指标波动可能只是划分随机性造成的玄学。3. 用 YOLOv8 跑通训练配置、命令与参数怎么改3.1 环境与 data.yaml 的最小配置训练前先把data.yaml写对这是最常见的翻车点。路径写错、类别数对不上YOLO 会在启动时报错或者静默用错类别。# sugarcane.yaml path: ./sugarcane_leaf # 数据集根目录 train: images/train # 相对 path 的训练图目录 val: images/val nc: 4 # 类别数必须和标注里的最大 class_id1 一致 names: # 类别名顺序必须和 class_id 对应 0: 健康 1: 褐斑病 2: 锈病 3: 枯萎病nc和names的对应关系是硬约束如果标注里class_id最大是 3那nc必须是 4。path用相对路径时YOLO 会相对于运行目录解析建议直接用绝对路径避免歧义。类别名用中文没问题但导出 ONNX 或部署到某些推理框架时可能遇到编码问题工程上更稳的做法是用英文名加一份映射表。3.2 训练命令与关键参数# 从预训练权重开始训练农业数据集样本量通常不大迁移学习是默认选择 yolo detect train \ datasugarcane.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ mosaic1.0 \ mixup0.1 \ degrees15 \ translate0.1 \ scale0.5 \ fliplr0.5 \ projectruns/sugarcane \ nameexp1modelyolov8s.pt是精度和速度的折中如果显存吃紧可以换yolov8n.pt但小模型对小病斑的召回会明显下降。imgsz640是默认值前面统计如果发现小目标多这里改 1024同时batch要相应降到 8 或 4。patience30表示 30 轮没有提升就早停农业数据集容易过拟合早停能省不少时间。mosaic1.0和mixup0.1是数据增强degrees15给旋转增强因为叶片在田间拍摄角度本来就多变。fliplr0.5水平翻转对叶片病害是安全的但垂直翻转要谨慎可能造出不存在的病斑分布。3.3 训练过程看什么指标启动后重点盯三个数box_loss、cls_loss和mAP50。box_loss下降慢说明框回归有问题常见原因是标注框质量差cls_loss震荡大说明类别不平衡或者学习率偏高mAP50在 50 轮后还不涨先别急着调模型回去看 val 集的标注有没有漏标。农业数据集里「健康叶片被误标成病叶」这种噪声很常见模型会学到一个矛盾的决策边界。# 训练结束后用验证集跑一次评估拿到分类别指标 yolo detect val \ modelruns/sugarcane/exp1/weights/best.pt \ datasugarcane.yaml \ imgsz640 \ conf0.25 \ iou0.5conf0.25是推理置信度阈值评估时用默认值即可实际部署时再根据漏检和误检的取舍调整。iou0.5是 NMS 的 IoU 阈值病斑密集时这个值可以降到 0.4 减少重叠框。4. 推理、可视化与踩坑排查4.1 单图与批量推理from ultralytics import YOLO model YOLO(runs/sugarcane/exp1/weights/best.pt) # 单图推理保存带框结果 results model.predict( source./test_leaf.jpg, conf0.3, iou0.45, imgsz640, saveTrue, projectruns/predict, namesingle ) # 批量推理整个目录 results model.predict( source./test_images/, conf0.3, saveTrue, save_txtTrue, # 同时导出 YOLO 格式的预测结果 projectruns/predict, namebatch )conf0.3比评估时的 0.25 略高是为了减少可视化时的误检干扰。save_txtTrue导出的预测结果可以拿去做后续统计比如计算病斑面积占比。批量推理时注意显存目录里图片尺寸差异大时YOLO 会按最长边缩放极端长宽比可能影响小目标检测。4.2 常见问题排查现象训练 loss 正常下降但 mAP 一直是 0。原因data.yaml里的val路径指向了空目录或者 val 图像的标注文件缺失。YOLO 不会报错只是评估时没有正样本。 解决跑一遍 2.1 里的孤儿图像检查脚本确认 val 目录下每张图都有对应.txt。现象推理时框大量重叠同一个病斑出好几个框。原因NMS 的iou阈值偏高或者模型对病斑边界学得不够紧。 解决把推理iou从 0.5 降到 0.4同时检查训练时box_loss是否收敛到位。现象模型在训练集上表现很好val 集一塌糊涂。原因农业数据集样本量小过拟合。也可能是 train 和 val 的拍摄条件差异大光照、背景。 解决加大mosaic、mixup增强加weight_decay或者用更强的预训练权重。如果条件允许把 val 集换成和 train 同分布的图像。现象某些类别 mAP 特别低其他类别正常。原因类别实例数太少或者该类别的标注一致性差。 解决先看 2.2 的类别统计对长尾类别做过采样或者用 copy-paste 增强合成更多样本。现象训练到一半显存爆了。原因imgsz调大后没有同步降batch或者mosaic增强在后期产生了超大图。 解决imgsz1024时batch降到 4~8或者开启ampTrue混合精度。5. 从能跑到能用切片推理与类别映射的工程习惯数据集跑通只是起点真正落地时你会遇到两个绕不开的问题小病斑在 640 分辨率下丢细节以及类别编号和业务系统对不上。先说切片推理这是农业和遥感检测里很实用的技巧。把原图切成有重叠的小块分别推理再合并结果能显著提升小目标召回。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/sugarcane/exp1/weights/best.pt) def sliced_predict(img_path, slice_size640, overlap128, conf0.3): img cv2.imread(img_path) h, w img.shape[:2] step slice_size - overlap all_boxes [] for y in range(0, h, step): for x in range(0, w, step): patch img[y:yslice_size, x:xslice_size] if patch.shape[0] 32 or patch.shape[1] 32: continue res model.predict(patch, confconf, verboseFalse)[0] for box in res.boxes: xyxy box.xyxy[0].cpu().numpy() xyxy[0] x; xyxy[1] y; xyxy[2] x; xyxy[3] y all_boxes.append((*xyxy, float(box.conf[0]), int(box.cls[0]))) return all_boxes boxes sliced_predict(./test_leaf.jpg) print(f切片推理共得到 {len(boxes)} 个框)slice_size640和overlap128是经验值重叠区域保证跨切片的病斑不会被切断。合并后还需要再做一次全局 NMS否则重叠区会出现重复框。这套逻辑在监控视频拉流、无人机航拍场景里同样适用只是切片策略要按目标尺度调整。再说类别映射。训练时的class_id是数据集内部的编号业务系统里可能是另一套编码。我一般会单独维护一个class_map.json推理后统一转换而不是在训练配置里改来改去。import json # 数据集类别 - 业务类别 的映射表 class_map { 0: {name: 健康, code: HEALTHY}, 1: {name: 褐斑病, code: BROWN_SPOT}, 2: {name: 锈病, code: RUST}, 3: {name: 枯萎病, code: BLIGHT} } with open(class_map.json, w, encodingutf-8) as f: json.dump(class_map, f, ensure_asciiFalse, indent2) # 推理结果转换 def to_business_format(boxes): out [] for x1, y1, x2, y2, conf, cid in boxes: info class_map[str(cid)] out.append({ code: info[code], name: info[name], bbox: [round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)], score: round(conf, 3) }) return out这样做的好处是数据集换版本、类别增删时只改映射表推理和下游代码不动。验证方法也简单拿一张已知病害的图跑完推理后人工核对code和实际病斑是否一致再抽查 20 张覆盖所有类别的图确认没有系统性错位。从那以后我每次拿到新的农业检测数据集都强制先跑一遍目录检查、类别统计和分层划分这三步再动训练脚本。这套习惯帮我省下了大量「训完才发现数据有问题」的后悔药。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站