尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

葡萄成熟度检测数据集构建与YOLO训练全流程避坑指南

发布时间:2026/9/28 16:35:47

资讯中心
01
ARTICLE

葡萄成熟度检测数据集构建与YOLO训练全流程避坑指南

葡萄成熟度检测数据集构建与YOLO训练全流程避坑指南
简介本资源为面向目标检测任务的葡萄成熟度检测数据集适合从事农业视觉、果实分级或YOLO系列模型训练的研究者与开发者使用。数据集已统一处理为YOLO格式可直接用于YOLO各系列网络的训练与验证帮助解决果实成熟度识别中样本不足、标注不规范的问题。压缩包共2000个文件以1999个txt标签文件和1个Python可视化脚本为主整体约226.63MB其中txt文件对应图像标注信息py脚本可将边界框绘制到图像上便于快速检查标注质量。类别共4类涵盖未熟、半熟、成熟等阶段具体类别可参考class文本文件数据规模超过7000张并配有labelme标注源文件。目前已有328人学习下载适合需要快速搭建葡萄成熟度检测基线、验证模型效果或进行数据增强实验的读者参考使用。1. 葡萄成熟度检测数据集从「能跑通」到「能落地」的第一道坎葡萄成熟度检测这件事听起来像是农业场景里一个很窄的需求但真正做过的人都知道它比通用目标检测难得多。难的不是模型结构而是数据本身——同一串葡萄今天拍是青绿色三天后拍就泛紫光照、遮挡、串型密度、品种差异全都在变。你拿 COCO 上预训练的权重直接推理模型大概率会把整串葡萄框成一个目标根本分不清哪颗熟、哪颗生。这就是为什么「葡萄成熟度检测数据集」这个方向值得单独拿出来讲它不是一个拿来即用的公开数据集而是一套需要你自己构建、标注、增强、验证的数据工程流程。我最初接触这个方向是因为帮一个做设施农业的团队做采摘机器人视觉模块。他们一开始的想法很简单找现成的葡萄数据集标几个类别训一个 YOLO 就完事。结果第一版模型在测试集上 mAP 看着还行一到棚里就翻车——因为测试集和训练集来自同一批拍摄光照条件几乎一样模型学到的其实是「背景颜色」而不是「葡萄成熟度」。这个血泪经验让我意识到葡萄成熟度检测的核心不在模型选型而在数据集构建阶段就要把「成熟度」这个语义定义清楚并且让数据分布覆盖真实采摘场景。这篇文章面向的是已经了解目标检测基础、准备自己动手做葡萄成熟度检测数据集的从业者。我会从成熟度分级标准怎么定、数据采集怎么规划、标注怎么做、增强怎么选、训练怎么验证这条完整链路讲清楚。中间会给出可直接复现的脚本和参数配置也会把我在实际项目中踩过的坑按「现象→原因→解决」写出来。如果你正在做农业视觉、采摘机器人或者果蔬品质检测这套流程可以直接迁移到番茄、草莓、柑橘等类似场景。2. 成熟度分级标准与数据采集先定义清楚「熟」是什么2.1 成熟度分级不是拍脑袋要跟农艺标准对齐很多人做葡萄成熟度检测第一步就错了自己拍一批图凭感觉标「熟」「半熟」「生」。这样标出来的数据集换一个人看就是另一套标准模型训出来也没法跟采摘执行机构对接。正确的做法是先跟农艺师或种植户确认品种的成熟度分级依据。常见的是按可溶性固形物含量糖度和果皮颜色变化来分但视觉检测只能看颜色和纹理所以要把农艺标准翻译成视觉可判别的等级。我一般会建议至少分四级未熟、转色期、近熟、完熟。未熟是整串青绿、果粒硬转色期是果皮开始出现紫红色斑块但底色仍绿近熟是大部分果粒已上色但果梗还绿完熟是果皮颜色均匀、果粉明显、果梗木质化。这四级在图像上的区分度是够的而且跟采摘决策能对应上——比如采摘机器人只摘「近熟」和「完熟」那「转色期」就可以作为下一轮采摘的候选。注意不同品种的颜色变化差异很大。巨峰系是紫黑阳光玫瑰是黄绿红提是鲜红。如果你的数据集要跨品种要么按品种分别建子集要么在标注时把品种作为元数据存下来训练时做条件归一化。2.2 采集设备与拍摄规范别让相机成为瓶颈数据采集阶段最容易忽略的是相机参数一致性。我见过一个团队用手机拍了一部分、用工业相机拍了另一部分结果模型在工业相机图上表现好手机图上直接崩。原因是手机有自动白平衡和自动曝光同一串葡萄在不同光照下颜色差异巨大模型学到的颜色特征完全不可靠。我的建议是如果目标是落地到采摘机器人就用机器人上实际要用的相机采集固定焦距、固定曝光时间、固定白平衡关闭所有自动模式。如果只是做算法验证至少保证同一批次数据用同一台设备、同一组参数。采集时记录每张图的元数据拍摄时间、光照条件晴天/阴天/补光、拍摄距离、品种、地块编号。这些元数据在后面做数据划分和偏差分析时非常有用。采集数量上一个品种至少需要 8001200 张有效图像覆盖不同光照顺光、逆光、侧光、不同遮挡程度无遮挡、叶片半遮挡、果粒相互遮挡、不同拍摄角度正对、俯拍、侧拍。如果要做四级分类每个等级至少 200 张以上否则模型对少数类会严重欠拟合。import cv2 import os import json from datetime import datetime # 采集元数据记录模板 def capture_with_metadata(camera_id, save_dir, variety, plot_id): cap cv2.VideoCapture(camera_id) # 固定相机参数关闭自动曝光、自动白平衡 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) # 手动曝光模式 cap.set(cv2.CAP_PROP_EXPOSURE, -6) # 根据实际光照调整 cap.set(cv2.CAP_PROP_AUTO_WB, 0) cap.set(cv2.CAP_PROP_WB_TEMPERATURE, 4500) # 固定色温 os.makedirs(save_dir, exist_okTrue) ret, frame cap.read() if not ret: raise RuntimeError(相机读取失败检查连接和驱动) timestamp datetime.now().strftime(%Y%m%d_%H%M%S_%f) img_name f{variety}_{plot_id}_{timestamp}.jpg img_path os.path.join(save_dir, img_name) cv2.imwrite(img_path, frame) # 元数据单独存 JSON方便后续按条件筛选 meta { image: img_name, variety: variety, plot_id: plot_id, timestamp: timestamp, exposure: -6, wb_temp: 4500, light_condition: unknown # 人工补填sunny/cloudy/fill_light } with open(os.path.join(save_dir, metadata.jsonl), a) as f: f.write(json.dumps(meta, ensure_asciiFalse) \n) cap.release() return img_path这段脚本的关键点在于曝光和色温是手动锁死的元数据用 JSONL 追加写入每行一条记录。参数CAP_PROP_EXPOSURE的值在不同相机上范围不同需要先试拍几张确认。light_condition留空让人工补填因为自动判断光照条件很容易出错。采集完成后用 metadata.jsonl 可以快速统计各条件下的样本数量避免某一类光照严重不足。2.3 数据划分的坑别让同一串葡萄同时出现在训练集和测试集这是我在项目中翻车最严重的一次。第一版数据集按图像随机划分训练集和测试集里都有同一串葡萄的不同角度照片。模型在测试集上 mAP 0.85实际部署时同一串葡萄换个角度就检测不出来。原因很简单模型记住了这串葡萄的纹理特征而不是学会了成熟度判别。正确的划分方式是按「串」或按「地块」划分。同一串葡萄的所有照片只能出现在一个集合里。如果数据量够最好按地块划分——训练集用 A 区验证集用 B 区测试集用 C 区。这样能真实反映模型在新场景下的泛化能力。如果数据量不够至少按「串 ID」划分采集时就给每串葡萄编号标注时把串 ID 写进文件名或元数据。import random from collections import defaultdict def split_by_bunch(metadata_path, train_ratio0.7, val_ratio0.15): 按葡萄串 ID 划分数据集避免同一串跨集合 bunches defaultdict(list) with open(metadata_path) as f: for line in f: meta json.loads(line) # 假设文件名格式品种_地块_串ID_时间戳.jpg bunch_id _.join(meta[image].split(_)[:3]) bunches[bunch_id].append(meta[image]) bunch_ids list(bunches.keys()) random.seed(42) # 固定随机种子保证可复现 random.shuffle(bunch_ids) n len(bunch_ids) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) splits { train: bunch_ids[:train_end], val: bunch_ids[train_end:val_end], test: bunch_ids[val_end:] } for split_name, ids in splits.items(): with open(f{split_name}.txt, w) as f: for bid in ids: for img in bunches[bid]: f.write(img \n) return splits这个划分脚本的核心逻辑是先按文件名前缀提取串 ID把同一串的所有图像聚在一起再以串为单位随机分配到 train/val/test。random.seed(42)保证每次运行结果一致方便复现。输出是三个 txt 文件每行一个图像文件名后续转 YOLO 格式时直接读这些文件即可。如果你的文件名没有串 ID那就在采集阶段补上这是前期多花五分钟、后期省五小时的事。3. 标注规范与 YOLO 格式转换让标注员和模型都少受罪3.1 标注粒度选择整串框还是单果粒框葡萄成熟度检测的标注粒度直接决定模型能做什么。整串框bounding box 覆盖整串葡萄适合判断「这串能不能摘」标注快但无法处理一串里部分果粒成熟的情况。单果粒框适合精细分级但葡萄果粒密集重叠标注成本极高而且很多果粒在图像上只露出一点点标了反而引入噪声。我的经验是如果目标是采摘决策用整串框 成熟度类别就够了。如果目标是品质分级或产量预估可以考虑整串框 关键点标注标出果梗位置和果串轮廓而不是逐粒标。逐粒标只在果粒稀疏、遮挡少的品种上可行比如某些鲜食葡萄品种。对于巨峰这类密集品种逐粒标就是给自己挖坑。标注工具上LabelImg 和 CVAT 都支持 YOLO 格式导出。我一般用 CVAT因为它支持多人协作和审核流程标注员标完后有复核环节能减少漏标和错标。标注规范要写成文档每个等级给 510 张示例图标注员培训半天再上岗。别指望标注员自己理解「转色期」和「近熟」的区别必须给可视化标准。3.2 YOLO 格式转换脚本与四个边界坑YOLO 格式要求每张图对应一个 txt 文件每行格式为类别索引 中心x 中心y 宽度 高度所有坐标归一化到 01。从 LabelImg 的 XML 或 CVAT 的 JSON 转 YOLO 时有四个坑几乎每个人都会踩。第一个坑坐标归一化时用错图像尺寸。XML 里存的是绝对像素坐标转换时要用原图宽高做归一化。如果图像在标注后被缩放或裁剪过必须用标注时的尺寸不能用当前文件尺寸。第二个坑类别索引从 0 开始还是从 1 开始。YOLO 要求从 0 开始但有些工具默认从 1 开始差一位就全错。第三个坑浮点数精度。归一化后保留 6 位小数就够了保留太多位数会让文件变大且无意义。第四个坑空标注文件。有些图没有目标YOLO 要求仍然生成空 txt 文件否则训练时会报错。import xml.etree.ElementTree as ET import os import cv2 # 类别映射必须从 0 开始顺序与训练配置一致 CLASS_MAP { unripe: 0, # 未熟 veraison: 1, # 转色期 near_ripe: 2, # 近熟 ripe: 3 # 完熟 } def xml_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 用 XML 中记录的尺寸而不是当前图像文件尺寸 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: print(f跳过未知类别: {cls_name} in {xml_path}) 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) # 边界裁剪防止标注超出图像范围 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 归一化并保留 6 位小数 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) # 即使没有目标也要生成空文件 base os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(out_dir, base .txt) with open(out_path, w) as f: f.write(\n.join(lines)) return out_path这段转换脚本的关键处理用 XML 里的size节点做归一化而不是重新读图避免图像被修改后尺寸不一致对坐标做边界裁剪防止标注框超出图像范围导致 YOLO 训练时报错未知类别直接跳过并打印警告不静默丢弃空标注也生成空文件。CLASS_MAP的顺序必须和训练时的data.yaml里names列表顺序完全一致否则类别会错位。3.3 数据增强别把葡萄转成「抽象画」YOLO 训练时自带 Mosaic、HSV 增强、翻转等。但葡萄成熟度检测对颜色极其敏感HSV 增强里的色调偏移如果太大会把「未熟」的绿色调成「近熟」的紫色模型直接学废。我的做法是色调偏移范围控制在 ±10 以内饱和度和亮度可以适当放宽到 ±30。翻转可以用但垂直翻转要谨慎——葡萄串通常是下垂的垂直翻转后变成朝上不符合真实场景。另外Mosaic 增强在小数据集上容易导致边界框错位尤其是葡萄串相互遮挡时。如果数据集里遮挡样本本来就多建议关闭 Mosaic 或降低概率。我一般会在data.yaml同级目录放一个hyp.yaml把hsv_h设为 0.01hsv_s设为 0.3hsv_v设为 0.3mosaic设为 0.5。这些参数不是拍脑袋是试出来的——色调偏移超过 0.02 时验证集上的成熟度分类准确率会掉 8 个点以上。4. 训练配置与验证mAP 高不代表能用4.1 YOLOv8 训练参数怎么设才不翻车选 YOLOv8 还是 YOLOv11对葡萄成熟度检测来说差异不大关键是输入分辨率和 anchor 配置。葡萄果粒在图像中占比小如果输入分辨率用默认的 640小果粒特征会丢失。我一般会把imgsz设到 960 或 1280显存不够就减小 batch size。batch设 8 或 16epochs设 150200patience设 30 做早停。学习率用默认的lr00.01通常没问题但如果从预训练权重微调建议降到 0.001否则前期 loss 震荡严重。optimizer选 AdamW 比 SGD 收敛快但最终精度可能略低看你的时间预算。close_mosaic设 10意思是最后 10 个 epoch 关闭 Mosaic让模型在真实分布上收尾。# data.yaml path: ./grape_dataset train: train.txt val: val.txt test: test.txt names: 0: unripe 1: veraison 2: near_ripe 3: ripe# 训练命令 yolo detect train \ data./grape_dataset/data.yaml \ modelyolov8m.pt \ imgsz960 \ epochs200 \ batch16 \ patience30 \ lr00.001 \ optimizerAdamW \ close_mosaic10 \ hsv_h0.01 \ hsv_s0.3 \ hsv_v0.3 \ mosaic0.5 \ projectgrape_runs \ nameexp01命令里的modelyolov8m.pt是中号模型如果显存够可以换yolov8l.pt。imgsz960是平衡精度和速度的选择再大推理会慢。hsv_h0.01是前面说的色调偏移限制。project和name控制输出目录方便对比不同实验。训练日志里重点看val/box_loss和metrics/mAP50-95如果 box_loss 震荡不降检查标注框是否有大量越界或宽高为 0 的情况。4.2 验证集指标好看但实际翻车怎么排查这是葡萄成熟度检测最典型的翻车场景验证集 mAP 0.9部署到棚里 mAP 不到 0.5。排查思路按优先级来。第一检查验证集和测试集是否真的按串划分了。如果验证集里有训练集同串的图像指标虚高。第二检查光照分布。训练集如果全是晴天顺光测试集有阴天逆光模型对颜色判断会失准。第三检查成熟度类别是否平衡。如果「完熟」样本占 70%模型会倾向于把所有葡萄都判成完熟mAP 看着高但召回率极低。我一般会做一个混淆矩阵分析看模型把哪些类别判错了。如果「转色期」大量被判成「近熟」说明这两个等级在视觉上区分度不够要么合并等级要么补充更多边界样本。如果「未熟」被判成背景说明未熟样本的标注框太小或特征不明显需要增加未熟样本的采集量。from ultralytics import YOLO import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix model YOLO(grape_runs/exp01/weights/best.pt) results model.val(data./grape_dataset/data.yaml, imgsz960) # 提取混淆矩阵 cm results.confusion_matrix.matrix plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmt.0f, xticklabels[unripe, veraison, near_ripe, ripe], yticklabels[unripe, veraison, near_ripe, ripe]) plt.xlabel(预测) plt.ylabel(真实) plt.title(葡萄成熟度混淆矩阵) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150)这段代码用 Ultralytics 自带的val接口跑验证然后画混淆矩阵。重点看对角线以外的数值。如果某一列特别亮说明模型倾向于预测那个类别如果某一行特别暗说明那个类别的召回率很低。根据矩阵结果决定是补数据还是调类别定义比盲目调参有效得多。4.3 部署前的最后一关用视频流做时序验证静态图验证通过后别急着部署。用一段实际采摘场景的视频流跑推理观察模型在连续帧上的表现。葡萄成熟度检测有个特点单帧可能判错但连续帧的投票结果往往更稳。如果模型在视频里频繁跳变——同一串葡萄上一帧判「近熟」下一帧判「完熟」——说明模型对颜色边界不稳定需要补充边界样本或加时序平滑。我一般会写一个简单的视频推理脚本把每帧的检测结果画框并保存然后人工看一遍。如果跳变率超过 10%就回去补数据。这个步骤花不了多少时间但能避免部署后被用户骂「这模型一会儿说熟一会儿说不熟」。5. 避坑与常见问题那些让我熬夜的瞬间5.1 标注框重叠导致 NMS 后处理丢目标现象训练时 loss 正常下降但推理时密集葡萄串只框出一两个目标大量果粒被 NMS 过滤掉。原因葡萄果粒相互遮挡标注框重叠度高NMS 的 IoU 阈值默认 0.45重叠超过阈值的框会被抑制。解决推理时把iou阈值调到 0.60.7或者改用 Soft-NMS。如果用的是 Ultralytics可以在model.predict()里传iou0.65。但注意阈值太高会引入重复框需要根据实际场景试。5.2 类别不平衡导致「完熟」霸屏现象模型把所有葡萄都预测成「完熟」其他类别召回率接近零。原因采集时完熟样本容易拍未熟和转色期样本少类别分布严重倾斜。解决在data.yaml里加类别权重或者在训练时用focal_loss。更直接的办法是过采样少数类——把未熟和转色期的图像复制多份但要注意不能简单复制最好用旋转、裁剪生成变体。我一般会把少数类过采样到和多数类 1:2 的比例再配合hsv增强。5.3 图像尺寸不一致导致训练报错现象训练启动时报RuntimeError: shape mismatch或AssertionError。原因数据集中混入了不同分辨率的图像YOLO 虽然会 resize但如果标注文件里的归一化坐标是基于原始尺寸算的resize 后坐标就不对了。解决统一把所有图像 resize 到同一尺寸或者确保标注转换时用的是每张图自己的尺寸。我习惯在转换脚本里加一步校验读图获取宽高和 XML 里的尺寸对比不一致就报警并跳过。5.4 验证集 mAP 虚高实际部署崩盘现象验证集 mAP 0.9棚里测试 mAP 0.4。原因数据划分时同一串葡萄跨了训练集和验证集模型记住了纹理而非成熟度特征。解决按串或按地块划分确保同一串只出现在一个集合。如果已经训完了才发现重新划分后重训别想着微调能救回来。5.5 模型对光照变化敏感阴天直接失效现象晴天拍的图检测正常阴天或补光条件下漏检严重。原因训练集光照条件单一模型过拟合到特定光照下的颜色分布。解决采集时覆盖多种光照训练时用hsv_v增强模拟亮度变化但色调增强要克制。如果条件允许在部署端加一个自动曝光或直方图均衡化预处理能缓解一部分问题。6. 进阶技巧用半自动标注把数据集扩到 5000 张当你把前面流程跑通、有了一个 1000 张左右的基线数据集后下一步就是扩量。纯人工标注 5000 张葡萄图像成本高且周期长。我的做法是半自动标注用基线模型对未标注图像做推理把置信度高于 0.7 的检测框导出为预标注人工只需要修正漏检和错标效率能提升 34 倍。具体流程是先用model.predict()跑一批新图把结果存成 YOLO 格式的 txt然后导入 CVAT 或 LabelImg 做人工复核。复核时重点看两类置信度在 0.30.7 之间的框以及模型完全没检出的区域。前者往往是边界样本后者可能是新出现的遮挡模式或光照条件。复核完的标注直接并入训练集重新训练一版模型再跑下一轮半自动标注。这个循环跑三轮数据集就能从 1000 张扩到 5000 张而且新增样本都是模型当前薄弱的场景信息量比随机采集高得多。from ultralytics import YOLO import os model YOLO(grape_runs/exp01/weights/best.pt) img_dir ./new_images out_dir ./pre_labels os.makedirs(out_dir, exist_okTrue) for img_name in os.listdir(img_dir): if not img_name.lower().endswith((.jpg, .png)): continue img_path os.path.join(img_dir, img_name) results model.predict(img_path, conf0.3, iou0.65, imgsz960) lines [] for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if conf 0.3: continue # YOLO 格式类别 中心x 中心y 宽 高归一化 xywhn box.xywhn[0].tolist() lines.append(f{cls_id} {xywhn[0]:.6f} {xywhn[1]:.6f} {xywhn[2]:.6f} {xywhn[3]:.6f}) base os.path.splitext(img_name)[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines))这段脚本把置信度阈值设到 0.3是为了尽量多召回候选框宁可多标也不漏标。iou0.65是前面说的密集场景调参。导出的预标注文件直接放到 CVAT 里作为初始标注人工复核时只改错的不从零标。注意预标注的类别索引必须和你的data.yaml一致否则导入后类别全乱。半自动标注的边界在于如果基线模型本身在某类场景上表现很差预标注会引入大量错误人工复核反而更累。所以每轮扩量后要重新评估模型如果某一类召回率低于 0.5先补该类的人工标注别急着跑半自动。另外预标注文件不要直接当训练标签用必须经过人工复核否则错误会累积放大。我自己的习惯是每轮扩量后留 10% 的新数据做「盲测」——不参与训练只用来评估模型在新场景下的真实表现。如果盲测 mAP 比验证集低超过 15 个点说明数据分布还有偏差继续补采。这个习惯帮我避免了好几次「验证集好看、部署翻车」的尴尬。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。