简介:这份玉米生长阶段目标检测数据集面向农业视觉算法开发者与目标检测学习者,用于训练和验证玉米生育期识别模型。数据按幼苗、初期生长、快速生长、成熟前期、成熟期及成熟期不健康状态共6类划分,覆盖从植株矮小到穗部发育、叶片老化的完整生长周期,并包含病害或不健康样本,适合YOLO系列模型训练与农业场景迁移实验。资源包共2000个文件,以1482个txt标注文件、516张jpg图像为主,另含1个yaml数据配置和1份docx说明文档,压缩包约53.11MB,其中训练集1035张、验证集294张、测试集153张,标注采用YOLO边界框与类别标签格式,可直接接入主流检测框架。目前已有461人学习下载,适合需要快速构建农业目标检测基线、验证类别划分与数据增强策略的读者参考使用。
1. 玉米生长阶段检测数据集:从标注文件到YOLO训练,我踩过的那些坑
做农业视觉项目的兄弟大概率都遇到过这个场景:模型在实验室里识别玉米叶片病害准得飞起,一拉到田间地头就翻车——不是把拔节期认成苗期,就是把抽雄期和成熟期搞混。问题往往不在网络结构,而在数据集本身。我最近拆了一份玉米生长阶段检测数据集,里面按苗期、拔节期、抽雄期、成熟期等关键生育期做了目标框标注,格式是标准的目标检测标注结构,可以直接喂给YOLO系列做训练。这份资源适合两类人:一是做智慧农业、作物表型分析的算法工程师,需要快速搭一个生长阶段识别的baseline;二是农学背景想入门视觉检测的研究生,拿它练手比用COCO强得多,因为类别语义清晰、场景单一、标注密度适中。但别急着解压就开跑,农业数据集有几个特有的坑,比如光照跨度大、植株遮挡严重、标注粒度不统一,这些在后面章节我会逐个拆开讲。
2. 数据集结构拆解与YOLO格式转换:别让标注格式成为第一道拦路虎
2.1 先搞清楚手里的文件到底长什么样
解压之后,典型的结构一般是images/放原图,labels/放同名txt标注,外加一个classes.txt或data.yaml记录类别名。但农业数据集有个常见问题:不同来源的包,标注格式可能是Pascal VOC的XML、COCO的JSON,也可能是已经转好的YOLO txt。你得先确认,否则后面训练时loss不降,你还以为是学习率的问题。
我一般会先跑一段脚本做体检,统计图片数量、标注文件数量、类别分布,以及有没有空标注文件。空标注在农业数据集里特别常见——有些图片里玉米苗太小或者被杂草挡住,标注员直接跳过了,这种图如果留在训练集里,会被YOLO当成背景负样本,少量可以,多了就会拉低召回。
import os from collections import Counter img_dir = "images" lbl_dir = "labels" img_files = [f for f in os.listdir(img_dir) if f.endswith((".jpg", ".png", ".jpeg"))] lbl_files = [f for f in os.listdir(lbl_dir) if f.endswith(".txt")] print(f"图片数: {len(img_files)}, 标注数: {len(lbl_files)}") # 检查图片和标注是否一一对应 img_stems = {os.path.splitext(f)[0] for f in img_files} lbl_stems = {os.path.splitext(f)[0] for f in lbl_files} print(f"有图无标注: {len(img_stems - lbl_stems)}") print(f"有标注无图: {len(lbl_stems - img_stems)}") # 统计类别分布和空标注 cls_counter = Counter() empty_count = 0 for f in lbl_files: path = os.path.join(lbl_dir, f) with open(path) as fh: lines = [l.strip() for l in fh if l.strip()] if not lines: empty_count += 1 continue for line in lines: cls_id = line.split()[0] cls_counter[cls_id] += 1 print(f"空标注文件数: {empty_count}") print(f"类别分布: {dict(cls_counter)}")这段脚本的逻辑很直白:先对齐图片和标注的文件名主干,找出缺失对;再逐行读标注,统计每个类别的框数量和空文件数。参数上你只需要改img_dir和lbl_dir两个路径。重点看两个输出:如果“有图无标注”数量超过总数的5%,说明标注不完整,训练时这些图会被当负样本;如果某个类别框数极少(比如成熟期只有几十个框),那这个类别大概率训不出来,需要考虑过采样或合并类别。
2.2 从VOC/COCO转到YOLO txt的实操
如果体检发现标注是XML或JSON,就得转。VOC转YOLO的核心是把绝对坐标的xmin, ymin, xmax, ymax归一化成中心点加宽高的相对值。这里有个容易翻车的地方:不同图片尺寸不一样,归一化时必须用对应图片的宽高,不能用统一值。
import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w, h = Image.open(img_path).size lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text if cls_name not in class_map: continue cls_id = class_map[cls_name] 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 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return lines class_map = {"苗期": 0, "拔节期": 1, "抽雄期": 2, "成熟期": 3}class_map必须和你的data.yaml里names的顺序完全一致,否则训练出来的类别全是错位的。归一化后的值必须在0到1之间,如果出现大于1的,说明原标注框超出了图片边界,这种框要么裁掉要么修正,直接留着会让YOLO的坐标回归震荡。
2.3 data.yaml的写法与路径陷阱
YOLOv5/v8都认data.yaml,结构不复杂但路径写法坑了不少人。train和val最好用绝对路径,或者相对于你执行训练命令时的当前目录。我见过有人写相对路径,结果换台机器就找不到文件。
path: /data/corn_growth train: images/train val: images/val nc: 4 names: ["苗期", "拔节期", "抽雄期", "成熟期"]nc是类别数,必须和names长度一致。中文类别名在部分YOLO版本里会有编码问题,如果训练时报UnicodeDecodeError,把names改成英文或拼音,然后在可视化阶段再映射回中文。另外path和train拼接后的路径必须真实存在,YOLO不会帮你创建目录,路径错了直接报FileNotFoundError,别问我怎么知道的。
3. 训练参数怎么设:农业场景下的YOLO调参经验
3.1 图像尺寸与增强策略的选择
玉米生长阶段检测和通用目标检测最大的区别在于:植株在图像中的尺度变化极大。苗期可能只占几十个像素,抽雄期能占半张图。YOLO默认的imgsz=640在苗期样本上会丢失细节,我一般会拉到imgsz=960甚至1280,代价是显存占用翻倍,batch size得降。
数据增强方面,农业场景要慎用Mosaic。Mosaic把四张图拼一起,对通用检测提点明显,但玉米不同生育期的颜色和形态差异大,拼完之后模型容易学到“拼图边缘”这种伪特征。我的做法是前期用Mosaic快速收敛,最后20个epoch关掉,让模型在真实分布上微调。degrees旋转增强可以开,因为田间拍摄角度本来就多变;hsv_h色调增强要克制,玉米从苗期到成熟期的颜色变化是重要特征,过度色调扰动会让模型把拔节期和抽雄期搞混。
yolo detect train \ data=/data/corn_growth/data.yaml \ model=yolov8m.pt \ imgsz=960 \ epochs=150 \ batch=8 \ mosaic=1.0 \ close_mosaic=20 \ degrees=15.0 \ hsv_h=0.01 \ hsv_s=0.5 \ hsv_v=0.3 \ lr0=0.01 \ patience=30close_mosaic=20表示最后20个epoch关闭Mosaic,这个参数在YOLOv8里直接支持。patience=30是早停耐心值,验证集loss连续30轮不降就停,避免过拟合。hsv_h=0.01把色调扰动压到很低,就是为了保住生育期的颜色特征。
3.2 类别不平衡的处理
农业数据集里各生育期的样本量往往不均匀。苗期和拔节期容易拍,样本多;抽雄期和成熟期植株高大、田间行走困难,样本少。如果直接训,模型会偏向多数类。除了过采样少数类,还可以用YOLO的cls损失权重来调,但更直接的办法是在数据集层面做平衡。
我一般会统计每个类别的框数,然后对少于阈值(比如总框数的10%)的类别,在训练时复制其对应图片。注意是复制图片和标注,不是复制框,否则一张图里同一个目标出现多次,NMS阶段会出问题。复制后重新生成train.txt索引文件即可。
3.3 验证集划分的讲究
随机划分验证集在农业数据上有个隐患:同一块田、同一天的图片可能被分到训练和验证两边,导致验证指标虚高。正确做法是按田块或按拍摄日期划分,让验证集来自模型没见过的地块。如果你手里没有田块信息,至少按图片的文件名前缀或时间戳做分组划分。
import random from collections import defaultdict # 假设文件名格式为 fieldID_date_seq.jpg groups = defaultdict(list) for f in img_files: field_id = f.split("_")[0] groups[field_id].append(f) field_ids = list(groups.keys()) random.shuffle(field_ids) split = int(len(field_ids) * 0.8) train_fields = set(field_ids[:split]) train_imgs = [f for fid in train_fields for f in groups[fid]] val_imgs = [f for fid in field_ids[split:] for f in groups[fid]]这样划分能保证同一块田的图片不会同时出现在训练和验证集里,验证指标更接近真实部署效果。如果你的文件名没有田块信息,那就用拍摄日期做分组,逻辑一样。
4. 避坑与排查:农业数据集训练中最容易翻车的五个点
4.1 现象:训练loss正常下降,但验证集mAP极低
原因:验证集和训练集来自同一块田或同一天,数据泄漏导致训练指标虚高,验证指标才是真实水平。另一种可能是验证集的标注格式和训练集不一致,比如训练集是YOLO txt,验证集还是VOC XML,YOLO读不到标注就当负样本。
解决:按田块或日期重新划分验证集,确保分布独立。用前面2.1的体检脚本分别跑一遍训练集和验证集,确认标注格式、类别ID、文件对应关系完全一致。
4.2 现象:模型把拔节期大量误判为苗期
原因:这两个生育期在视觉上确实接近,尤其是拔节初期,株高还没明显拉开。如果训练集里苗期样本远多于拔节期,模型会倾向于把模糊样本判为苗期。
解决:先检查两个类别的样本量比例,如果超过3:1,对拔节期做过采样。然后在增强上给拔节期图片更多的尺度扰动,让模型学到株高以外的特征,比如叶片展开角度和茎秆粗细。还可以在损失函数里给拔节期更高的分类权重。
4.3 现象:推理时同一株玉米被检出多个框,NMS后仍然重叠
原因:农业场景下植株密集,YOLO的默认NMS IoU阈值是0.45,对于重叠严重的玉米植株,这个阈值会把不同植株的框互相抑制,或者同一植株的多个预测框保留下来。
解决:把NMS IoU阈值调到0.5到0.6之间,具体值在验证集上试。如果植株特别密集,可以考虑用Soft-NMS替代标准NMS,但YOLOv8默认不支持,需要自己改后处理。更简单的办法是训练时用overlap_mask或者调整Anchor,但YOLOv8是Anchor-free的,所以重点还是调NMS阈值和置信度阈值。
4.4 现象:训练到一半突然报CUDA out of memory
原因:imgsz设太大加上batch没降,或者Mosaic增强在后期还在跑,拼图后的实际输入尺寸波动导致显存峰值超标。
解决:先把batch降到能跑的最小值,然后开amp=True混合精度训练,能省30%左右显存。如果还不行,把imgsz从960降到768,或者用close_mosaic提前关掉Mosaic。另外注意workers设太大也会占显存,一般设为CPU核数的70%就够了。
4.5 现象:中文类别名在可视化时显示为方框或乱码
原因:OpenCV的putText不支持中文,YOLO自带的绘图函数用的就是OpenCV。
解决:可视化时用PIL的ImageDraw配合中文字体文件,或者把类别名映射成英文再画。如果只是自己看,映射成拼音最快。如果要出报告,建议用PIL方案,指定simhei.ttf或NotoSansCJK字体路径。
5. 进阶技巧:用TTA和类别映射把mAP再往上推一推
训练完模型只是第一步,推理阶段的技巧往往能再挤出几个点。我一般会在验证集上先跑一遍基础推理,记录每个类别的AP,然后针对低AP的类别做定向优化。
测试时增强(TTA)是最容易上手的。YOLO官方支持augment=True,推理时对图片做翻转、缩放等多尺度变换,然后把结果融合。代价是推理速度慢3到4倍,但mAP通常能涨1到3个点。对于玉米生长阶段这种类别间差异细粒度的任务,TTA的收益比通用检测更明显。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="images/val", imgsz=960, conf=0.25, iou=0.5, augment=True, # 开启TTA save=True, save_txt=True )augment=True就是TTA开关,conf=0.25是置信度阈值,低于这个值的框直接丢弃。iou=0.5是NMS阈值,前面说过农业场景可以适当调高。跑完之后对比augment=False的结果,如果某个类别的AP反而降了,说明TTA对该类别的尺度变换不友好,可以单独关掉。
另一个技巧是类别映射。如果你的应用场景只关心“营养生长期”和“生殖生长期”两个大类,可以把苗期和拔节期合并,抽雄期和成熟期合并,重新训练一个4类到2类的映射模型。类别少了,类间混淆自然降低,mAP会明显上升。但这是以牺牲细粒度信息为代价的,看你的业务需求。
还有一个我常用的验证方法:把验证集按拍摄时间段分成上午、中午、下午三组,分别跑推理,看模型在不同光照下的表现差异。如果中午的AP明显低于早晚,说明模型对强光过曝敏感,训练时需要加更多的亮度扰动增强。这个分析比只看总体mAP有用得多,能直接指导下一轮数据采集和增强策略。
从那以后我每次拿到新的农业数据集,都强制先跑一遍分组验证,确认模型在真实田间条件下的表现,而不是被随机划分的验证集给骗了。希望帮到你。
本文还有配套的精品资源,点击获取