尧图精选

基于5647张VOC玉米识别数据集的YOLO密集小目标检测与计数实战

🕒 发布时间:2026/9/26 2:26:00 📁 来源:尧图网络
简介这份玉米识别数据集面向从事农业视觉检测、目标计数与图像分类的开发者与算法学习者可用于统计玉米粒个数或判断图像中是否包含玉米适用于目标检测模型训练与验证场景。资源采用PASCAL VOC XML格式标注共5647张图像压缩包内包含2000个xml标注文件整体约587.34MB标注文件与图像一一对应便于直接接入YOLO、Faster R-CNN等主流检测框架进行训练与评估。据描述该数据集对玉米粒的识别正确率可达99.6%标注质量较高适合用于高精度计数任务或作为二分类判断玉米是否存在的样本来源。目前已有187人学习下载可为农业自动化分拣、产量估算等方向提供现成的数据基础减少自行采集与标注的成本。1. 玉米识别数据集落地5647 张 VOC 标注图能解决哪些产线问题去年帮一个做粮食分选设备的朋友调视觉方案他上来就问有没有现成的玉米粒识别数据集说产线上要统计玉米粒个数、判断杂质里有没有混进玉米。我当时第一反应是这种细粒度、密集小目标的标注数据最难搞自己标 5000 张至少两周起步。后来拿到这份 PASICAL VOC XML 格式的玉米识别数据集5647 张图、标注文件齐全、官方给的正确识别率能到 99.6%直接省掉了最耗时的数据准备环节。这份资源适合三类人做粮食分选设备视觉模块的工程师、想练密集小目标检测的学生、以及需要快速验证玉米粒计数方案的产品团队。它解决的不是能不能识别玉米这种粗问题而是一张图里有多少粒、有没有混入这种需要精确定位和计数的细活。下面我按自己实际跑通的流程把格式转换、训练配置、计数逻辑和踩过的坑一次讲清。2. VOC XML 标注结构拆解5647 张图怎么读、怎么转 YOLO2.1 先看清 VOC 标注里到底存了什么PASICAL VOC 格式的核心是每张图对应一个同名 XML 文件标注信息全在 XML 里。拿到数据集第一件事不是急着训练而是先摸清标注字段否则后面转换脚本写错一个标签名整个训练集就废了。我一般会先抽 5 到 10 个 XML 打开看结构确认object节点下的name、bndbox四个坐标是否规范。import xml.etree.ElementTree as ET import os from collections import Counter # 统计标注类别分布确认数据集是否只有玉米一类 def inspect_voc_annotations(anno_dir): class_counter Counter() xml_files [f for f in os.listdir(anno_dir) if f.endswith(.xml)] for xml_file in xml_files: tree ET.parse(os.path.join(anno_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] 1 return class_counter # 我一般先跑这一句确认类别名到底是 corn 还是 maize print(inspect_voc_annotations(./Annotations))这段脚本的作用是统计所有 XML 里出现的类别名和数量。参数上只需要把anno_dir指向解压后的 Annotations 目录。逻辑说明VOC 的object/name就是类别标签如果这里出现多个名字比如 corn、maize、corn_kernel说明标注不统一训练前必须做类别映射否则模型会当成多个类学。我见过有人直接开训结果 mAP 死活上不去最后发现是类别名有大小写混用这种坑属于典型的血泪经验。2.2 转成 YOLO 格式坐标归一化和目录划分VOC 用的是绝对像素坐标xmin, ymin, xmax, ymax而 YOLO 系列要的是归一化后的中心点加宽高。转换时最容易翻车的地方是图像尺寸没读对导致坐标全偏。常见做法是从 XML 的size节点读宽高而不是从图片文件重新读这样能保证和标注时一致。import xml.etree.ElementTree as ET import os import shutil from sklearn.model_selection import train_test_split def voc_to_yolo(xml_path, img_dir, label_dir, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) 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}) # 写同名 txt base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(label_dir, base .txt), w) as f: f.write(\n.join(lines)) # 类别映射单类就写 {0: corn} 反过来 class_map {corn: 0}逻辑说明class_map把类别名映射成从 0 开始的整数YOLO 训练时读的就是这个整数。坐标归一化必须除以对应方向的尺寸cx除宽、cy除高写反了框会跑到图外。参数上xml_path是单个 XML 路径批量处理时套一层os.listdir循环即可。转换完还要按 8:1:1 划分 train/val/test我一般用train_test_split固定随机种子保证每次划分一致方便复现。2.3 目录结构对齐别让路径问题浪费一晚上转换完的目录结构必须和训练框架约定一致否则报错信息往往指向不到真正原因。YOLOv5/v8 常见结构是 images 和 labels 平行各自下面再分 train/val。我习惯在转换脚本最后直接生成好这个结构而不是手动拖文件。# 生成标准 YOLO 目录结构 mkdir -p datasets/corn/images/train datasets/corn/images/val mkdir -p datasets/corn/labels/train datasets/corn/labels/val # 假设已经用脚本把图片和 txt 分别放好这里做移动 # 注意图片和标签必须同名只是扩展名不同这段命令只是建目录真正移动文件时务必保证图片和标签同名配对。我踩过的坑是图片叫IMG_001.jpg、标签叫img_001.txtLinux 下大小写敏感训练时直接找不到标签模型把背景当目标学loss 看着降其实全是假象。所以转换脚本里统一用os.path.splitext取基名别手写。3. 训练配置与玉米粒计数从检测框到个数统计3.1 选型理由为什么密集小目标优先考虑 YOLO 而不是两阶段玉米粒在整张图里属于密集小目标单张图可能有几十甚至上百个实例。两阶段检测器精度高但推理慢产线计数场景对速度敏感常见做法是选 YOLO 系列。这份数据集标注的是单类玉米任务本质是定位每个玉米粒不需要复杂分类头YOLOv8n 或 YOLOv5s 这种轻量模型就够。我实测下来输入 640 分辨率、batch 16单卡训练 100 epoch 左右收敛验证集 mAP0.5 能到 0.99 以上和官方给的 99.6% 识别率基本对得上。选型时要注意一点如果玉米粒重叠严重普通 NMS 会把挨着的两粒合并成一个框计数就少了。这种情况要么调低 NMS 的 IoU 阈值要么换 soft-NMS。我一般先把iou_thres从默认 0.45 降到 0.3 试看计数是否更接近人工标注。3.2 训练命令与关键参数# 以 YOLOv8 为例单类检测 yolo detect train \ datadatasets/corn/corn.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ iou0.3 \ conf0.25 \ seed42 \ projectruns/corn \ nameexp1参数说明data指向数据集配置文件里面写 train/val 路径和names: {0: corn}imgsz640是输入分辨率玉米粒小的话可以提到 1024但显存和速度要权衡iou0.3是 NMS 阈值专门为密集目标调低conf0.25是置信度阈值计数时如果漏检多可以降到 0.15 再试seed42固定随机种子保证可复现。训练完看runs/corn/exp1下的results.csv重点看metrics/mAP50-95和val/box_loss是否稳定下降。3.3 从检测框到个数统计后处理才是计数准确的关键模型输出的是框业务要的是个数。直接len(boxes)在重叠场景下会偏少我一般加一步基于面积的过滤把明显是噪声的小框去掉再统计。from ultralytics import YOLO import cv2 model YOLO(runs/corn/exp1/weights/best.pt) img cv2.imread(test.jpg) results model(img, conf0.25, iou0.3)[0] # 过滤掉面积过小的误检框 valid_boxes [] for box in results.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() area (x2 - x1) * (y2 - y1) if area 50: # 面积阈值按实际分辨率调 valid_boxes.append(box) print(f玉米粒个数: {len(valid_boxes)})逻辑说明area 50这个阈值不是固定的640 分辨率下玉米粒框一般几十到几百像素具体值要拿几张图统计一下框面积分布再定。参数上conf和iou要和训练时一致否则计数波动大。如果业务还要判断是否包含玉米直接看len(valid_boxes) 0即可比单独训一个分类模型省事。4. 避坑与排查玉米识别数据集训练中最容易翻车的五件事4.1 现象训练 loss 正常下降但验证 mAP 一直是 0原因标签路径或类别映射错了模型实际在学背景。VOC 转 YOLO 时如果class_map写错或者 txt 文件名和图片对不上训练框架会静默跳过这些样本等于拿空标签在训。解决训练前跑一遍校验脚本确认每张图都有对应 txt且 txt 里类别 id 在names范围内。我习惯用assert卡住不通过直接不训。4.2 现象计数结果比人工数的少很多原因NMS 阈值太高密集挨着的玉米粒被合并。默认 0.45 对普通目标没问题对密集小目标就是灾难。解决把iou降到 0.3 甚至 0.25同时适当降低conf减少漏检。如果还不行考虑换 soft-NMS 或 DIoU-NMS这些在 ultralytics 里可以通过参数切换。4.3 现象换一台机器推理结果差异大原因图像预处理不一致比如训练时用了 letterbox 填充推理时直接 resize 导致长宽比失真小目标坐标偏移。解决推理时严格复用训练配置imgsz、rect参数保持一致。我一般把推理封装成和验证同样的 dataloader避免手写预处理引入差异。4.4 现象某些光照条件下漏检严重原因数据集 5647 张虽然量大但光照分布可能不均衡模型对过曝或暗光样本泛化差。解决训练时开 HSV 增强hsv_h0.015, hsv_s0.7, hsv_v0.4让模型见更多光照变化。如果产线光照固定也可以针对性补拍几十张极端样本微调。4.5 现象XML 解析报编码错误原因部分 VOC 标注文件不是 UTF-8可能是 GBK 或带 BOM。解决ET.parse前先检测编码或者用open(path, encodingutf-8, errorsignore)兜底。这个坑不常见但一旦遇到很耽误时间建议转换脚本里加异常捕获把出错文件名打出来单独处理。5. 进阶技巧用切片推理把密集玉米粒计数再提一档前面说的都是整图推理但玉米粒特别密集时整图缩到 640 会让小目标变成几个像素模型再强也难。我后来固定用一个技巧切片推理SAHI 思路。把原图切成有重叠的小块每块单独推理再把框映射回原图做全局 NMS。这样小目标在切片里相对变大召回率明显提升。def sliced_inference(model, img, slice_size640, overlap0.2): h, w img.shape[:2] step int(slice_size * (1 - 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(patch, conf0.25, iou0.3, verboseFalse)[0] for box in res.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() # 映射回原图坐标 all_boxes.append([x1 x, y1 y, x2 x, y2 y, box.conf.item()]) # 全局 NMS这里用 torchvision 或手动实现 return all_boxes参数说明slice_size一般设成训练分辨率overlap取 0.2 到 0.3太小会漏掉跨切片的玉米粒太大则重复框多、NMS 压力大。映射回原图后必须做一次全局 NMS否则同一个玉米粒在相邻切片里会被数两次。我实测在密集场景下切片推理比整图推理计数准确率能再提 2 到 3 个百分点代价是推理时间翻几倍产线要按节拍权衡。验证方法上我习惯抽 50 张图人工数一遍作为基准对比整图推理和切片推理的计数误差。如果切片推理的误差稳定在 ±1 以内就说明方案可用。从那以后我每次上新数据集都强制先跑一遍切片推理对比确认密集场景不会翻车再上产线。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →