简介面向计算机视觉与农业智能化方向的算法工程师、科研人员及高校学生这份数据集提供超过7000张葡萄成熟度标注样本类别覆盖未熟、半熟、成熟等4个等级并已统一转换成YOLO格式可直接用于YOLOv5、YOLOv8等全系列模型训练免去繁琐的格式转换。压缩包内共2000个文件以txt格式的标注标签为主另附1个show.py可视化脚本整体大小226.63MB目录按训练集、验证集及class类别文本清晰组织解压即可接入主流检测流程。目前已有338人学习下载。配套的show.py脚本能把检测框直接绘制到对应图像上便于快速核查标注质量、类别分布和数据划分显著缩短预览与清洗周期。无论用于葡萄成熟度识别实验、果园产量预估落地还是作为目标检测教学案例均能直接复用。1. 目标检测数据集为什么先拿葡萄成熟度试手数据格式决定训练能走多远一个目标检测数据集的核心资产不是图片数量而是标签格式和类别定义。葡萄成熟度检测这份资源把 labelme 的 JSON 标注处理成了 YOLO 可直接训练的 txt 格式超过 7000 张图像、4 个成熟度类别覆盖训练集、验证集和对应标签文件可以拿来跑 YOLO 全系列网络训练。对刚接触检测的开发者来说它最大的价值是省掉了自己写转换脚本的环节但前提是你得先看懂标签文件里存的到底是什么。这篇笔记就按工程落地的顺序把格式原理、可视化验证、踩坑点依次拆开讲清楚确保你拿到资源后当天就能跑通验证流程。2. YOLO 标签格式与四类成熟度划分先读懂 txt 里到底存了什么2.1 从 labelme JSON 到 YOLO txt转换逻辑和 7000 张图的资产结构这份数据集的源头是 labelme 标注标注结果最终被处理成了 YOLO 格式的 txt 文件。我见过不少人在这一步翻车拿到的 txt 用记事本打开是一堆数字不知道每个数字代表什么更不知道坐标是绝对像素还是归一化值。YOLO 的标签格式每行固定 5 个字段逗号或空格分隔都可以但大部分实现里用空格类别ID 目标中心X 目标中心Y 目标宽度 目标高度关键点在于X、Y、W、H 全部是相对图像的归一化值范围在 0 到 1 之间。比如一张 1920×1080 的图像里某个葡萄串中心在像素坐标 (960, 540)宽度是 480 像素那这一行就写成0 0.5 0.5 0.25 xxx。归一化的好处是图像分辨率变化后标签依然有效640 输入和 1280 输入共用同一套 txt不需要重新标。从 labelme 的 JSON 转成这种格式核心逻辑是把多边形的顶点坐标换算成外接矩形的中心点与宽高def labelme_to_yolo(vertices, img_w, img_h, class_id): xs [pt[0] for pt in vertices] ys [pt[1] for pt in vertices] x1, x2 min(xs), max(xs) y1, y2 min(ys), max(ys) x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h return f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}这里vertices是 labelme 标注文件里points字段的坐标列表每个点是图像上的绝对像素坐标。取所有点的最小和最大值组成外接框是目标检测数据转换里最常见也是最稳妥的做法。注意最后除以img_w和img_h这一步漏掉的话训练时框会全部错位而且肉眼还不容易发现。class_id的取值不是标注文本里的中文名而是类别列表里的索引这点在下一节单独说。解压这份资源后目录结构按常见做法大概是这样的路径内容images/train/训练集原图jpg 或 png 混合images/val/验证集原图labels/train/训练集 yolo txt 标签与图片同名labels/val/验证集 yolo txt 标签classes.txt类别清单一行一个类名顺序决定 class idshow.py可视化脚本把标注框画到图上注意 txt 标签的文件名和图片名并不完全相等常见命名里带随机后缀比如图片是00248-3249807169_png.png标签是00248-3249807169_png.rf.cc6b53b9066b3e78eef7f5c2b643472b.txt本质还是同一份文件的一一对应只是加了一串哈希后缀防止重名覆盖。后面训练时如果用目录式加载靠文件名前缀配对是没问题的。2.2 class 文件与类别映射四类成熟度对应的真实含义摘要里写类别个数是 4分别是未熟、半熟、成熟等。实际类别名称要以classes.txt里的文本为准这份文件就是类别顺序的最终定义。每次拿到数据集第一件事就是打开它确认 id 顺序cat classes.txt一个典型的类别清单可能是这样的unripe half-ripe ripe overripe对应关系也就确定了txt 每行第一个数字是 0那这个框的类别就是unripe是 1 就是half-ripe。这个逻辑听起来简单但它是整个数据集最容易被忽略的地基。后面任何脚本只要依赖类别顺序全部以这个文件为一致来源不要凭标注时的记忆去判断某个框是哪种成熟度。四类成熟的边界本身也是个工程问题。葡萄成熟度是连续物理变化标注时打上的类别标签却有明确断点。实践中半熟和成熟两类在颜色上经常接近特别是青提这类品种标注人员同一个人的前 500 张和后 500 张都可能出现标准漂移。这属于目标检测数据集里典型的语义边界模糊不是 bug但会影响最终 mAP 上限。缓解手段通常有两个一是抽检类别间的混淆情况把容易混的图片归到同一类或干脆删掉模糊样本二是在 loss 里给中间类别加权让模型不偏向样本多的类别。还有一个值得养成的习惯检查每张图的标签是否都是 4 个类别内的数字。因为 class id 一旦越界训练框架通常直接报错中断或者把越界样本吞掉导致训练集和验证集数据量对不上。检查方法后面避坑章会给具体命令。3. 用 show.py 把标注框画回去7000 张图的可视化检查三步走3.1 show.py 脚本详解从 txt 画框到图像拿到数据集先别急着训练第一件事是可视化抽查。show.py 的作用就是把 txt 里的归一化坐标换算回图像像素坐标然后把框画在原图上让你能直观看到标注和真实目标的贴合程度。这套检查虽然原始但能挡住 80% 的数据问题。常见视觉脚本的写法是import os import sys import cv2 def draw_yolo_boxes(image_path, label_path, color(0, 255, 0), thickness2): img cv2.imread(image_path) if img is None: print(f[ERROR] cannot read image: {image_path}) return None h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh parts xc, yc, bw, bh float(xc), float(yc), float(bw), float(bh) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness) cv2.putText(img, cls_id, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, thickness) return img if __name__ __main__: image_path sys.argv[1] label_path os.path.splitext(image_path)[0] .txt if not os.path.exists(label_path): # 部分标签带随机后缀找不到时在同目录做前缀匹配 for f in os.listdir(os.path.dirname(image_path)): if f.startswith(os.path.splitext(os.path.basename(image_path))[0]) and f.endswith(.txt): label_path os.path.join(os.path.dirname(image_path), f) break result draw_yolo_boxes(image_path, label_path) if result is not None: cv2.imwrite(show_result.jpg, result) print(saved to show_result.jpg)这段脚本里最关键的是坐标换算(xc - bw / 2) * w把归一化的中心点和宽高还原成左上角和右下角的绝对像素坐标。画框前cv2.imread的判空处理不是摆设路径里带中文或图片损坏时 OpenCV 会静默返回 None不加判断就可能把空白图保存下来让你误以为标签全空了。后面那段前缀匹配是为了应对这数据集里带随机哈希后缀的 txt 文件保证show.py 图片名单命令能正确找到标签。跑的方式很简单python show.py images/train/00248-3249807169_png.png跑完打开show_result.jpg核对三点框是不是紧贴真实目标框的数量和图片里实际目标数量是否一致类别 id 显示是否和成熟度一目了然对应。抽样量我一般会取训练集的 1% 到 2%7000 张里至少看够 50 张再进训练环节。3.2 训练集与验证集划分文件命名与目录组织这份资源已经按 YOLO 的常见目录式结构分好了 train 和 val。目录式组织的含义是训练时框架自动读取images/train下的图片并在labels/train找对应 txt。这种组织方式对新手最友好不用手动维护路径清单文件。不过有一类训练框架需要的是train.txt和val.txt里面每行写一张图片的绝对或相对路径。目录式转清单式其实很简单find images/train -name *.jpg -o -name *.png | sort train.txt find images/val -name *.jpg -o -name *.png | sort val.txt执行完检查行数是不是和 images 子目录里文件数一致。注意train.txt的路径要和工程目录的基准一致因为很多框架会先把图片路径解析出来再拼上labels目录去读标签路径基准不一致的典型错误是把images/train/xxx.jpg写成train/xxx.jpg结果训练时大量图片找不到标签直接被跳过了。数据划分比例的确认也值得做。成熟度检测场景下4 类中半熟和成熟的样本往往集中在多个连续的采集批次里如果验证集恰好落在某一种成熟度出现高峰的时间段验证 mAP 会虚高。经验做法是用ls images/val | wc -l看验证集大小确认划分没有把某个类别单独切出去。真要严格划分时可以用按类别分层抽样的脚本而不是纯随机切分。纯随机在 7000 张规模下问题通常不大但如果你后面决定合并其他来源的数据分层抽样就必不可少。4. 常见问题与排查标注错位、类别缺失和路径玄学4.1 框和图像对不上不是 show.py 的锅现象用 show.py 画出来的框整体偏移或者框明显比目标大一圈但文件里的数字看起来都在 0 到 1 之间。原因最常见的是 txt 没有真正归一化。有些中途处理过的标签文件中心点和宽高仍然用的绝对像素值只是数值碰巧在 0 到 1 范围之外或者被错误地除以了一个固定值另一种场景是把 labelme 转换脚本里的img_w / img_h换成了一次性的缩放比例而原图分辨率不统一导致部分图片错位。解决先随机抽 3 张图手动读取图像尺寸并把 txt 的第一个框换算回像素坐标看看左上角是否落在图像范围内。如果框坐标有大于 1 或小于 0 的情况说明标签还是绝对坐标需要重新走一遍归一化转换。注意 YOLO 训练时会把这类非法框直接丢弃但不会提示你丢了多少所以很多人训练完了都不知道训练数据里有一部分框压根没参与计算。4.2 类别总是报错或训练中断txt 里的 class id 越界现象训练刚开始几百步就报class id out of range之类的错误或者干脆训练结束验证时报 mAP 为 0。原因txt 标签里出现了大于等于 4 的数字。发生这类情况通常是标注阶段类别名没统一比如同一个人前一批用halfRipe后一批改成half-ripe转换脚本把它们当成两个类别索引排到了第 4 位甚至第 5 位。由于这个数据集来源标注批次多越界样本并不集中报错也只是在读取到那一行时才触发。解决训练前先全量扫描一次标签目录找出非法类别 idgrep -R ^[^0-3] labels/ | head -20^[^0-3]匹配的是行首第一个字符不是 0、1、2、3 的标签行。把产生的清单输出后要么删掉这些标签和对应图片要么修正类别 id。但要记住修正时必须回到 classes.txt 的顺序核对一次而不是凭印象把 4 改成 2因为 4 对应的可能并不是“半熟”而是标注人员口误导致的另一个类别名。4.3 训练反复不收敛或 mAP 很低问题先查数据再查网络现象loss 在前面几十个 epoch 降得很慢训练结束后会发现验证集上某类别的 AP 特别低比如未熟和半熟之间互相误检严重。原因典型的数据原因有三个。一是半熟样本数量明显少于其他三类模型在这个类别上欠拟合二是成熟度的边界标注存在大量主观差异同一个果实两张图里一个标成半熟一个标成成熟三是有空标签文件wc -l为 0 的 txt 对应图片没有框而在成熟度场景里有些图只是远景没有被标到。解决先用一行命令统计类别分布再决定策略awk {print $1} labels/train/*.txt | sort | uniq -c | sort -rn参数含义很直白$1是每行第一个字段也就是类别 idsort | uniq -c统计每个 id 出现的次数。输出的数字里如果某个类别不到总量的 10%那就是典型的长尾。处理办法是给模型 config 里的loss 权重上做手脚给少数类更高的权重更简单粗暴的是把多数类随机下采样让 4 类比接近 4:4:4:4但前提是总量仍然够训练。标注主观差异属于数据噪声能做的只有把混乱样本单独挑出来手动合并修正。这些操作在训练前做比训练后返工省下不止半天时间。5. 把数据集接进训练流程验证集划分与成熟度样本平衡这套葡萄成熟度数据集真正好用的地方在于它已经是 YOLO 能直接吃的格式。接到训练流程时我习惯在正式训练前固定跑一遍自查形成一个固定套路python show.py images/train/00248-3249807169_png.png awk {print $1} labels/train/*.txt | sort | uniq -c | sort -rn ls labels/train/*.txt | wc -l第一行抽看标注质量第二行看类别分布第三行确认标签总数量。这三条命令执行时间不超过一分钟但能把你从“训练了一整天、验证时发现数据集有问题”的血泪里救出来。类别样本平衡这块我的处理习惯是不轻易删样本。删样本会让模型丢失对边界情况的判断力尤其是成熟度这种连续变化场景删掉低置信度的半熟样本测试时遇到表现稍差的果实反而更容易误判。正确做法是把少数类做简单的数据增强复刻或者调整训练时的采样权重。部分 YOLO 训练代码里可以直接指定类别权重参数把半熟这类目标设为 1.5 到 2 倍权重比改变损失函数本身风险更小。跑一轮短训练验证分类正确率如果提升不显著再回到数据层面。关于 mAP 的验证有一点容易被忽略看验证集 mAP 时不要只看 overall 值要按类别分别看。此数据集如果某个类别上 AP 比其他三类低 20 个点以上基本可以断言是数据问题而不是网络能力问题。把验证集的预测结果输出成混淆矩阵能直接看出是未熟误判成半熟还是相反这个方向同时决定了数据的修正方式是合并模糊类别还是补充特定成熟度的图片。从那以后我每次拿到任何标注好的目标检测数据集都强制走一遍这套流程先看 classes.txt再统计类别分布抽至少 20 张图可视化最后才进入训练配置。这个顺序不起眼但让我避开了好几次标签格式错误带来的无效训练。如果你也正打算拿这份葡萄成熟度数据集跑自己的实验建议把上面三步当成踏入训练前的固定仪式能让你省下的是真正宝贵的时间希望帮到你。本文还有配套的精品资源点击获取