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

玉米识别数据集实战:COCO标注转YOLO训练与部署全流程

玉米识别数据集实战:COCO标注转YOLO训练与部署全流程 ★ FEATURED ARTICLE
简介这是一份面向计算机视觉学习者和目标检测开发者的玉米识别图像数据集整体包含4880张真实拍摄的玉米图片可支撑作物检测、农田智能监测等项目的模型训练与验证。数据集标注采用COCO JSON格式可方便转换为YOLO、Pascal VOC等常用格式标注信息完整适合训练高精度玉米识别模型。本次提供的压缩包共2000个文件其中1997张JPG原图与3个JSON标注文件包体大小约219.79MB文件结构简洁便于直接导入常用深度学习框架使用。目前已有240人浏览学习。基于该数据集训练的模型正确识别率可达98.6%能准确识别玉米目标对农业AI落地具有实用参考价值。下载后可直接获得图像数据与配套标注省去自行采集和标注的时间成本无论是入门目标检测还是优化已有模型都能提供高质量训练样本。1. 玉米识别数据集到模型落地4880 张图能撑起 98.6% 的识别率吗玉米识别数据集4880 张田间影像、coco json 标注标题里写着识别率可达 98.6%——做农林业视觉的人看到这种描述通常会先停一下。单类别检测任务里准确率这个数字很容易虚高真正决定模型能不能用起来的是标注一致性、图像覆盖范围和验证集的切法。这篇笔记就把拿到这套数据集后最常走的流程完整走一遍先拆 coco json 的字段结构再把标注转成 YOLO 能直接读的 txt调一轮增强参数最后把那些让训练翻车的坑挨个点名。适合刚接触目标检测的农学背景开发者也适合打算把玉米识别模型部署到田间统计、表型分析场景的工程师。4880 张图不算多但用在单类别密集检测上配合合理的增强和训练策略足够撑起一个能落地的模型98.6% 到底怎么理解后文会专门说。2. 读懂 coco json 标注从字段结构到转换脚本2.1 玉米识别任务和普通检测的差异遮挡、密集和生育期通用目标检测数据集里类别之间通常有清晰的轮廓差异比如车和行人、猫和狗。玉米识别是典型的单类别任务难点反而在类内差异和场景干扰苗期和成熟期形态完全不同抽雄期的雄穗长在顶端、果穗藏在叶腋里吐丝期的花丝从苞叶顶端伸出来颜色和干枯叶片高度接近。再加上田间植株互相遮挡、叶片把果穗盖住大半检测器要学的不是玉米长什么样而是在一堆玉米须、玉米叶和阴影里怎么把果穗边界找出来。这种差异直接决定了两件事。第一标注框怎么画果穗是近似椭圆的实体框稍微松一点就会把叶片包进来特征里混入大量背景第二训练集怎么切如果 4880 张图全来自同一地块的同一生育期模型在另一个田块上往往直接翻车这和图片分辨率无关是场景偏差问题。另外这类数据集很多来自无人机低空遥感影像视角固定朝下和手机平拍的视角差异很大训练前最好确认标注图像的采集方式避免部署时视角一变就失效。2.2 coco json 三段结构images / annotations / categoriescoco json 是目标检测领域最常见的标注交换格式结构上就是三个顶层键。images 记录每张图的 id、文件名、宽高annotations 记录每个目标框属于哪张图、类别 id、bbox 和面积categories 记录类别 id 到类别名的映射。下面是最小可用的结构{ images: [ {id: 1, file_name: field_01.jpg, width: 1280, height: 720} ], annotations: [ {id: 1, image_id: 1, category_id: 1, bbox: [410, 233, 86, 154], area: 13244, iscrowd: 0} ], categories: [ {id: 1, name: corn} ] }这里最容易搞错的是 bbox 的格式。coco 的 bbox 是绝对像素坐标的 [x, y, width, height]x、y 是框左上角的坐标不是中心点。area 字段在矩形标注下等于宽高乘积但如果标注源是 polygon 多边形area 必须按实际多边形面积计算直接用宽高乘积会让一些训练框架在按面积过滤小目标时产生偏差。iscrowd 在玉米识别这种单实例标注里必须为 0一旦混入 1检测框架会把它当作密集人群或物体堆来处理训练时直接跳过或计算异常。2.3 用 Python 把 labelme 标注转成 coco json常见标注工具里labelme 生成的 json 是单文件模式每张图配一个同名 json。很多做玉米识别的人手里攒的标注都是这种格式转成 coco json 的脚本可以这样写import json, os, glob from PIL import Image def labelme_to_coco(label_dir, img_dir, out_path, category_namecorn): images, annotations [], [] categories [{id: 1, name: category_name}] ann_id, img_id 0, 0 for label_path in glob.glob(os.path.join(label_dir, *.json)): with open(label_path, r, encodingutf-8) as f: label json.load(f) img_name label[imagePath] with Image.open(os.path.join(img_dir, img_name)) as im: width, height im.size img_id 1 images.append({ id: img_id, file_name: img_name, width: width, height: height }) for shape in label[shapes]: if shape[label].lower() not in (category_name, maize): continue xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x_min, y_min min(xs), min(ys) w max(xs) - x_min h max(ys) - y_min ann_id 1 annotations.append({ id: ann_id, image_id: img_id, category_id: 1, bbox: [x_min, y_min, w, h], area: w * h, iscrowd: 0 }) with open(out_path, w, encodingutf-8) as f: json.dump({ images: images, annotations: annotations, categories: categories }, f, ensure_asciiFalse, indent2) print(fdone: {len(images)} images, {len(annotations)} annotations)逻辑说明脚本遍历 labelme 目录下所有 json从 imagePath 找到原始图片并读取宽高把每个 shape 的 points 取横纵坐标的最小最大值得到外接矩形作为 bbox。这里刻意用了 min/max 而不是直接取矩形框的前两个点是为了兼容 labelme 里用矩形工具画出的四点坐标。参数说明label_dir 和 img_dir 建议用绝对路径避免相对路径在不同脚本调用位置下解析出错category_name 默认 corn如果你的标注文件里混用了 corn 和 maize 两种写法脚本会同时纳入。真实项目里最翻车的地方不是脚本逻辑而是 shape 里有空的 polygon 或 label 字段为空转换前先用 json.load 检查一遍全部标注文件比较稳妥。2.4 划分 train / val / test按地块和时段分层别混切4880 张图不算小但划分方式比数量更影响最终指标。常见的错误是随机按文件名 shuffle 后切 8:1:1如果这些图来自同一地块连续拍摄的视频抽帧train 和 val 里会出现大量近似重复帧模型相当于开着卷子考试val 指标虚高。我一般的做法是按采集维度分组文件名里带 field_01、field_02 之类的就按地块分组后再分 train/val如果是同一地块不同日期拍的就按日期分组。这样能保证同一地块的画面不会同时出现在训练集和验证集里。用 sklearn 的 GroupShuffleSplit 可以快速实现关键参数是 groups 传入每个样本对应的地块编号。切分比例上单类别检测任务 80% 训练、20% 验证够用测试集如果条件允许专门留一个没参与训练的地块比随机切更接近真实部署的难度。3. 用 YOLO 训练玉米检测模型coco json 转 YOLO txt 与最小命令3.1 选型为什么是 YOLOv8 / YOLO11 而不是双阶段模型单类别密集检测场景我一般先选 YOLO 系理由不是精度最高而是工程链路最短。YOLOv8 和 YOLO11 的训练、验证、导出命令统一Ultralytics 框架自带增强、NMS 和多尺度推理对新手友好推理速度在 GPU 上能达到实时后续部署到 Jetson 或普通服务器做批量推理都够用。双阶段模型如 Faster R-CNN 在严重遮挡场景下精度可能略高但训练配置复杂、推理慢对玉米识别这种目标不算太小、数量不算极端的任务性价比不划算。真正要注意的是模型尺度。4880 张图、每张可能含几十个果穗属于中小目标密集分布yolov8s 或 yolo11s 通常是起点如果图像分辨率不高、果穗像素占比很小再考虑 yolov8m 甚至 l 版本。不要一上来就用 nano 版本玉米穗比不过人脸那么大nano 的特征提取能力在遮挡场景下明显不够。3.2 把 coco json 转成 YOLO txt 的脚本Ultralytics 不直接读 coco json 训练需要转成 YOLO 的 txt 格式每张图一个同名 txt每行是 class_id cx cy w h坐标全部归一化到 0~1。转换脚本如下import json, os def coco_to_yolo(coco_path, out_dir): with open(coco_path, r, encodingutf-8) as f: coco json.load(f) cat_map {c[id]: idx for idx, c in enumerate(coco[categories])} img_map {img[id]: img for img in coco[images]} os.makedirs(out_dir, exist_okTrue) for img_id, img in img_map.items(): lines [] for ann in coco[annotations]: if ann[image_id] ! img_id: continue x, y, w, h ann[bbox] cx (x w / 2) / img[width] cy (y h / 2) / img[height] nw w / img[width] nh h / img[height] cls cat_map[ann[category_id]] lines.append(f{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: stem os.path.splitext(img[file_name])[0] with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines)) print(fconverted {len([f for f in os.listdir(out_dir) if f.endswith(.txt)])} txt files)逻辑说明coco 的 bbox 是像素坐标左上角加宽高YOLO 需要中心点坐标且归一化。脚本先建立 category id 到连续数字 class id 的映射然后逐图逐标注转换。参数说明最关键的坑在 cat_map 这一步。coco 的 category_id 可以是 1、3、5 这种非连续值而 YOLO 的 class id 必须从 0 开始连续递增直接用原始 id 会导致类别数对不上。另外归一化的分母必须用每张图自己的 width 和 height不能图省事统一除以 1280一旦图片尺寸不一致所有框的位置都会偏。3.3 yolov8 训练自己的数据集data.yaml 与关键超参转完 txt 后需要写一个 data.yaml 指向数据目录path: D:/corn_dataset train: images/train val: images/val nc: 1 names: [corn]然后执行训练命令yolo detect train \ datacorn.yaml \ modelyolo11s.pt \ imgsz1280 \ batch8 \ epochs200 \ patience30 \ lr00.01参数说明imgsz 是玉米识别任务里最值得加的参数。默认 640 对普通检测够用但玉米果穗在整张图里占比往往只有百分之几640 分辨率下小目标直接糊成一团我一般起步就设 1280显存够可以试 1536。batch 受显存限制8 在 12GB 显存下配合 1280 基本是安全值epochs 设 200 配合 patience 30 做早停单类别任务通常 100 轮左右就收敛不必死等 200。lr0 保持默认 0.01 即可玉米识别没有复杂到需要特殊调学习率的程度。训练过程中重点看两个输出train_loss 下降曲线和 val 的 mAP 曲线。如果 train_loss 降了但 val mAP 纹丝不动优先怀疑数据集划分而不是模型容量。3.4 验证视角为什么看 mAP50 和 recall而不是 98.6% 这个数标题里的 98.6% 需要先问三个问题在哪个验证集上算的、置信度阈值取多少、用的是准确率还是 mAP。单类别目标检测里准确率是个容易被背景样本稀释的指标如果一张图里只有一个玉米穗模型把整张图框满准确率也可能很高。真正要看的指标是 mAP50IoU 阈值 0.5 下的平均精度和 recall。验证命令yolo detect val modelruns/detect/train/weights/best.pt datacorn.yaml imgsz1280输出里重点看 mAP50 和混淆矩阵。玉米识别场景 mAP50 达到 0.95 以上不算难难的是 recall 和 precision 的平衡recall 低说明漏检多地块里玉米穗数统计偏少precision 低说明误检多把叶子、雄穗算进去会虚胖。所以 98.6% 这个数只有搞清楚口径才有意义你自己的模型得用 val 集重新验证一遍。4. 把 4880 张图榨干数据增强参数与样本均衡4.1 玉米场景的增强组合mosaic 怎么开翻转怎么设4880 张图对深度学习来说不算多数据增强直接决定模型泛化上限。Ultralytics 的增强参数默认值对通用场景友好但玉米识别需要针对性调整。mosaic 默认开启把四张图拼成一张对提高小目标检测和遮挡鲁棒性很有帮助但要注意训练后期必须关闭否则模型始终看到的是拼接图对真实单图场景不敏感。设 close_mosaic10 表示最后 10 轮关闭。翻转设置上fliplr左右翻转在玉米识别里可以开果穗左右位置没有物理约束flipud上下翻转我一般设 0.0 或不超过 0.1。原因是玉米植株有明确的上下结构果穗长在茎秆中下部上下翻转会让模型学到错误的先验部署时对倒伏玉米可能误检。HSV 扰动建议适度开启田间光照变化大hsv_h 0.015、hsv_s 0.5、hsv_v 0.4 是比较稳的组合能模拟不同时段和天气的光照差异。4.2 离线和在线增强的取舍什么时候预生成一批增强样本YOLO 的增强是实时的每轮 epoch 读图时随机变换不额外占磁盘这是首选方式。但有一个场景必须考虑离线增强492 张原始图太少或某些地块类别不平衡时实时增强的随机性不够模型每轮看到的样本分布仍然倾斜。离线增强的做法是先把 4880 张图复制一份用 imgaug 或 albumentations 生成亮度抖动、仿射变换、随机裁剪的样本再合并进训练集。注意两点第一离线增强只作用于训练集val 和 test 保持原始图第二离线增强后的图片不要和原始图在文件名上撞车否则划分数据集时同源图片可能同时进 train 和 val。4.3 单类别任务的背景误检问题负样本该不该加单类别检测模型最常见的翻车方式是背景误检地膜反光、干枯叶片堆、田垄阴影被框成玉米。原因是训练集里所有图都含有正样本模型没见过完全没有玉米但看着像玉米的画面。解决方式是收集一批负样本图——同一地块里没有果穗的过道、杂草区、远处农田——放进训练集对应的 txt 文件留空即可。Ultralytics 读空 txt 时会自动把该图当作背景图参与训练。负样本的比例我一般加到训练集的 10%~20%。加太多会让模型变得保守precision 上去了 recall 掉下来加太少起不到抑制作用。验证集里也建议放少量负样本否则部署时模型到底误检成什么样你在训练指标上完全看不到。5. 玉米识别常见问题排查标注、训练与部署里的五个坑5.1 标注框稍大 5%mAP 掉了两个点现象模型没改任何参数只是换了一版标注mAP50 从 0.98 掉到 0.96。看起来差距不大但在地块统计场景里框大 5% 意味着遮挡严重的果穗边界被背景污染导致 IoU 计算时反复踩 0.5 阈值这条线。原因标注人员画框时习惯性地把果穗外的叶子也包进去尤其玉米须从苞叶顶端伸出时框会不自觉地往外扩。特征里混入叶片纹理检测器学到的不是果穗边界而是叶片包围的绿色区域。解决重新审视标注规范要求框的四边紧贴果穗可见边缘不包含花丝和相邻叶片。如果不想全部重标退一步的办法是转换时对 bbox 做 5% 内缩我实际操作时用w * 0.95; h * 0.95并让中心点不变效果接近人工收紧但对长条形目标不适用。5.2 雄穗当成果穗被检出标注规则不统一现象val 的 precision 掉到 0.9 以下打开验证集预测图发现模型把顶部雄穗也框成 corn。雄穗和果穗都是穗状结构颜色在抽雄初期接近。原因标注人员在早期批次把雄穗也标了框或者标了一部分雄穗后又改规则导致训练数据里同一类别的目标定义不一致。模型学到的是穗状物就是玉米而不是果穗才是玉米。解决统一标注规则明确只标果穗抽雄期的雄穗一律不标。如果数据里的雄穗框已经混进去了用 json 工具把类别名筛出来逐个确认删除。这里可以用 jq 快速统计每一类标注数量确认清理干净后再转 YOLO 格式重新训练。5.3 json 文件解析报错被 Excel 和系统预览改坏的标注现象训练脚本读 coco json 时报Expecting property name enclosed in double quotes或UnicodeDecodeError停在第 320 个文件。原因标注文件被 WPS、Excel 或系统图片预览程序打开过并被自动保存导致 json 的引号变成中文引号、编码从 UTF-8 变成带 BOM甚至被截断。这类问题在数据集分享时特别常见下载源文件没坏本地一预览就坏。解决不要用 Excel 打开 json 文件做查看。排查时先用 Python 单文件定位到出错的 json再用文本编辑器按行检查确认哪一处被改动。如果文件已经乱掉最快的方法是重新下载原始文件不要手工拼接手工拼出来的 json 容易存在肉眼看不出的边界错误。顺手用 jq 跑一遍jq .images | length可以快速确认 json 整体结构是否完好。5.4 dataloader 卡死在 num_workersWindows 多进程与内存现象训练一开始 GPU 利用率是 0%进度条停在第一个 epochWindows 下报DataLoader worker (pid xxx) exited unexpectedly。原因num_workers 设太大每个 worker 都要把图片加载进内存4880 张图加上增强后的副本内存瞬间吃满Windows 下多进程数据加载的主进程和 worker 还可能发生冲突卡死是常态。解决Windows 上 num_workers 直接设 0 或 4不要学 Linux 教程设 8 或 16。显存和内存足够的条件下优先开 cacheTrue把图片缓存到内存里加速读取但先评估机器内存能不能装下全部训练图。我在这上面翻车过以为多开 worker 能提速结果爬了一个小时没出一个 batch老老实实调回 4 就跑了。5.5 val 指标虚高同一地块帧被切进 train 和 val现象训练曲线很漂亮val mAP50 到 0.99模型拿到另一个地块的图上直接跌掉 20 个点。原因数据集来源于无人机视频抽帧train 和 val 里混进了同一段飞行的相邻帧。相邻帧的背景、植株、光照几乎没有变化模型在 val 上等于看过了标准答案。解决按地块和日期分组划分保证同源帧永远不会跨集合。用 GroupShuffleSplit 指定 groups 为地块编号。我一般的做法是先把文件名单独导出看一眼每个地块的占比如果某个地块图片特别多就按时间均匀抽帧而不是全要避免单一地块主导训练分布。6. 把 98.6% 变成自己的模型指标阈值、TTA 与 ONNX 导出的三个技巧先做阈值选择。训练完成后best.pt 的默认置信度阈值是 0.25但这个值对玉米识别不一定是 F1 最优。用 val 集跑一遍预测把置信度从 0.05 到 0.9 按 0.05 步长扫描画出 precision-recall 曲线取 F1 最大的点作为部署阈值。密集地块需要高召回就适当放低阈值到 0.15代价是误检变多统计测产场景宁可漏检少不能误检多阈值调到 0.4 附近更稳。然后是测试时增强 TTA。Ultralytics 预测时开ttaTrue会对每张图做三种尺度推理再融合结果。玉米穗是中小目标多尺度融合能显著降低漏检率适合对精度要求高的批量离线推理场景。代价是推理时间大约翻三倍实时视频流不适合开但田间拍照统计完全能接受。最后是导出。部署时把 best.pt 转 ONNX 是常见做法命令用yolo export modelbest.pt formatonnx imgsz1280注意导出时的 imgsz 必须和训练一致否则直接掉精度。如果要做 int8 量化加速校准集必须包含过曝、阴天、背光这些边界场景只用正常光照的图校准量化后的模型会在逆光地块上翻车。我自己的习惯是拿到任何标注数据集先花半小时做标注可视化把框画在图上人工抽检 50 张确认没有雄穗混标、没有框严重外扩再动手训练指标漂亮只是第一步换一块地还能打的模型才算数。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站