简介:棉花植物病害图像目标检测标注数据,面向深度学习目标检测入门与进阶开发者,可用于YOLO系列模型训练、调参及改进实践。数据覆盖枯萎病、卷曲、灰霉、叶斑病及健康叶片等六类常见状态,并已划分训练集、验证集与测试集,类别映射参考classes文件,可直接用于模型训练与指标评估。资源包共两千个文件,以一千九百九十九个YOLO格式文本标注文件为主,另含一个Python可视化脚本,压缩包整体约一百五十七兆字节。目前已有四十七人学习,数据规模对应约四千六百张棉花病害图像的标注信息,配合可视化脚本可快速审查标签质量,降低人工校验成本。对正在实践YOLOv5或目标检测改进方案的学习者而言,这套标注数据能节省大量数据准备时间,便于将精力集中在模型结构设计与参数优化上。
1. 棉花植物病害图像目标检测数据:4,600张标注图能做什么
做棉花巡检项目的人最容易卡在第一步:图拍回来了,病害区域却没人标,YOLO训练脚本写得再顺也无处发力。棉花植物病害图像目标检测数据这类已标注数据集,价值就是把最费人力的图片采集、病斑框标注和格式整理一次做完,约4,600张图和配套标签,按YOLO标注格式的txt文件放好,解压后可以直接进入目标检测训练流程。它适合两类人:一是刚接触YOLO的工程师,想在一个干净数据集上跑通“数据读取→训练→评估→部署”全链路;二是做农业病害识别落地的团队,先用它验证方案可行性,再补自己的田间数据。它能解决冷启动问题,但替代不了现场采样,后续效果取决于标注质量、病害类别分布和你对数据的分割方式。
2. 读懂YOLO标注格式:标签结构、可视化验证与格式转换
2.1 YOLO标签一行五个数:类别ID加归一化坐标
所谓YOLO标注格式,并不是图片里直接写了框,而是每张jpg旁边放一个同名的txt文件。打开任意一个txt,里面每一行描述一个病斑或受害区域。比如一行是1 0.512 0.483 0.214 0.168,第一个数字是类别ID,后四个数字依次是框中心点x、中心点y、宽度、高度,全部做了归一化处理。
归一化意味着坐标已除以图片像素尺寸,取值落在0到1之间。不管原始图片是1024×768还是4000×3000,标签都不受分辨率影响。训练时YOLO把自己设置的imgsz重新缩放图片,标签不需要重新计算,这套设计让YOLO格式能跨分辨率通用。
| 字段 | 含义 | 取值范围 |
|---|---|---|
| 第一个数 | 类别ID,从0开始 | 0到nc-1 |
| 第二个数 | 框中心点x坐标,除以图片宽 | 0到1 |
| 第三个数 | 框中心点y坐标,除以图片高 | 0到1 |
| 第四个数 | 框宽度,除以图片宽 | 大于0,通常不超过1 |
| 第五个数 | 框高度,除以图片高 | 大于0,通常不超过1 |
一张图有几个病斑,txt里就写几行。没有病害目标的图不生成txt,或者保留0字节txt。“已标注”在这里的含义是图片和txt一一对应,而不是给你一个看不懂的JSON压缩包。
拿到数据后先别急着训练,打开几个文件确认上面这套结构是成立的。如果发现某行只有四个数,或者类别ID从1开始而不是从0开始,说明这批数据可能经过其他工具导出,必须做一次格式统一。
2.2 把标签画回原图:训练前最便宜的质量检查
我拿到任何检测数据,第一步都是把标签画回原图。标注质量直接影响模型上限,尤其“已标注”这种描述,更要自己验证一遍。框是紧贴病斑边缘,还是包住半张叶子,类别有没有标反,这些靠肉眼扫几十张图就能发现,比训练完再查指标快得多。
import cv2 from pathlib import Path # 示例类别清单,实际以数据集的类别顺序为准 class_names = ["炭疽病", "角斑病", "枯萎病", "棉铃虫为害"] out_dir = Path("preview") out_dir.mkdir(exist_ok=True) for img_path in Path("images").glob("*.jpg"): txt_path = Path("labels") / (img_path.stem + ".txt") if not txt_path.exists(): print(f"[缺标签] {img_path.name}") continue img = cv2.imread(str(img_path)) if img is None: print(f"[坏图] {img_path.name}") continue h, w = img.shape[:2] lines = txt_path.read_text(encoding="utf-8").strip().splitlines() if not lines: print(f"[空标签] {img_path.name}") continue for line in lines: parts = line.strip().split() if len(parts) != 5: print(f"[格式错] {img_path.name}: {line}") continue cls_id, xc, yc, bw, bh = map(float, parts) # 归一化坐标换算回像素坐标 x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, class_names[int(cls_id)], (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imwrite(str(out_dir / img_path.name), img)脚本逻辑很简单:按图片名找到同名txt,逐行解析,把归一化坐标乘回图片宽高,画框和类别名。有个细节容易踩坑,img.shape[:2]拿到的是(高, 宽),不是(宽, 高),新手在这里翻车,框全部画歪。另外glob("*.jpg")只匹配jpg,如果数据里有png,改成glob("*.*")再判断后缀。strip().splitlines()会把空文件变成空列表,顺带查出0字节标签。
参数说明:class_names的顺序必须和data.yaml里的names一一对应,否则框上显示的文字和实际内容对不上。预览图建议随机抽100张以上,不要只看前10张,文件排序时同一批采集图片往往排在一起,看前10张等于只看了一个场景。
2.3 从YOLO转成COCO或VOC:不是必须,但要知道边界
不少人对“标准格式”有执念,拿到YOLO txt还想转成COCO。常见做法是写一个转换函数把归一化框换算回像素坐标。COCO里bbox是像素单位[x_min, y_min, width, height],类别ID在不同框架里有从0开始和从1开始两种约定,需要额外留意。
def yolo_line_to_coco(line, img_w, img_h): cls_id, xc, yc, bw, bh = map(float, line.strip().split()) x_min = (xc - bw / 2) * img_w y_min = (yc - bh / 2) * img_h return int(cls_id), [x_min, y_min, bw * img_w, bh * img_h]这个函数只做了坐标单位换算,并没增加任何新信息。所以什么时候才值得转?你要把数据喂给MMDetection、DETR等框架,或者合作方只认COCO格式。如果就是用Ultralytics的YOLO训练,直接保留txt即可,转换只会多一层出错可能。
从VOC的XML转YOLO时也要小心:XML里是像素单位的xmin, ymin, xmax, ymax,转成中心点坐标时要先除以图片宽高,常见的错误是忘记归一化,直接把像素值写进txt,训练时loss直接乱掉。
3. 用4,600张已标注数据训练棉花病害目标检测模型:目录配置与参数选择
3.1 数据目录与data.yaml:先让YOLO认识这批数据
Ultralytics YOLO的默认约定是图片目录和标签目录分开,train/val各建一套子目录。拿到数据后我一般会先重排成下面这种结构,后续训练和查错都方便。
cotton_disease/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── ... │ └── val/ │ ├── 0101.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── ... │ └── val/ │ ├── 0101.txt │ └── ... └── data.yaml这套排布不是硬性要求,data.yaml里的路径与实际位置对上就行。标签目录不用写进yaml,Ultralytics会自动在images同一级目录下找labels。
path: /home/user/datasets/cotton_disease train: images/train val: images/val nc: 4 names: 0: anthracnose 1: angular_leaf_spot 2: fusarium_wilt 3: bollworm_damagepath写绝对路径最稳,相对路径取决于执行命令时的工作目录,经常有人在这里对不上。nc必须等于类别数量,names顺序要和txt里的类别ID一一对应。验证方法:把上面那个画框脚本里的class_names按这里names的顺序填回去,如果框上方类别名和图内容对不上,说明映射反了。
3.2 第一次训练:最小命令行和必调参数
环境准备就是pip install ultralytics,第一次接触YOLO的零基础读者也可以先跑通再细看参数。第一个训练命令我用得很保守:
yolo detect train \ model=yolov8s.pt \ data=datasets/cotton_disease/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs \ name=cotton_base这条命令做了三件事:加载COCO预训练权重、读取数据集配置、开始训练并把结果写到runs/cotton_base。第一次跑不要急着堆参数,建议先把epochs临时设成1,确认目录和代码无误后再改回去。否则训练到中途才发现data.yaml路径写错,白跑几小时。
参数的选择逻辑要清楚。model=yolov8s.pt里的s是YOLOv8系列的小规格,对农业目标检测是稳妥的起步点。4,600张图量级不算大,用x规格容易过拟合,n规格虽然快但对小病斑的拟合能力弱。换用Ultralytics的v11也是同一套命令,模型文件换成新权重即可。imgsz=640是训练分辨率,棉花病斑往往只占图片很小区域,如果小目标多,优先升到960,代价是训练时间几乎线性变长。batch=16取决于显存,爆显存就降到8或4。batch过小会导致BN统计不稳定,表现为loss震荡明显。device=0指第一块GPU,用CPU训练速度慢得多,不建议。
3.3 训练完看哪些文件:mAP50、mAP50-95和混淆矩阵
训练结束后,runs/cotton_base目录下会生成一串文件。重点只看四个:weights/best.pt、weights/last.pt、results.png和confusion_matrix.png。前两个是权重,后两个是评估依据。
| 指标 | 含义 | 判断经验 |
|---|---|---|
| mAP50 | IoU阈值0.5时的平均精度 | 比较容易到0.8以上,但不足以代表真实可用 |
| mAP50-95 | IoU从0.5到0.95逐档平均 | 更严格,一般比mAP50低10到20个百分点 |
| 精确率P | 预测框里有多少是对的 | 误检多时下降 |
| 召回率R | 真实框有多少被找到 | 漏检多时下降 |
| 混淆矩阵 | 类别间的互相错检 | 棉花叶片相似症状常互相串 |
mAP50-95是把IoU阈值从0.5逐步提到0.95,每档算一次平均精度再取平均,它比mAP50更看重框和真实位置的贴合度。如果mAP50不错但mAP50-95上不去,多半是框的边界不够紧,或者病斑太小带来的定位误差。
训练完后做一次推理验证,也是必须的:
yolo detect predict \ model=runs/cotton_base/weights/best.pt \ source=datasets/cotton_disease/images/val \ imgsz=640 \ save=True生成的预测图会保存在runs/detect下。不要只看数字,把预测图翻出来,尤其看验证集里那些标了框但模型没检出来的图。这比任何指标都直观。
4. 棉花病害数据避坑:训练前后最常遇到的5个问题
4.1 类别ID对不上:训练能跑,但指标莫名其妙低
现象:训练全程不报错,但验证集mAP50一直很低,或者某个类别永远检不出来。把推理图打开,发现框的位置是对的,框上的类别名却是错的。
原因:YOLO标签里cls_id是数字,类别含义完全靠data.yaml的names顺序翻译。如果这批数据是从其他标注工具导出的,类别ID顺序很可能和你写的names不一致。常见情况是原数据集按类别名首字母排序,而你按自己的印象写names,两边错位。
解决:训练前做两件事。第一,用画框脚本把标签和names一起画回原图,抽100张肉眼核对。第二,写几行统计代码确认每个类别ID在train/val里都有样本,ID是0到nc-1连续且没有跳号。如果发现ID错位,训练前一次性批量修正,而不是在训练途中补。
4.2 归一化坐标越界:训练中期loss会变成NaN
现象:模型训练到某几个epoch,loss突然变成nan或inf,之后不再恢复。也有人训练没崩,但推理时预测框超出图片一大截。
原因:标签里出现负数、大于1的坐标,或者width/height被标成0。这多半是标注工具导出时没有对图像边缘的框做裁切,或者手工编辑txt时手误。YOLO对越界框的容错有限,极端值会在损失计算时把梯度冲爆,这是许多人遇到NaN后最先应该查的地方。
解决:训练前跑一遍检查脚本,把越界行找出来。
from pathlib import Path nc = 4 # 按实际类别数修改 def check_labels(label_dir): bad = [] for txt_path in Path(label_dir).glob("*.txt"): for line in txt_path.read_text().strip().splitlines(): parts = line.strip().split() if len(parts) != 5: bad.append((txt_path, "列数错误", line)) continue cls_id, xc, yc, bw, bh = map(float, parts) if not (0 <= cls_id < nc): bad.append((txt_path, "类别ID越界", line)) if not (0 <= xc <= 1 and 0 <= yc <= 1): bad.append((txt_path, "中心点越界", line)) if bw <= 0 or bh <= 0 or bw > 1.5 or bh > 1.5: bad.append((txt_path, "宽高异常", line)) return bad bad_list = check_labels("labels/train") for item in bad_list: print(item)这段脚本按“能否被YOLO安全消费”做底线检查,类别ID、中心点、宽高三个维度都会查。宽高上限我留了1.5的余量,因为手工标注边缘框时偶尔会略微超界。如果这批数据边缘框很多,先用裁剪脚本把框裁到图像边缘再重新归一化,比在训练时硬扛更稳。
4.3 空标签与纯背景图:负样本比例不能失控
现象:训练完成后,某个类别的precision高但recall低,或者在验证集上把纯背景框出一推目标。翻数据一看,不少图没有txt,或者txt是0字节。
原因:采集时把没发病的叶片、土壤背景也一起打包了。YOLO把无标签图片当负样本参与训练,少量负样本能压误检,但比例太高模型就学不出“这里到底该不该框”。
解决:先统计无标签图片数量。如果只有几十张,可以保留在训练集里当负样本,占比控制在10%以内;如果超过20%,优先检查图片采集合围,再决定是否删图。验证集里不要放无标签图,否则每张无目标的验证图都会被强行参与计算,mAP被明显拉低。
4.4 类别不均衡:张数和框数是两个口径
现象:整体mAP50不错,但看混淆矩阵,A类把大量B类样本错检成A,B类召回率只有0.3。训练日志里B类loss也降不下去。
原因:棉花病害天然不均衡。炭疽病病斑小而多,枯萎病可能整株才一两个框。4,600张是“图像张数”,不是“标注框数”,统计时要按框数看。某个类别虽然出现在很多张图里,但每张只有一个框,总框数占比可能只有5%,模型对它的学习严重不足。
解决:先按类别统计训练集的框数分布:
from pathlib import Path from collections import Counter counter = Counter() for txt_path in Path("labels/train").glob("*.txt"): for line in txt_path.read_text().strip().splitlines(): counter[int(line.split()[0])] += 1 print(counter)counter统计的是每个类别ID出现的框数。如果发现某个类的框数不到多数类的十分之一,少样本类优先做图像级增强,比如复制粘贴病斑块、局部放大裁剪,比直接调类别损失权重更稳。把imgsz提高到960也会帮小目标占优。最有效的还是补数据,模型学不到数据里不存在的外观特征。
4.5 train/val划分串图:指标虚高的假象
现象:训练时mAP50到0.9以上,拿到另一块地的照片一测掉到0.5。随手按0.8/0.2随机划分,训练集和验证集里出现了同一株棉花的连续照片。
原因:很多数据集打包时按采集批次排列,随机分割很容易把同一植株、同一病害的不同角度照片分到两边。模型看着在测验证集,实际在考背诵记忆,换一批真正没见过的田间照片就露馅。
解决:划分前先按采样来源分组。常见做法是给图片文件名加地块编号或拍摄日期前缀,然后用GroupShuffleSplit按组划分,保证同一组图片只出现在训练集或验证集里。如果数据本身没给这些信息,至少按文件名前缀聚类后再随机抽样。验证集宁可少,也不要交叉。
5. 评估再进一步:交叉验证、难例收集和部署前的吞吐实测
模型训练完,不要只盯着mAP。先把4,600张数据跑一次5折交叉验证,用每一折mAP的均值和方差评估数据集本身的稳定性。随机划分对4,600张这种量级来说偶然性很大,某一折如果明显掉点,先查那一折的验证集是不是恰好集中了某个稀有类别。这样做能提前发现数据分割问题,而不是等部署后再翻车。
还要主动收集难例。在每折验证集上导出预测图,凡是你自己看着像病斑、模型却漏掉或误检的样本,单独挑出来。枯叶边缘、虫粪、水滴反光、药害斑点,这些都会让模型产生假阳性。把它们收成一个难例文件夹,后期补标,比盲目加数据更有效。我在一个项目里试过只靠难例补标,误检率下降比加普通图快得多。
最后是部署前的实测。把best.pt导出成ONNX或TensorRT engine,在目标设备上用实际工作分辨率做一次连续检测压力测试。不要只看单帧FPS,要看视频流接入后延迟是否稳定、显存占用有没有缓慢上涨。通过导出命令yolo export model=best.pt format=onnx做完转换后,在目标设备上跑一段真实采集数据,观察中间帧的预处理和后处理是否成为瓶颈。多个项目里结论都一样:卡顿往往不在网络推理,而在图像缩放和框后处理队列。
有一回我图省事,跳过画框预览直接训练,结果类别ID错位跑了一周才发现,后来每次拿到新数据都老老实实先做可视化检查。4,600张已标注数据是很好的起点,但从这份数据开始养成验证习惯,比它本身更有价值。希望帮到你。
本文还有配套的精品资源,点击获取