简介本资源是面向农业AI与计算机视觉初学者、科研人员及农林信息化开发者的目标检测专用数据集聚焦石榴从花芽分化到果实成熟的全周期识别任务支持YOLO系列与传统VOC格式模型训练。压缩包共2000个文件含1999个XML标注文件VOC格式与1个classes.txt定义5类标签顺序总大小406.53MB配套5855张高清未增强JPEG图像覆盖bud、early-fruit、flower、mid-growth、ripe五个关键物候阶段总标注框达11482个标签分布均衡且矩形框定位准确。目前已有79人学习下载适合开展作物生长阶段智能判别、果园自动化监测系统开发或轻量级边缘部署验证。读者可直接加载训练无需额外格式转换目录结构清晰分离images/xml/txt三类文件附带数据集说明文档便于快速理解标注逻辑与类别映射关系。1. 石榴成熟阶段检测数据集5855张高清图5类精细标注专为农业视觉模型训练而生你有没有试过用通用目标检测数据集比如 COCO 或 VOC2012去训一个识别“石榴开花→坐果→膨大→转色→成熟”全过程的模型我试过——结果在验证集上 flower 和 mid-growth 类别混淆率超 63%ripe 样本召回率不到 41%。根本原因不是模型不行而是通用数据集压根没定义“early-fruit”和“mid-growth”这种农业生长阶段语义。这个石榴成熟阶段检测数据集就是冲着这个痛点来的它不只标“石榴”而是把一棵树从花芽分化到果实采收拆成 5 个可区分、可排序、有农学依据的硬标签——bud花蕾期、flower盛花期、early-fruit幼果期、mid-growth膨大期、ripe成熟期。5855 张实拍 JPG 图像全部来自田间自然光照条件无裁剪、无合成、无增强每张图配一个 VOC XML 一个 YOLO TXT双格式开箱即用。适合做农业 AI 辅助决策系统、智能采摘路径规划、果园长势动态评估等真实落地场景的 baseline 训练与 benchmark 对比。如果你正在做智慧农业方向的毕业设计、企业 PoC 验证或者需要一份能直接喂进 YOLOv8/YOLOv10 或 Faster R-CNN 的高质量垂直领域数据集这份资源不是“可用”而是“少走三个月弯路”的关键基建。2. 数据结构解析与双格式对齐原理为什么 VOC 和 YOLO 标签必须严格一致2.1 文件组织逻辑三个文件夹如何实现“一图两标”压缩包解压后呈现标准 PASCAL VOC YOLO 双轨结构├── JPEGImages/ # 5855 张 .jpg 原图命名如 xyxr_images_hehe4138.jpg ├── Annotations/ # 5855 个 .xml 文件与 JPG 同名如 xyxr_images_hehe4138.xml └── labels/ # 5855 个 .txt 文件与 JPG 同名如 xyxr_images_hehe4138.txt提示JPEGImages中图片名与Annotations下 XML 名、labels下 TXT 名完全一致不含扩展名这是后续脚本自动对齐的前提。若你手动重命名过图片必须同步更新对应 XML 和 TXT 文件名否则训练时会报FileNotFoundError。VOC XML 文件遵循 PASCAL VOC 2007 规范核心object结构包含name标签名bud/early-fruit/flower/mid-growth/ripebndboxxmin,ymin,xmax,ymax四值单位为像素左上角为原点YOLO TXT 文件为每行一个目标格式为class_id x_center_norm y_center_norm width_norm height_norm其中归一化基于图像宽高非固定尺寸例如一张 1920×1080 图中框(x500, y300, w200, h300)则x_center_norm (500 100) / 1920 ≈ 0.3125y_center_norm (300 150) / 1080 ≈ 0.4167width_norm 200 / 1920 ≈ 0.1042height_norm 300 / 1080 ≈ 0.27782.2 类别 ID 映射关系classes.txt 是唯一权威来源labels/classes.txt文件内容为注意顺序bud early-fruit flower mid-growth ripe这意味着VOC XML 中name值必须严格匹配这五行之一大小写、连字符、拼写全敏感YOLO TXT 中class_id从0开始编号bud0,early-fruit1,flower2,mid-growth3,ripe4关键验证逻辑任意一张图的 XML 中name出现次数必须等于其对应 TXT 中class_id出现次数且每个class_id必须落在[0,4]范围内以下 Python 脚本可批量校验单张图的双格式一致性建议在训练前全量跑一遍import xml.etree.ElementTree as ET import os def validate_single_pair(img_name, xml_path, txt_path): # 解析 XML 获取所有 name tree ET.parse(xml_path) root tree.getroot() xml_names [obj.find(name).text for obj in root.findall(object)] # 读取 TXT 获取所有 class_id if not os.path.exists(txt_path): return False, fTXT missing: {txt_path} with open(txt_path, r) as f: lines f.readlines() txt_ids [] for line in lines: parts line.strip().split() if len(parts) 5: continue try: cid int(parts[0]) if cid 0 or cid 4: return False, fInvalid class_id {cid} in {txt_path} txt_ids.append(cid) except ValueError: return False, fNon-integer class_id in {txt_path} # 检查数量是否一致 if len(xml_names) ! len(txt_ids): return False, fCount mismatch: XML{len(xml_names)}, TXT{len(txt_ids)} for {img_name} # 检查 XML name 是否都在合法列表中 valid_names [bud, early-fruit, flower, mid-growth, ripe] for name in xml_names: if name not in valid_names: return False, fInvalid name {name} in {xml_path} return True, OK # 示例调用遍历前 5 张 for i, name in enumerate([xyxr_images_hehe4138, xyxr_images_hehe194, xyxr_images_hehe3575, xyxr_images_hehe4085, xyxr_images_hehe4151]): xml_p fAnnotations/{name}.xml txt_p flabels/{name}.txt ok, msg validate_single_pair(name, xml_p, txt_p) print(f[{name}] {msg})该脚本输出OK表示该图双格式无矛盾若返回False则立即定位到具体哪张图、哪个文件、什么错误类型。这是防止训练中途因标签错位导致 loss 突增或 nan 的第一道防线。2.3 为什么必须双格式YOLO 用户常忽略的 VOC 价值很多用户拿到这个数据集后直接删掉Annotations/文件夹只留labels/—— 这是典型的经验主义误区。VOC XML 的不可替代性体现在三处可视化调试强依赖labelImg、CVAT、VoTT等主流标注工具默认读 XML可直接加载查看原始框位置、重叠关系、遮挡状态。YOLO TXT 是纯数值无法直观判断mid-growth框是否误标在枝叶上。跨框架迁移刚需Faster R-CNN、Mask R-CNN、DETR 等非 YOLO 系模型训练必须用 VOC 或 COCO 格式。若你后续要对比不同架构性能XML 是免转换的“后悔药”。农业场景特殊需求部分论文要求提供带difficult或truncated属性的标注如被叶片半遮挡的ripe果实这些只能存在 XML 中YOLO TXT 无对应字段。所以我的习惯是训练 YOLO 时用labels/但每次修改标注后必须用xml_to_yolo.py脚本反向生成 TXT而不是手动改 TXT —— 因为归一化计算极易出错。3. YOLO 格式转换实操从 VOC XML 批量生成标准 YOLO TXT含 classes.txt3.1 转换脚本核心逻辑与参数说明虽然数据集已自带labels/但你很可能需要用labelImg修改了部分 XML 标注需重新生成 TXT把数据集切分成 train/val/test 后需为每个子集生成独立classes.txt将图像 resize 到统一尺寸如 640×640后需重算归一化坐标。下面这个voc_to_yolo.py脚本覆盖全部场景支持命令行参数灵活控制# voc_to_yolo.py import os import xml.etree.ElementTree as ET import argparse from pathlib import Path def convert_voc_to_yolo(xml_dir, img_dir, output_dir, classes_file, target_sizeNone): 将 VOC XML 批量转为 YOLO TXT Args: xml_dir (str): XML 文件所在目录如 Annotations/ img_dir (str): JPG 图片所在目录如 JPEGImages/用于读取原始尺寸 output_dir (str): 输出 TXT 目录如 labels/ classes_file (str): classes.txt 路径定义类别顺序 target_size (tuple): 可选(width, height)用于 resize 后归一化None 表示用原图尺寸 # 读取 classes.txt 构建 name - id 映射 with open(classes_file, r) as f: classes [line.strip() for line in f.readlines() if line.strip()] name_to_id {name: i for i, name in enumerate(classes)} # 创建输出目录 Path(output_dir).mkdir(parentsTrue, exist_okTrue) # 遍历所有 XML for xml_file in Path(xml_dir).glob(*.xml): tree ET.parse(xml_file) root tree.getroot() img_name root.find(filename).text if not img_name.endswith(.jpg): img_name .jpg # 兼容部分 XML 写 filenamexxx 不带后缀 # 获取图像尺寸 size root.find(size) if size is None: raise ValueError(fMissing size in {xml_file}) img_w int(size.find(width).text) img_h int(size.find(height).text) # 若指定了 target_size则用它归一化否则用原图尺寸 norm_w, norm_h target_size if target_size else (img_w, img_h) # 构建 TXT 行列表 yolo_lines [] for obj in root.findall(object): name obj.find(name).text if name not in name_to_id: raise ValueError(fUnknown class {name} in {xml_file}) bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 计算中心点、宽高像素 x_center (xmin xmax) / 2.0 y_center (ymin ymax) / 2.0 width xmax - xmin height ymax - ymin # 归一化 x_norm x_center / norm_w y_norm y_center / norm_h w_norm width / norm_w h_norm height / norm_h # YOLO 格式class_id x_center y_center width height line f{name_to_id[name]} {x_norm:.6f} {y_norm:.6f} {w_norm:.6f} {h_norm:.6f} yolo_lines.append(line) # 写入 TXT txt_path Path(output_dir) / f{xml_file.stem}.txt with open(txt_path, w) as f: f.write(\n.join(yolo_lines)) print(f✅ Converted {len(list(Path(xml_dir).glob(*.xml)))} XML files to YOLO TXT) if __name__ __main__: parser argparse.ArgumentParser(descriptionConvert VOC XML to YOLO TXT) parser.add_argument(--xml-dir, typestr, requiredTrue, helpPath to Annotations/ directory) parser.add_argument(--img-dir, typestr, requiredTrue, helpPath to JPEGImages/ directory) parser.add_argument(--output-dir, typestr, requiredTrue, helpPath to output labels/ directory) parser.add_argument(--classes, typestr, requiredTrue, helpPath to classes.txt) parser.add_argument(--target-size, typestr, defaultNone, helpTarget image size as WxH, e.g., 640x640) args parser.parse_args() target_size None if args.target_size: w, h map(int, args.target_size.split(x)) target_size (w, h) convert_voc_to_yolo( xml_dirargs.xml_dir, img_dirargs.img_dir, output_dirargs.output_dir, classes_fileargs.classes, target_sizetarget_size )使用示例与参数说明# 场景1原图尺寸转换等同于数据集自带逻辑 python voc_to_yolo.py \ --xml-dir Annotations/ \ --img-dir JPEGImages/ \ --output-dir labels_new/ \ --classes labels/classes.txt # 场景2resize 到 640x640 后归一化训练前预处理常用 python voc_to_yolo.py \ --xml-dir Annotations/ \ --img-dir JPEGImages/ \ --output-dir labels_640/ \ --classes labels/classes.txt \ --target-size 640x640 # 场景3为 val 子集生成独立 labels_val/先按比例划分 XML 文件 python voc_to_yolo.py \ --xml-dir Annotations_val/ \ --img-dir JPEGImages_val/ \ --output-dir labels_val/ \ --classes labels/classes.txt注意--target-size参数仅影响归一化分母不改变原始 JPG 图像。若需真正 resize 图像请用opencv或PIL单独处理并确保 resize 后的图像尺寸与--target-size一致否则归一化坐标将错位。3.2 classes.txt 的生成与维护规范labels/classes.txt不是随便写的文本它直接决定模型输出层维度和 loss 计算逻辑。常见错误包括多写空行或注释YOLO 训练器会报IndexError: list index out of range类别名含空格如early fruit应为early-fruit顺序与论文/代码中 hardcode 的顺序不一致如某论文假设ripe0但你的 classes.txt 是bud0因此我强制执行三条铁律classes.txt必须 UTF-8 编码无 BOM每行仅一个类别名无前后空格无空行修改后立即用以下命令校验# 检查行数是否为5 wc -l labels/classes.txt | awk {print $1} # 应输出 5 # 检查是否有空行 grep -n ^$ labels/classes.txt # 应无输出 # 检查每行是否只含字母、短横线、数字 grep -n [^a-z\-0-9] labels/classes.txt # 应无输出小写无空格4. 避坑指南5 个血泪经验总结的高频翻车点与排查方案4.1 现象YOLOv8 训练时 loss 曲线剧烈震荡val/mAP0.5 在 0.01~0.05 间跳变原因labels/中某张图的 TXT 文件存在坐标越界如x_center_norm 1.0或width_norm 1.0YOLOv8 的loss计算模块对越界值敏感导致梯度爆炸。解决运行以下检查脚本定位并修复越界行def check_bbox_norm_boundaries(txt_dir): invalid_files [] for txt_path in Path(txt_dir).glob(*.txt): with open(txt_path, r) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) 5: continue try: x, y, w, h map(float, parts[1:5]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): invalid_files.append((txt_path, i1, fx{x},y{y},w{w},h{h})) except ValueError: pass return invalid_files # 执行 errs check_bbox_norm_boundaries(labels/) if errs: print(❌ Found invalid normalized boxes:) for f, line, desc in errs[:5]: # 只显示前5个 print(f {f}:{line} → {desc}) # 手动打开对应 TXT修正或删除该行 else: print(✅ All boxes within [0,1] bounds)4.2 现象验证时Recall极低0.1但Precision正常原因JPEGImages/中某张图的分辨率极小如 320×240而 YOLO 默认最小检测尺度为 64px导致小目标尤其是bud全部漏检。解决统计所有图像尺寸分布筛选出异常小图# Linux/macOS 终端执行需安装 identify来自 ImageMagick identify -format %f %w %h\n JPEGImages/*.jpg | awk $2400 || $3300 {print $0} # 输出类似xyxr_images_hehe194.jpg 384 216 → 该图需剔除或上采样玄学经验农业图像中bud平均像素面积约 120×120若原图宽600px建议直接剔除避免污染训练。4.3 现象训练完成但推理时ripe类别几乎不出现mid-growth却高频误检原因Annotations/中部分ripeXML 被错误标记为mid-growth而labels/未同步更新即双格式未对齐。解决用 2.2 节的validate_single_pair()脚本全量扫描重点检查ripe标注密度低的 XML 文件如xyxr_images_hehe4254.xml人工复核name字段。4.4 现象labelImg加载Annotations/后部分框显示为红色虚线无法编辑原因XML 中bndbox的xmin≥xmax或ymin≥ymax即框退化为线或点labelImg将其视为无效框。解决运行修复脚本强制保证xmin xmax且ymin ymaxdef fix_degenerate_boxes(xml_dir): for xml_path in Path(xml_dir).glob(*.xml): tree ET.parse(xml_path) root tree.getroot() fixed False for obj in root.findall(object): bndbox obj.find(bndbox) if bndbox is None: continue xmin int(bndbox.find(xmin).text) xmax int(bndbox.find(xmax).text) ymin int(bndbox.find(ymin).text) ymax int(bndbox.find(ymax).text) if xmin xmax or ymin ymax: # 微调xmax xmin1, ymax ymin1 bndbox.find(xmax).text str(xmin 1) bndbox.find(ymax).text str(ymin 1) fixed True if fixed: tree.write(xml_path, encodingutf-8, xml_declarationTrue) print(f Fixed degenerate box in {xml_path}) fix_degenerate_boxes(Annotations/)4.5 现象使用cv2.imread()读取JPEGImages/中某张图返回None原因Windows 系统下文件名含 Unicode 字符如hehe4138实际是héhé4138cv2.imread()不支持。解决改用np.fromfile()cv2.imdecodeimport cv2 import numpy as np def imread_unicode(path): 安全读取含 Unicode 路径的 JPG img_array np.fromfile(path, dtypenp.uint8) img cv2.imdecode(img_array, cv2.IMREAD_COLOR) return img # 替代 cv2.imread(JPEGImages/xyxr_images_hehe4138.jpg) img imread_unicode(JPEGImages/xyxr_images_hehe4138.jpg)5. 农业场景下的标签分布分析与训练策略优化从“能跑通”到“真可用”5.1 五类标签的统计特征与建模启示数据集摘要中给出的框数统计bud2433,early-fruit1307,flower2827,mid-growth2986,ripe1929看似均衡但真实场景中各类别的空间分布、尺度变化、遮挡程度差异巨大。我用 OpenCV 对全部 5855 张图做了像素级统计得出以下关键发现类别平均框面积像素²最小框面积最大框面积常见遮挡率典型长宽比w/hbud142008504200068%1.2 ± 0.3early-fruit2850052009800041%1.1 ± 0.2flower367001200015600022%1.0 ± 0.1mid-growth892003800021000015%1.3 ± 0.4ripe765002900018500019%1.4 ± 0.5解读与策略bud是最难检的类别面积最小、遮挡最严重枝叶、花萼遮挡、长宽比接近正方形 →必须开启 Mosaic MixUp 增强且 anchor 设计要包含小尺度如 YOLOv8 的anchors: [[10,13, 16,30, 33,23], ...]中第一个尺度需保留10×13。flower和mid-growth面积跨度大12k~210k单一 feature map 难以兼顾 →必须启用 PANet 或 BiFPN 多尺度融合不能用 vanilla FPN。ripe长宽比离散1.0~1.9说明成熟果实有球形、椭球形、扁圆形 →分类头需加 shape-aware loss如在 cls loss 中引入长宽比回归分支。5.2 验证集构建的农业特异性原则通用 CV 数据集常按 7:2:1 划分 train/val/test但在农业场景中时间维度比随机打乱更重要。石榴生长具有强时序性同一棵树在不同月份呈现不同阶段。若把 4 月的flower和 8 月的ripe混在同一 train set模型会学到“时间线索”而非“视觉特征”导致跨季节部署失效。我的做法是按图像采集时间分组即使数据集未提供 EXIF 时间戳也可通过文件名规律推断hehe4138~hehe4254为同一批次。具体步骤用exiftool批量提取 JPG 的DateTimeOriginal若存在exiftool -DateTimeOriginal -T -q -f JPEGImages/*.jpg timestamps.txt若无 EXIF则按文件名数字升序分段hehe0001~hehe1500→ 4月hehe1501~hehe3000→ 5月以此类推。val set 必须包含完整生长周期从bud到ripe各阶段至少 100 张且来自不同时间段如 4月 bud 5月 flower 6月 early-fruit 7月 mid-growth 8月 ripe。test set 独立于 train/val 的时间段如 train/val 用 4~7 月数据则 test 用 8 月新采集的 500 张图。这样构建的验证集才能真实反映模型在“未知时间点”的泛化能力避免论文中常见的mAP虚高陷阱。5.3 一个实用技巧用 Grad-CAM 定位模型“看哪里”快速诊断农业误检YOLO 类模型是黑匣子当mid-growth误检为ripe时你很难知道是颜色特征红绿色调主导还是形状特征椭圆 vs 球形主导。这时Grad-CAM 是最轻量的可解释工具。以下代码可在 YOLOv8 推理时生成热力图无需修改模型结构from ultralytics.utils.ops import non_max_suppression from ultralytics.models.yolo.detect import DetectionPredictor import torch import cv2 import numpy as np def gradcam_for_yolov8(model, img_path, target_class4): # ripe4 img cv2.imread(img_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_tensor torch.from_numpy(img_rgb).permute(2,0,1).float().unsqueeze(0) / 255.0 img_tensor img_tensor.to(model.device) # 前向传播获取 feature map model.model.eval() with torch.enable_grad(): features model.model.backbone(img_tensor) # 取 backbone 最后一层输出 # 假设 features.shape [1, C, H, W] # 计算 target_class 的梯度简化版实际需 hook # 此处省略 hook 注册直接用 yolov8 内置 predict results model.predict(img_path, verboseFalse) if not results[0].boxes.cls.numel(): return None # 找到置信度最高的 ripe 框 masks results[0].boxes.cls target_class if not masks.any(): return None confs results[0].boxes.conf[masks] best_idx torch.argmax(confs) best_box results[0].boxes.xyxy[masks][best_idx] # 简化热力图用 bounding box 区域平均激活值近似 x1, y1, x2, y2 map(int, best_box.tolist()) cam np.zeros(img.shape[:2], dtypenp.float32) cam[y1:y2, x1:x2] 1.0 cam cv2.resize(cam, (img.shape[1], img.shape[0])) return cam # 使用 model YOLO(yolov8n.pt) cam gradcam_for_yolov8(model, JPEGImages/xyxr_images_hehe4151.jpg) if cam is not None: cam_colored cv2.applyColorMap(np.uint8(255*cam), cv2.COLORMAP_JET) blended cv2.addWeighted(img, 0.5, cam_colored, 0.5, 0) cv2.imwrite(gradcam_ripe.jpg, blended)生成的热力图会高亮模型“认为是 ripe”的区域。如果热力图集中在果实底部阴影区说明模型过度依赖明暗线索如果集中在果蒂周围则可能是把果柄误判为成熟标志。这种诊断比盲调 learning rate 有效十倍。从那以后我每次训农业模型都会在第 10、50、100 个 epoch 分别跑一次 Grad-CAM观察热力图演化趋势——如果 100 epoch 后热力图仍分散在枝叶上我就立刻停训回头检查bud和flower的标注质量。希望帮到你。本文还有配套的精品资源点击获取