简介:数据来自葡萄不同生长阶段,覆盖发育进程中的多种外观形态,可用于训练生长阶段识别或果实检测模型。v0.1 含 1212 张 1280×720 图片、3993 个边界框;v0.2 增至 2099 张图片、6641 个边界框,两版分辨率一致,便于复用现有模型输入尺寸,也能对比数据规模对效果的影响。压缩包共 2000 个文件,整体约 314.1MB,主要包含 JPG 原图、TXT 标注文本和 XML 标注文件;其中 JPG 为原始图像,TXT 与 XML 记录目标框信息,便于转入 YOLO 或 VOC 等常用检测框架。已有 180 人学习下载。样本以唯一编号命名,图像与标注可快速配对,适合训练集/验证集划分;对深度学习初学者而言,可直接用于练习数据预处理、目标检测训练与结果评估,也可为葡萄长势分析、果园智能监测提供数据基础;已有检测基础的开发者还可先用 v0.1 快速跑通流程,再用 v0.2 验证模型鲁棒性。
1. 葡萄在不同生长阶段的图像数据集:先搞清楚“长到哪了”,再谈估产和病害预警
做果园视觉的人都会碰到一个尴尬:网上搜“葡萄图像”,出来一大片成熟果穗的精修图,真要拿来训练一个“分清葡萄当前长到哪个阶段”的模型,数据立刻成了瓶颈。这个标题所指的图像数据集,不是随手拍的相册,而是严格按生长阶段组织、统一拍摄条件、带明确标注的作物图像数据集。它和常见的病害图像数据集有本质区别——病害数据集聚焦病斑特写,而这个数据集要回答的是“时间序列上的形态变化”。它能直接支撑三类任务:生长阶段分类、果穗检测与计数、基于物候期的农事决策。适合谁?做植物表型分析的算法工程师、果园数字化团队、以及想用视觉模型替代人工巡园的一线农技人员。
2. 生长阶段怎么定:用 BBCH 编码把九个阶段钉死
图像数据集的灵魂不是像素,是标签体系。葡萄在不同生长阶段的划分,农学上有一套现成的通用语言——BBCH 编码。这套体系把葡萄生命周期拆成 0 到 9 共十个主阶段,从芽萌发一路走到休眠,每个阶段都有明确的形态学判据。做图像数据集如果不先定这套标准,标注员会在同一张图上吵起来。
2.1 为什么选 BBCH:果园拍照的“公共语言”
BBCH 是欧洲植物保护组织推广的物候学编码标准,广泛用于小麦、玉米、果树和葡萄。它的好处有三点:第一,阶段边界清晰,每个主阶段都能对应到一个肉眼可辨的形态事件;第二,不是葡萄领域自造的分类,后续想和气象数据、农事记录合并分析时,能直接对上数据库里的物候字段;第三,论文和农业软件里普遍使用,你标注出来的数据集别人拿来就能用,不存在“这个阶段你叫花期我叫开花期”的语义混乱。
实际采集时,我一般不会把 9 个休眠阶段收进数据集。原因很直接——落叶后的光杆枝条在视觉上几乎没有辨识度,模型学不到可迁移的特征,徒增背景噪声。数据集覆盖 0 到 8 阶段即可。
2.2 九个阶段的视觉判据:从芽绒到果穗成熟
光有编码不够,标注员需要知道“每个阶段在图里长什么样”。这九个阶段的形态差异不是均匀分布的——有些阶段隔两天就大变样,有些阶段能持续两周却没明显变化。做标注规范时,我会给每个阶段配上三句话:一句描述整体形态、一句点出最关键的判别部位、一句说明最容易和相邻阶段混淆的点。
下面这个表是标注规范的核心,也是所有标注员共用的一套“看图说话”标准:
| BBCH 主阶段 | 阶段名 | 图像中的视觉判据 | 拍摄重点 |
|---|---|---|---|
| 0 | 芽萌发 | 芽鳞裂开,绿色芽尖或绒球状结构露出 | 枝蔓节位特写 |
| 1 | 叶发育 | 第一片叶平展,叶面积明显增大 | 新梢顶部俯视角 |
| 3 | 花序出现 | 花穗在叶间可见,小穗紧密抱合 | 新梢中段侧视 |
| 5 | 开花 | 花帽脱落,可见花瓣掉落痕迹 | 花穗整体与特写 |
| 6 | 果实膨大 | 果粒如绿豆至黄豆,果串下垂 | 果穗侧面 45 度 |
| 7 | 转色 | 红品种果粒变红,白品种变透明 | 果穗与背景叶对比 |
| 8 | 成熟 | 果粒颜色稳定,可采收 | 果穗全貌与果粒细节 |
我给每个阶段配了“最小可分辨特征”——比如开花期你优先看花帽脱落,而不是数花瓣。这样即使两个标注员水平不同,看到的关键部位一致,标注就不会跑偏。
2.3 用代码把阶段字典定下来
标注规范要落地,不能只写文档,得变成代码里的字典。这个字典是后续所有脚本的“金标准”,数据整理、标签检查、模型类别映射都从它出发。
GROWTH_STAGES = { 0: {"name": "budbreak", "cn_name": "芽萌发", "discriminant": "芽鳞裂开、绿色芽尖外露"}, 1: {"name": "leaf_development", "cn_name": "叶发育", "discriminant": "第一片叶平展、新梢伸长"}, 3: {"name": "inflorescence_emergence", "cn_name": "花序出现", "discriminant": "花穗可见、小穗簇生"}, 5: {"name": "flowering", "cn_name": "开花", "discriminant": "花帽脱落、可见落花痕迹"}, 6: {"name": "berry_swelling", "cn_name": "果实膨大", "discriminant": "果粒黄豆大小、果串下垂"}, 7: {"name": "veraison", "cn_name": "转色", "discriminant": "红品种转红/白品种转透明"}, 8: {"name": "maturity", "cn_name": "成熟", "discriminant": "果粒颜色稳定、果梗木质化"}, }这段字典的作用是让“阶段”从自然语言变成程序可用的枚举。注意我把开花期放在 5、果实膨大放在 6,中间跳过了 2、4 和 9——2 和 4 是过渡性细分,图像上区分度太低,收集了只会让模型学到噪声;9 是休眠期,对农事决策没有价值。类别定义里最关键的是discriminant字段,它在后面做标签复核时会被自动读取,作为人工检查的提示语。
3. 数据采集与整理:拍多少、怎么拍、目录怎么组织
标签体系定了,接下来是数据集建设的体力活。很多团队在这个环节翻车,不是因为算法不行,而是因为拍回来的图没法用。图像数据集和工业图像数据集不一样,它在自然光照下拍摄,环境变量完全不可控,采集策略决定了数据集的天花板。
3.1 先算账:一个能训练的数据集要拍多少张
先别急着架相机,把数量账算清楚。对于生长阶段分类任务,每个阶段至少 300 到 500 张图,七个阶段共需要 2000 到 3500 张;如果还要做果穗检测,建议每个阶段翻倍,因为检测任务需要覆盖不同角度、不同距离、不同遮挡程度。算上筛选时剔除的模糊和过曝图,实际拍摄量要按目标量的 1.5 倍准备。
这里有个常见误区:同一株葡萄每隔三天拍一张,拍十次就以为有十张不同样本。错,这是同一株同一果穗的时间序列,不是独立样本。模型会把背景的枝条纹理当作特征,换一株葡萄就失效。独立样本的定义是:不同植株、不同位置、不同朝向的果穗。采集时一定要“打一枪换一个地方”,而不是蹲在同一棵树下按快门。
3.2 拍摄设备与选型:手机也能用,但有三条底线
设备选择上,一台 1200 万像素以上的主流手机完全够用,前提是满足三条底线。第一,图像不得压缩过度,关闭省电模式下的“智能压缩”,保证长边不低于 2000 像素;第二,不得开美颜或任何场景增强滤镜,这会改变果粒的真实颜色分布,直接影响转色期的分类;第三,拍摄时保持果穗在画面中占比超过三分之一,远景图留作辅助数据,不作为主样本。
如果预算充足,可以用一台带 GPS 和光谱传感器的工业相机做补充采集。GPS 能记录株行位置,后续和产量数据做空间分析时非常有用。但多光谱不是必需——生长阶段识别依赖的是形态和颜色特征,普通 RGB 已经能覆盖绝大部分判别信息。
3.3 目录结构与批量整理脚本
采集回来的原始文件名是IMG_20240602_163000.jpg这种格式,直接扔给训练脚本会让人崩溃。整理时先人工粗筛,把模糊的、过曝的、果穗占比过低的删掉,再按阶段归类到以 BBCH 码命名的目录里。
下面这段脚本把散落在多个子目录里的原始图统一改名并归入标准结构,是每次采集完之后的例行操作:
#!/bin/bash # organize_grapes.sh # 用法: ./organize_grapes.sh <原始目录> <阶段码> <输出根目录> # 例如: ./organize_grapes.sh ./raw_0601 7 ./grapes_dataset RAW_DIR=$1 STAGE=$2 OUT_ROOT=$3 STAGE_DIR="$OUT_ROOT/${STAGE}_$(echo stage_$STAGE)" mkdir -p "$STAGE_DIR" COUNT=1 for img in "$RAW_DIR"/*.jpg; do # 按拍摄日期和序号重命名,避免不同设备文件重名 ts=$(stat -c %y "$img" | cut -d' ' -f1) newname="stage${STAGE}_${ts}_${COUNT}.jpg" cp "$img" "$STAGE_DIR/$newname" COUNT=$((COUNT+1)) done echo "放入 $STAGE_DIR 的图片共 $((COUNT-1)) 张"这段脚本的核心逻辑是:输入原始目录和阶段码,把图片复制到标准目录并重新命名。注意我用stat -c %y提取了文件修改日期,这是为了给每张图打上时间戳;采集时如果相机时间设置乱了,后期还能靠文件名里的日期字段补做时间序列分析。实际使用时有个细节:cp改成mv可以省一半磁盘空间,但我建议保留cp,原始文件在筛错时是后悔药。
4. 标注方案与格式转换:从边界框到 YOLO/COCO 的三条路线
图像整理完成,下一步是标注。这是整个数据集建设中最耗人工的环节,也是决定模型上限的关键。很多人在这里省功夫,最后模型精度上不去,回头查才发现是标注标准没定清楚。
4.1 标注三选一:分类标签、边界框还是分割掩膜
一个常见的疑问是“我这个数据集该标成什么样”答案是取决于下游任务。三种情况:
第一种,整张图只标一个阶段标签,适合做生长阶段分类器,比如判断当前果园处于转色期还是成熟期,这是成本最低的方案,一张图一次点击就能完成。第二种,在整图标签之外再加边界框,框出花穗或果穗的位置,适合做检测计数,能同时回答“是什么阶段”和“有多少串”。第三种,像素级分割,标出果穗的轮廓掩膜,适合做果穗尺寸测量、估产模型。第三种标注成本最高,一张图可能要十分钟。
从数据集的通用性考虑,我强烈建议至少做到第二种——分类加检测。因为有了边界框,你随时可以裁出单个果穗的样本,把一个数据集拆成“整体场景”和“果穗特写”两份数据来用。只标分类标签的数据集,后期想做检测还得回头补标,返工成本极高。
4.2 从 LabelImg 到 YOLO/COCO 的转换脚本
标注工具我习惯用 LabelImg(边界框)或 Labelme(多边形分割),两者导出格式不同——LabelImg 默认输出 VOC XML,Labelme 输出 JSON。训练时常用的 YOLO 和 COCO 格式都不是它们的默认格式,所以需要一小段转换脚本。
以 LabelImg 输出的 XML 为例,转为 YOLO 格式时最容易出错的就是坐标换算。XML 里是左上角和右下角的绝对像素坐标,YOLO 需要的是中心点坐标加宽高,并且要全部归一化到 0 到 1 区间。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_path, class_names): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) yolo_lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in class_names: continue cls_id = class_names.index(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) # 中心点坐标 + 宽高,全部除以图像宽高做归一化 x_center = (xmin + xmax) / 2 / w y_center = (ymin + ymax) / 2 / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") out_file = os.path.join(out_path, os.path.splitext(os.path.basename(xml_path))[0] + ".txt") with open(out_file, "w") as f: f.write("\n".join(yolo_lines)) class_names = ["flower_cluster", "grape_bunch"] for xml in os.listdir("annotations_xml"): if xml.endswith(".xml"): voc_to_yolo(os.path.join("annotations_xml", xml), "labels_yolo", class_names)转换逻辑本身不难,真正的坑藏在几个细节里。第一,size/width和size/height必须从 XML 里读,不能假设原始图像文件还在——标注完如果图像被压缩过,像素对不上,所有框都会错位。第二,class_names的顺序一旦定了就不能改,因为 YOLO 的标签用数字索引,改顺序等于把所有标注重标一遍。第三,归一化后保留 6 位小数就够了,过多位数不会提升精度,只会让文件变大。
这套脚本跑完后,建议做一次可视化检查:把 YOLO 格式的框画回原图上,肉眼抽查二十张。格式转换没有报错不等于坐标正确,这是血泪经验。
4.3 数据划分:按果园划分而不是按图像随机划分
数据划分是个看着简单、实际决定成败的步骤。很多数据集翻车就翻在这里:按图像随机 8:2 划分训练集和验证集,验证集精度 0.95,一到真实果园就掉到 0.7。
原因是同一株葡萄的连续时间序列图像被同时分进了训练和验证。模型相当于“见过”这些背景了。正确做法是按拍摄地点或植株编号划分,同一株或同一行的所有图像必须进同一边。这样才能测试模型的泛化能力。
5. 标注与训练的五个经典坑:现象、原因、对策
做这个方向几年,踩过的坑比成功经验值钱。挑五个最高频的,按“现象 → 原因 → 解决”写清楚,这些钱你们不用再交一遍。
5.1 同一幅图里有多个阶段:标签到底归谁
现象:一张图里两串果穗,一串已经开始转色,另一串还在果实膨大期,标注员 A 标了转色期,标注员 B 标了膨大期,反复返工。
原因:数据集规范只定义了“阶段长什么样”,没定义“同一画面里有多个阶段时怎么处理”,标注规则存在真空区。
解决:明确规则——分类标签以画面中占比最大或处于画面中心的果穗为准;检测标签则分别对每串果穗标注各自的阶段类别。也就是分类用“主要对象投票”,检测用“逐个指定”。定规则后两类任务都不会出现歧义。
5.2 开花期和果实膨大初期长得太像,模型一直混淆
现象:训练出的模型在开花期和果实膨大初期之间频繁错判,混淆矩阵里这两个类别的互相误判率超过三成。
原因:花帽脱落后的幼果和刚坐果的形态差异极小,肉眼尚且需要凑近看,普通分辨率下模型更难区分。
解决:有两个方向的补救。一是数据补采,在两个阶段的过渡期专门加拍一组 200 张的高重叠度样本;二是修改标签粒度,把过渡期的图像单独收一个“transition”类,让模型先学会区分稳定状态,再学边界状态。
5.3 模型在正午拍摄的图像上严重掉点
现象:验证集精度正常,但部署到现场,上午十一点到下午两点之间拍的图像识别准确率骤降。
原因:数据集中绝大部分图像是在清晨和傍晚拍摄的,因为那个时段光线柔和、果穗颜色还原度高、摄影师也舒服。正午的顶光在果穗表面形成高光和硬阴影,模型从未见过这种光照分布。
解决:采集计划强制加入正午时段的视频帧抽取和补拍,每个阶段至少保证 10% 的样本来自正午时段。拍摄时不需要在意过曝,让果穗处于真实光照条件下的状态,模型要学的就是这个。
5.4 同一张图标注两次,两次得到不同标签
现象:做标注一致性检查时,发现同一张图像隔一周标注两次,结果不一致率高达 15%,个别阶段超过 20%。
原因:标注标准里没有指出“看哪里”,标注员凭借整体观感做判断。果穗位置、大小、颜色在不同图片中差异巨大,整体观感并不可靠。
解决:在标注界面的每个阶段旁边固定展示一个“参考部位截图”——例如转色期固定展示“果粒中部颜色变化”,这样操作时注意力有锚点。这个措施通常能把不一致率从 15% 压到 5% 以内。
5.5 验证集精度高,一上真实果园就崩
现象:训练集和验证集划分用随机划分,模型在测试集上准确率 0.93,但部署到另一个产区后准确率不足 0.6。
原因:数据划分时没有按果园分组。同一株葡萄的图像被随机拆散,训练时把某一株的枝条纹理作为特征,换到另一产区后特征失效。这是典型的数据泄漏问题。
解决:划分按“拍摄批次/地块”维度进行,一个地块的图像全部进同一侧。另外,如果面向多产区部署,采集时就至少覆盖三个不同地区的果园,单园数据集训练出来的模型注定泛化力有限。
6. 用数据集微调一个阶段分类器:迁移学习的最小实验
有了结构化的葡萄生长阶段图像数据集,第一个验证实验我建议从迁移学习分类器做起。以 PyTorch 为例,用 ResNet18 在 ImageNet 预训练权重上微调,七分类任务,输入尺寸 224,输出七类。
import torch import torchvision.models as models from torch import nn model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) num_classes = 7 # 0,1,3,5,6,7,8 共七个阶段 model.fc = nn.Linear(model.fc.in_features, num_classes) # 只微调最后两层,前面的冻结权重保留通用特征 for name, param in model.named_parameters(): if "layer4" not in name and "fc" not in name: param.requires_grad = False optimizer = torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) criterion = nn.CrossEntropyLoss()这里的关键参数:学习率 1e-3 不算小,但因为冻结了大量前层,实际更新的参数量很少,这个学习率是安全的。layer4和fc开放微调,是因为这些层负责高层语义特征,和葡萄的果穗颜色、形态最相关。如果数据量超过 3000 张,可以再放开layer3一起微调,通常能再提 2 到 3 个点的准确率。
训练 30 个 epoch 后,除了看 top-1 准确率,一定要输出混淆矩阵。如果数据集质量过关,混淔主要集中在前一章 5.2 提到的开花期和膨大期。这个结果反过来能指导补采方向,形成“采集-标注-训练-分析-再采集”的闭环。
我自己的经验是:数据集的价值不在图像数量,在标签一致性经不经得起交叉验证。标准统一、采集覆盖光照和地域多样性、标注规则可复查,三点做到了,这个数据集就能持续产生价值。希望这套流程能帮你在自己的果园数据上少走一段弯路。
本文还有配套的精品资源,点击获取