简介:面向计算机视觉与农业智能化方向的算法工程师、科研人员及高校学生,这份数据集提供超过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 id |
| show.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), thickness=2): 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', encoding='utf-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 位。由于这个数据集来源标注批次多,越界样本并不集中,报错也只是在读取到那一行时才触发。
解决:训练前先全量扫描一次标签目录,找出非法类别 id:
grep -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是每行第一个字段也就是类别 id,sort | 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 张图可视化,最后才进入训练配置。这个顺序不起眼,但让我避开了好几次标签格式错误带来的无效训练。如果你也正打算拿这份葡萄成熟度数据集跑自己的实验,建议把上面三步当成踏入训练前的固定仪式,能让你省下的是真正宝贵的时间,希望帮到你。
本文还有配套的精品资源,点击获取