ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

玉米识别数据集实战:1000张图片COCO标注与YOLOv8训练全流程

玉米识别数据集实战:1000张图片COCO标注与YOLOv8训练全流程 简介这份玉米识别数据集面向计算机视觉学习者、农业图像识别研究者及目标检测模型训练者用于解决玉米图像样本不足、标注格式不统一的问题。资源包共1005个文件以1000张jpg原始图片为主体另含3个json标注文件和2个txt说明文件压缩包约39.02MBjson文件采用COCO标注格式可直接对接主流检测框架进行训练与验证。图片覆盖不同生长阶段与拍摄场景的玉米样本命名规范、目录结构清晰便于按类别划分训练集与测试集。已有375人学习下载适合作为目标检测课程实验、毕业设计或农业智能识别项目的入门数据。读者可借助现成标注快速搭建数据加载流程省去人工标注成本将精力集中于模型选型、参数调优与效果对比也可在此基础上扩充样本或迁移至其他作物识别任务。1. 玉米识别数据集1000 张原始图片做 COCO 标注到底能跑出什么结果手里有一批玉米田间的原始照片想训练一个能认出玉米植株、雄穗或者果穗的目标检测模型第一步往往不是选 YOLO 还是 Faster R-CNN而是卡在标注上。这个标题说的就是这件事1000 张原始图片按 COCO 格式做标注打包成一个可直接喂给检测框架的玉米识别数据集。它解决的是从「一堆散图」到「模型能读的标注文件」之间那段最枯燥、最容易翻车的路。适合两类人一类是农学或智慧农业方向的研究生手上有图但没标注经验另一类是做边缘部署的工程师想拿一个中等规模数据集快速验证玉米场景下的检测可行性。1000 张不算大但足够跑通全流程也足够暴露标注质量、类别定义、格式转换里的真实问题。2. COCO 标注格式拆解玉米数据集里每个字段在说什么2.1 COCO 的 JSON 骨架与玉米场景的对应关系COCO 格式本质上是一个 JSON 文件里面用几个顶层键把图像、标注、类别串起来。对玉米识别来说最需要盯住的是images、annotations、categories这三块。images里每条记录对应一张原始图片包含id、file_name、width、heightannotations里每条记录是一次标注包含image_id、category_id、bbox、area、iscrowdcategories定义类别名和对应的id。玉米场景下类别怎么定直接决定后面模型能输出什么。常见做法是分三类corn_plant整株、corn_tassel雄穗、corn_ear果穗。如果只做植株计数可以只保留corn_plant一类标注量会少很多但模型能表达的信息也少。bbox是[x, y, width, height]注意这里的x, y是左上角坐标不是中心点。很多从 YOLO 转过来的人会习惯性写中心点结果训练时框全偏了。area是框的面积COCO 评估时会用到一般直接算width * height如果标注里有遮挡或截断可以按可见区域算但 1000 张规模的数据集里统一用框面积更省事。iscrowd在玉米场景里基本用不上除非有密集植株互相遮挡且你不想让模型学单个实例一般设为 0。2.2 用 Python 校验 COCO 标注文件的最小脚本拿到或做完标注后别急着丢给训练脚本。先跑一段校验把明显有问题的记录揪出来。下面这段代码检查三件事图片文件是否存在、bbox是否越界、category_id是否在categories里定义过。import json import os from pathlib import Path # 标注文件和图片目录按实际路径改 ann_path annotations/instances_corn.json img_dir Path(images) with open(ann_path, r, encodingutf-8) as f: data json.load(f) # 建立 image_id 到文件名的映射 id2file {img[id]: img[file_name] for img in data[images]} # 建立合法 category_id 集合 valid_cats {cat[id] for cat in data[categories]} problems [] for ann in data[annotations]: img_id ann[image_id] fname id2file.get(img_id) if fname is None: problems.append(fannotation {ann[id]} 引用了不存在的 image_id {img_id}) continue # 检查图片文件是否存在 if not (img_dir / fname).exists(): problems.append(f图片缺失: {fname}) # 检查 bbox 是否越界 x, y, w, h ann[bbox] img_info next(i for i in data[images] if i[id] img_id) if x 0 or y 0 or x w img_info[width] or y h img_info[height]: problems.append(fbbox 越界: image {fname}, bbox {ann[bbox]}) # 检查 category_id 是否合法 if ann[category_id] not in valid_cats: problems.append(f非法 category_id: {ann[category_id]} in image {fname}) if problems: print(f发现 {len(problems)} 个问题) for p in problems[:20]: print( -, p) else: print(校验通过没有发现明显问题)这段脚本的逻辑很直白先把images和categories里的关键信息抽成字典和集合然后逐条遍历annotations。id2file用来快速定位标注对应的图片文件名valid_cats用来判断类别是否合法。bbox越界检查用的是图片原始宽高所以images里每张图的width和height必须准确不能随手填。如果输出里出现大量「图片缺失」先检查file_name是不是带了多余的前缀路径COCO 里通常只写文件名不写images/这种目录前缀。2.3 类别定义与标注粒度1000 张图怎么分才不浪费1000 张原始图片如果每张都标整株、雄穗、果穗三类标注工作量大概是 3000 个框起步。实际田间照片里雄穗和果穗往往只出现在特定生育期如果图片跨越多个时期类别分布会很不均匀。我一般会先抽 50 张看一眼统计一下每个类别大概能出现多少次。如果某一类在超过 80% 的图里都不存在那这类要么合并要么单独补图。另一个坑是「整株」和「雄穗」的框会大量重叠COCO 允许重叠标注但训练时模型要学的是不同尺度的特征如果雄穗框和整株框几乎一样大模型很难区分。常见做法是整株框标到植株可见轮廓雄穗框只标雄穗本身两者面积比至少差一个数量级这样多尺度检测才有意义。3. 从原始图片到 COCO 标注1000 张图的落地流程3.1 图片预处理统一尺寸、去重与命名规范原始图片往往来自不同手机或相机尺寸、方向、命名五花八门。直接拿去标注后面转格式时容易出乱子。第一步是统一处理把所有图片转成 JPEG长边缩到 1280 或 1920短边按比例缩放。EXIF 方向信息要提前用PIL.ImageOps.exif_transpose校正否则标注工具里看到的图和实际像素方向不一致框会画歪。命名上建议用corn_0001.jpg这种零填充的序号避免IMG_1234和DSC_5678混在一起排序混乱。from PIL import Image, ImageOps from pathlib import Path src_dir Path(raw_images) dst_dir Path(images) dst_dir.mkdir(exist_okTrue) # 按文件名排序保证序号稳定 files sorted([f for f in src_dir.iterdir() if f.suffix.lower() in (.jpg, .jpeg, .png)]) for idx, f in enumerate(files, start1): img Image.open(f) img ImageOps.exif_transpose(img) # 校正方向 img img.convert(RGB) # 长边缩到 1280 w, h img.size scale 1280 / max(w, h) if scale 1: img img.resize((int(w * scale), int(h * scale)), Image.LANCZOS) out_name fcorn_{idx:04d}.jpg img.save(dst_dir / out_name, quality95) print(f{f.name} - {out_name}, size{img.size})这段代码做了三件事EXIF 方向校正、RGB 转换、长边缩放。ImageOps.exif_transpose会直接修改像素数据保证后续标注工具读到的方向和肉眼看到的一致。缩放比例只在长边超过 1280 时才生效避免小图被放大。输出文件名用四位零填充1000 张图正好到corn_1000.jpg排序和索引都方便。处理完后原始图片目录可以保留不动后续标注和训练都只用images/下的副本。3.2 标注工具选型与 COCO 导出设置标注工具常见的有 LabelImg、CVAT、Roboflow、Label Studio。LabelImg 只支持 Pascal VOC 和 YOLO 格式要转 COCO 得自己写脚本CVAT 和 Label Studio 可以直接导出 COCO JSON适合团队协作Roboflow 在线标注方便但免费额度对 1000 张图来说比较紧。如果只是一个人标我一般用 LabelImg 标成 YOLO 格式再用脚本转 COCO因为 LabelImg 轻量、不依赖网络。如果多人协作CVAT 更合适导出时选「COCO 1.0」格式注意勾选「Include images without annotations」和「Use image file names instead of IDs」这样导出的 JSON 里file_name直接对应图片名不用再映射。标注时有个细节COCO 的bbox是绝对像素坐标不是归一化的。LabelImg 的 YOLO 格式输出的是归一化中心点坐标转换时要乘回宽高。下面是一个从 YOLO 转 COCO 的脚本片段假设每张图对应一个.txt标注文件每行是class_id cx cy w h。import json from pathlib import Path from PIL import Image img_dir Path(images) label_dir Path(labels) # YOLO 格式的 txt categories [ {id: 1, name: corn_plant, supercategory: corn}, {id: 2, name: corn_tassel, supercategory: corn}, {id: 3, name: corn_ear, supercategory: corn}, ] coco {images: [], annotations: [], categories: categories} ann_id 1 for img_id, img_path in enumerate(sorted(img_dir.glob(*.jpg)), start1): with Image.open(img_path) as im: w, h im.size coco[images].append({ id: img_id, file_name: img_path.name, width: w, height: h, }) label_path label_dir / (img_path.stem .txt) if not label_path.exists(): continue for line in label_path.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) # YOLO 归一化中心点转 COCO 绝对左上角 x (cx - bw / 2) * w y (cy - bh / 2) * h abs_w bw * w abs_h bh * h coco[annotations].append({ id: ann_id, image_id: img_id, category_id: int(cls_id) 1, # YOLO 从 0 开始COCO 从 1 开始 bbox: [round(x, 2), round(y, 2), round(abs_w, 2), round(abs_h, 2)], area: round(abs_w * abs_h, 2), iscrowd: 0, }) ann_id 1 with open(annotations/instances_corn.json, w, encodingutf-8) as f: json.dump(coco, f, ensure_asciiFalse, indent2) print(f写入 {len(coco[images])} 张图{len(coco[annotations])} 条标注)关键点在category_id的偏移YOLO 的类别索引从 0 开始COCO 的category_id从 1 开始如果直接照搬第一类会变成 0校验脚本会报非法。bbox转换时先算左上角再算宽高最后round到两位小数避免浮点误差导致越界。area直接用宽高乘积和bbox保持一致。如果某张图没有对应.txt脚本会跳过但images里仍然保留该图这样 COCO 里会有无标注图片训练时一般不影响但评估时要注意。3.3 标注质量抽检用可视化脚本快速定位漏标和错标标完 1000 张图不可能逐张肉眼过。写一个可视化脚本把 COCO 标注画回图片上随机抽 20 张看一遍能发现大部分低级错误。下面这段代码用matplotlib和PIL把框和类别名画出来。import json import random from pathlib import Path from PIL import Image, ImageDraw, ImageFont import matplotlib.pyplot as plt ann_path annotations/instances_corn.json img_dir Path(images) with open(ann_path, r, encodingutf-8) as f: data json.load(f) id2file {img[id]: img[file_name] for img in data[images]} cat_map {cat[id]: cat[name] for cat in data[categories]} # 按 image_id 分组标注 from collections import defaultdict img2anns defaultdict(list) for ann in data[annotations]: img2anns[ann[image_id]].append(ann) # 随机抽 20 张有标注的图 sample_ids random.sample(list(img2anns.keys()), min(20, len(img2anns))) fig, axes plt.subplots(4, 5, figsize(20, 16)) for ax, img_id in zip(axes.ravel(), sample_ids): fname id2file[img_id] img Image.open(img_dir / fname).convert(RGB) draw ImageDraw.Draw(img) for ann in img2anns[img_id]: x, y, w, h ann[bbox] draw.rectangle([x, y, x w, y h], outlinered, width3) draw.text((x, y - 10), cat_map[ann[category_id]], fillred) ax.imshow(img) ax.set_title(fname, fontsize8) ax.axis(off) plt.tight_layout() plt.savefig(check_annotations.jpg, dpi100) print(已保存 check_annotations.jpg)跑完后打开check_annotations.jpg重点看三种情况框明显偏出植株的、同一植株被标了多个重叠框的、类别名和实际对象对不上的。如果发现某类错误集中出现比如雄穗框普遍偏大那就回到标注工具里批量修别一张张改。抽检比例不用太高20 张里如果有 3 张以上有问题整个数据集的可信度就要打问号。4. 玉米识别数据集避坑标注和转换里最容易翻车的 5 个点4.1 现象训练时 loss 不降mAP 一直是 0原因category_id从 0 开始或者categories里定义的id和annotations里的category_id对不上。COCO 评估脚本默认类别从 1 开始如果标注里出现 0会被当成背景或直接忽略。解决跑一遍 2.2 的校验脚本把所有非法category_id打出来统一加 1 或重新映射。4.2 现象可视化时框的位置整体偏移原因bbox写成了中心点坐标或者图片缩放后没有同步更新width和height。COCO 的bbox必须是绝对左上角坐标且和images里的宽高一致。解决检查转换脚本里是否用了(cx - bw/2) * w这种公式缩放图片后重新生成images里的宽高不要沿用原始尺寸。4.3 现象某些图片在训练时被跳过日志提示「invalid bbox」原因bbox的宽或高为 0或者x w刚好等于图片宽度但浮点误差导致越界。标注时如果框拉得太小或者转换时round截断都可能出现。解决在转换脚本里加一层过滤w 1或h 1的标注直接丢弃x w img_width的裁剪到边界。4.4 现象同一张图里同类目标被标了多个高度重叠的框原因标注时重复点击或者多人协作时没有去重。COCO 不会自动合并重叠框训练时模型会学到重复的监督信号导致输出冗余框。解决用 IoU 阈值做后处理同类框 IoU 大于 0.7 的只保留面积最大的一个。这个操作可以在转 COCO 之前做也可以在训练前做。4.5 现象导出 COCO JSON 后用 pycocotools 加载报KeyError: segmentation原因某些工具导出的 COCO 只包含检测框没有segmentation字段但pycocotools的COCO类在初始化时会尝试访问。解决在 JSON 里给每条annotation补一个空的segmentation: []或者用COCO的loadRes而不是COCO构造函数。如果只做检测补空列表最省事。5. 用 1000 张玉米数据跑通验证从 COCO 到 YOLO 训练的最小闭环5.1 把 COCO 标注转成 YOLO 格式并划分训练验证集COCO 标注做完后最终要落到某个检测框架上。YOLOv8 是目前比较省心的选择但它吃的是 YOLO 格式的 txt不是 COCO JSON。所以需要再转一次同时按 8:2 划分训练集和验证集。下面这段脚本把 COCO 转成 YOLO 的images/和labels/目录结构并生成data.yaml。import json import random import shutil from pathlib import Path from PIL import Image ann_path annotations/instances_corn.json src_img_dir Path(images) out_root Path(dataset) for split in [train, val]: (out_root / images / split).mkdir(parentsTrue, exist_okTrue) (out_root / labels / split).mkdir(parentsTrue, exist_okTrue) with open(ann_path, r, encodingutf-8) as f: data json.load(f) id2file {img[id]: img[file_name] for img in data[images]} id2size {img[id]: (img[width], img[height]) for img in data[images]} cat_ids sorted([cat[id] for cat in data[categories]]) cat_id2idx {cid: i for i, cid in enumerate(cat_ids)} # COCO id 映射到 0-based 索引 from collections import defaultdict img2anns defaultdict(list) for ann in data[annotations]: img2anns[ann[image_id]].append(ann) all_ids list(id2file.keys()) random.seed(42) random.shuffle(all_ids) split_idx int(len(all_ids) * 0.8) train_ids set(all_ids[:split_idx]) val_ids set(all_ids[split_idx:]) for img_id in all_ids: split train if img_id in train_ids else val fname id2file[img_id] w, h id2size[img_id] shutil.copy(src_img_dir / fname, out_root / images / split / fname) lines [] for ann in img2anns.get(img_id, []): x, y, bw, bh ann[bbox] # 转成 YOLO 归一化中心点 cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h cls_idx cat_id2idx[ann[category_id]] lines.append(f{cls_idx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) label_path out_root / labels / split / (Path(fname).stem .txt) label_path.write_text(\n.join(lines)) # 生成 data.yaml names [cat[name] for cat in sorted(data[categories], keylambda c: c[id])] yaml_text fpath: {out_root.resolve()} train: images/train val: images/val names: for i, n in enumerate(names): yaml_text f {i}: {n}\n (out_root / data.yaml).write_text(yaml_text) print(转换完成data.yaml 已生成)这段脚本的核心是cat_id2idx映射COCO 的category_id可能是 1、2、3但 YOLO 要求类别索引从 0 开始连续。random.seed(42)保证每次划分结果一致方便复现。shutil.copy把图片复制到对应 split 目录标注文件按同名.txt写入。data.yaml里的names顺序必须和cat_id2idx的映射一致否则类别名会错位。5.2 启动 YOLOv8 训练并观察玉米场景的 mAP 变化环境装好后一条命令就能开跑。假设用的是ultralytics包data.yaml在dataset/下。yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns/corn \ nameexp1modelyolov8n.pt是最小的预训练权重1000 张图从零训练容易过拟合用预训练权重微调更稳。imgsz640是 YOLOv8 的默认输入尺寸如果原始图片长边 1280缩放后小目标会变模糊雄穗这种小框可能掉点。可以试imgsz960但显存占用会上去。patience20表示 20 个 epoch 没有提升就早停1000 张图通常 50 到 80 个 epoch 就收敛了。训练日志里重点看metrics/mAP50-95如果验证集 mAP 在 0.3 以下先回去查标注质量而不是调模型。5.3 验证集评估与单张图片推理的检查习惯训练结束后用验证集跑一次评估再抽几张图做推理可视化。yolo detect val \ modelruns/corn/exp1/weights/best.pt \ datadataset/data.yaml \ imgsz640评估输出里会给出每类的 AP如果corn_tassel的 AP 明显低于其他类大概率是雄穗框太小或标注不一致。推理检查用yolo detect predict把conf调到 0.25 左右看输出的框有没有明显漏检或误检。我一般会保留一个check_predictions/目录每次训练完抽 10 张图存进去对比不同 epoch 的权重肉眼判断模型是不是在学正确的东西。1000 张图的数据集从标注到训练跑通顺利的话两三天卡住的话一周都在修格式。希望帮到你。本文还有配套的精品资源点击获取
返回列表