简介:这份目标检测数据集聚焦葡萄成熟度识别,面向从事农业视觉、果蔬分拣或目标检测算法练习的开发者与研究者,帮助解决成熟度分级样本不足、标注格式不统一的问题。资源已统一处理为YOLO格式,可直接用于YOLO全系列网络训练,并附带show脚本,能将预测框绘制回图像上,便于快速核验标注质量与训练效果。包内共2000个文件,以1999个txt标签文件和1个Python可视化脚本为主,压缩包约226.63MB,标签覆盖未熟、半熟、成熟等4个类别,具体类别可参考class文本文件,原始数据超过7000张且由labelme标注。目前已有328人学习下载,适合希望快速搭建葡萄成熟度检测基线、验证数据增强与模型调参策略的读者,也可作为农业场景目标检测课题的现成数据支撑。
1. 葡萄成熟度检测数据集:7000 张图、4 个等级,直接能喂给 YOLO 的落地包
如果你正在做农业视觉方向的目标检测,尤其是葡萄采摘机器人、果园产量预估这类项目,最耗时的往往不是调模型,而是搞数据。我手上这份葡萄成熟度检测数据集,解压后就是一套已经处理成 YOLO 格式的完整资源:超过 7000 张田间葡萄图像,配套 labelme 标注转好的 txt 标签,外加一个 class 类别文件,训练集和验证集已经分好。类别一共 4 个,覆盖未熟、半熟、成熟等不同成熟度等级,具体名称直接看 class 文本文件即可。它解决的就是“从零标注到能开训”这段最枯燥的流程,适合想快速验证 YOLO 系列模型在细粒度分类检测上表现的从业者,也适合拿来做数据增强、类别不平衡分析的练手素材。show.py 脚本还能把 box 画回原图,先看数据再决定怎么训,这一步比盲目开跑重要得多。
2. 拆开这份数据集:目录结构、标签格式与类别映射
2.1 从文件名看懂数据组织方式
拿到压缩包解压后,你会看到图像和标签是成对出现的。项目正文里列出的那些文件名,比如00248-3249807169_png.rf.cc6b53b9066b3e78eef7f5c2b643472b.txt,就是典型的标注文件命名。这种带一串哈希值的命名方式,常见于 labelme 导出或某些标注平台的处理流程,好处是几乎不会重名,坏处是人眼没法直接判断内容。图像文件通常是同名的.jpg或.png,和 txt 标签放在平行目录里。
我一般拿到新数据集先做三件事:数文件、看配对、读类别。数文件是确认图像数量和标签数量是否一致,看配对是检查有没有孤儿标签或缺失标签,读类别就是打开 class 文件确认类别顺序。这三步花不了五分钟,但能避免后面训练到一半才发现标签对不上号的翻车现场。
# 统计图像和标签数量,确认是否配对 find images/ -type f \( -name "*.jpg" -o -name "*.png" \) | wc -l find labels/ -type f -name "*.txt" | wc -l # 检查是否有图像没有对应标签 for img in images/*.jpg; do base=$(basename "$img" .jpg) if [ ! -f "labels/${base}.txt" ]; then echo "缺失标签: $img" fi done上面这段 bash 脚本做的是最基础的完整性检查。find命令分别统计图像和标签数量,正常情况下两者应该相等。第二个循环遍历每张图像,检查 labels 目录下是否存在同名 txt,如果缺失就打印出来。参数上注意图像扩展名,如果你的数据是 png 就把-name "*.jpg"改成-name "*.png"。这一步跑完如果输出为空,说明配对没问题,可以继续往下走。
2.2 YOLO 标签格式与类别文件解读
YOLO 格式的标签是每行一个目标,格式为class_id x_center y_center width height,后四个值都是归一化到 0 到 1 之间的浮点数。class_id 从 0 开始,对应 class 文件里的类别顺序。这份数据集有 4 个类别,所以 class_id 只会出现 0、1、2、3。class 文件一般是纯文本,每行一个类别名,顺序不能乱,因为训练时模型就是按这个顺序输出类别的。
# 读取 class 文件并解析一个标签样本 with open("classes.txt", "r", encoding="utf-8") as f: class_names = [line.strip() for line in f if line.strip()] print("类别列表:", class_names) print("类别数量:", len(class_names)) # 解析一个标签文件 label_path = "labels/00248-3249807169_png.rf.cc6b53b9066b3e78eef7f5c2b643472b.txt" with open(label_path, "r") as f: lines = f.readlines() for line in lines: parts = line.strip().split() cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:]) print(f"类别: {class_names[cls_id]}, 中心: ({x:.3f}, {y:.3f}), 宽高: ({w:.3f}, {h:.3f})")这段 Python 代码先读 class 文件拿到类别名列表,然后解析一个具体标签文件。split()默认按空白字符分割,得到 5 个值。int(parts[0])是类别索引,map(float, parts[1:])把后四个转成浮点数。打印出来你就能直观看到每个框的类别和位置。常见坑是 class 文件里有空行或 BOM 头,strip()能处理空行,但如果第一行类别名前面有不可见字符,最好用encoding="utf-8-sig"打开。我一般会先跑一遍这个脚本,确认类别名和标签里的 class_id 能对上,再开始配置训练。
2.3 用 show.py 做可视化验证
show.py 是这份资源里很实用的一个脚本,作用是把标签里的 box 画回原图,让你肉眼确认标注质量。很多人拿到数据集直接开训,结果 mAP 上不去,回头查才发现标注框偏移严重或者类别标错。先可视化一遍,能省下大量排查时间。
# show.py 核心逻辑示意:读取图像和标签,绘制矩形框 import cv2 def draw_boxes(img_path, label_path, class_names): img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path, "r") as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) # 归一化坐标转像素坐标 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), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("preview", img) cv2.waitKey(0) cv2.destroyAllWindows()这段代码展示了 show.py 的核心逻辑。xc, yc是框中心归一化坐标,bw, bh是归一化宽高。转像素坐标时,左上角 x1 等于中心 x 减去一半宽度再乘图像宽,其余同理。cv2.rectangle画框,cv2.putText写类别名。实际使用时按空格或任意键切换下一张。注意 OpenCV 的窗口在某些远程环境下可能弹不出来,这种情况可以把绘制结果用cv2.imwrite保存到本地再看。我习惯随机抽 20 张过一遍,重点看成熟和半熟这两个容易混淆的类别,边界框有没有把相邻葡萄串混在一起。
3. 把数据集接进 YOLO 训练:配置、路径与参数设置
3.1 目录重组与 data.yaml 编写
原始数据集的目录结构不一定直接符合 YOLO 训练的要求。YOLO 官方推荐的结构是 images/train、images/val、labels/train、labels/val 四个目录。如果拿到的数据已经分好训练集和验证集,那只需要确认标签和图像在同一层级下对应即可。如果没有分,常见做法是按 8:2 或 7:3 随机划分,注意要按图像划分而不是按标签划分,避免同一张图的标签跑到不同集合。
import os import random import shutil # 按 8:2 划分训练集和验证集 image_dir = "images" label_dir = "labels" train_img_dir = "dataset/images/train" val_img_dir = "dataset/images/val" train_lbl_dir = "dataset/labels/train" val_lbl_dir = "dataset/labels/val" for d in [train_img_dir, val_img_dir, train_lbl_dir, val_lbl_dir]: os.makedirs(d, exist_ok=True) images = [f for f in os.listdir(image_dir) if f.endswith((".jpg", ".png"))] random.seed(42) random.shuffle(images) split = int(len(images) * 0.8) for i, img_name in enumerate(images): base = os.path.splitext(img_name)[0] label_name = base + ".txt" if i < split: shutil.copy(os.path.join(image_dir, img_name), train_img_dir) shutil.copy(os.path.join(label_dir, label_name), train_lbl_dir) else: shutil.copy(os.path.join(image_dir, img_name), val_img_dir) shutil.copy(os.path.join(label_dir, label_name), val_lbl_dir)这段脚本做的是数据集划分。random.seed(42)固定随机种子,保证每次划分结果一致,方便复现。split取 80% 作为训练集。循环里用os.path.splitext去掉图像扩展名,拼出对应标签文件名,然后分别复制到目标目录。参数上你可以把 0.8 改成 0.7 或 0.9,看数据量和任务难度。注意如果图像和标签扩展名不一致,比如图像是 jpg 标签是 txt,这里逻辑是没问题的,因为标签固定是 txt。
划分完之后需要写 data.yaml,这是 YOLO 训练时指定数据路径和类别的地方。
# data.yaml train: dataset/images/train val: dataset/images/val nc: 4 names: ['未熟', '半熟', '成熟', '过熟']train和val写相对路径或绝对路径都行,nc是类别数,names是类别名列表,顺序必须和 class 文件一致。如果 class 文件里是英文,这里就写英文,不要中英混用。我见过有人 data.yaml 里写 4 个类别,class 文件里只有 3 行,训练直接报索引越界,这种错误排查起来很快但一开始容易忽略。
3.2 训练命令与关键参数含义
YOLO 系列训练命令大同小异,以常见的 YOLOv8 为例,一条命令就能跑起来。
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=grape_maturity逐项说明:data指向刚才写的 yaml 文件;model是预训练权重,n 表示 nano 版本,速度快适合先跑通流程,后续可以换 s、m、l;epochs是训练轮数,100 轮对 7000 张图来说通常够用,但要看收敛曲线;imgsz是输入尺寸,640 是默认值,如果葡萄串在图中占比很小可以提到 1280,但显存消耗会明显增加;batch是批大小,16 在 8G 显存上比较稳,显存不够就降到 8;lr0是初始学习率,0.01 是常见起点;patience是早停耐心值,20 轮验证集指标不提升就停,省时间。project和name控制输出目录。
训练过程中重点看三个指标:box_loss、cls_loss 和 mAP50。box_loss 下降说明框回归在收敛,cls_loss 下降说明分类在学,mAP50 是 IoU 0.5 时的平均精度。如果 box_loss 一直震荡不降,常见原因是学习率太大或标注框质量差。如果 cls_loss 降不下去,多半是类别不平衡,成熟和未熟的样本数量差距太大,可以考虑用 copy-paste 增强少数类。
3.3 类别不平衡与数据增强策略
4 个成熟度等级在自然场景下分布往往不均匀,成熟和半熟的葡萄可能占多数,未熟和过熟的样本偏少。这种不平衡会让模型偏向多数类,表现为少数类的召回率明显偏低。我一般先统计每个类别的框数量,做到心里有数。
from collections import Counter counter = Counter() label_dir = "dataset/labels/train" for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: cls_id = int(line.strip().split()[0]) counter[cls_id] += 1 for cls_id, count in sorted(counter.items()): print(f"类别 {cls_id}: {count} 个框")这段代码遍历训练集所有标签文件,统计每个 class_id 出现的次数。输出后如果发现某个类别只有几百个框,而最多的有几千个,差距超过 5 倍就要处理。常见做法是在 data.yaml 同级配置里加增强参数,比如mosaic、mixup、copy_paste,或者用cls_pw给类别加权。YOLO 训练时默认开启 mosaic,对小目标和不平衡数据都有帮助。如果少数类实在太少,可以考虑过采样,但要注意过采样容易导致过拟合,验证集上表现会虚高。
4. 避坑与排查:标注、路径、显存这三类问题最常翻车
4.1 标签类别索引越界
现象:训练启动时报IndexError: list index out of range或AssertionError: class id out of range。原因:标签文件里的 class_id 超过了 data.yaml 中 nc 定义的范围,比如 nc=4 但标签里出现了 4 或更大的数字。解决:写个脚本扫描所有标签文件,找出最大 class_id,确认是否和 class 文件行数一致。如果标签里有多余类别,要么修正标签,要么在 data.yaml 里补上对应类别。
4.2 图像与标签路径不匹配
现象:训练时提示找不到标签文件,或者 mAP 始终为 0。原因:YOLO 根据图像路径推导标签路径,默认是把 images 替换成 labels,扩展名换成 txt。如果你的目录结构不是这个规则,就会找不到标签。解决:要么按 YOLO 推荐结构重组目录,要么在 data.yaml 里正确配置 train 和 val 路径。我一般会在训练前跑一遍配对检查脚本,确认每张图都有对应标签。
4.3 显存不足导致训练中断
现象:训练几轮后报 CUDA out of memory。原因:batch 太大、imgsz 太高,或者数据加载器缓存过多。解决:先把 batch 减半,如果还不行就把 imgsz 从 640 降到 416 或 320。另外可以设置workers少一点,减少数据加载进程占用的内存。如果用的是共享服务器,注意有没有其他进程占着显存,nvidia-smi看一眼心里有数。
4.4 可视化时框偏移或类别错乱
现象:show.py 画出来的框位置不对,或者类别名和实际内容不符。原因:坐标归一化反算时用错了图像尺寸,或者 class 文件顺序和标签里的 class_id 不对应。解决:确认img.shape取的是高和宽,不要搞反。类别错乱就重新核对 class 文件顺序,确保第 0 行对应 class_id 0,以此类推。如果 class 文件有 BOM 头,用 utf-8-sig 读取。
4.5 验证集指标虚高
现象:验证集 mAP 很高,但实际测试时效果很差。原因:训练集和验证集划分时没有按图像划分,同一张图的增强版本同时出现在两个集合里,造成数据泄露。解决:划分前先按图像去重,确保同一原始图像只出现在一个集合中。另外检查有没有重复图像,7000 张图里如果有大量近似重复,验证集指标参考价值会打折扣。
5. 进阶技巧:用混淆矩阵和单类 AP 定位模型短板
训练跑完不是终点,拿到模型后要知道它到底哪里不行。YOLO 训练结束会在 runs 目录下生成混淆矩阵和各类别 AP 曲线,这两个东西比总 mAP 有用得多。混淆矩阵能看出类别之间有没有互相误判,比如半熟被大量判成成熟,说明这两个类别的特征区分度不够,可能需要更细的标注或者增加这两类的样本。各类别 AP 则能直接告诉你哪个类别拖后腿,如果未熟的 AP 只有 0.3 而其他都在 0.8 以上,那问题就集中在这个类别上。
# 从验证结果中提取单类 AP 并排序 import pandas as pd # YOLO 验证后会输出 results.csv,包含各类别指标 df = pd.read_csv("runs/train/grape_maturity/results.csv") # 假设列名包含 metrics/mAP50(B) 和各类别 AP ap_cols = [c for c in df.columns if "AP" in c and "mAP" not in c] print("单类 AP 列:", ap_cols) for col in ap_cols: print(f"{col}: 最高 {df[col].max():.3f}, 最终 {df[col].iloc[-1]:.3f}")这段代码读取训练输出的 results.csv,筛选出单类 AP 列并打印最高值和最终值。如果某个类别的 AP 在训练后期还在上升,说明还没收敛,可以增加轮数;如果很早就平了甚至下降,说明过拟合了,需要加正则或减轮数。我一般会把混淆矩阵导出成图片,对着看哪些类别容易混。葡萄成熟度这个任务里,半熟和成熟之间的边界本身就模糊,标注时不同人的判断可能都不一致,这种类别混淆靠调模型很难根治,更有效的是统一标注标准或者把两个类别合并再重新训练。
还有一个实用技巧是拿验证集里置信度低的样本单独看。YOLO 验证时可以设置conf阈值,把低于 0.3 的预测框对应的图像挑出来,人工过一遍。这些低置信度样本往往是模型最不确定的,要么是标注有问题,要么是图像质量差,要么是类别本身模棱两可。我上次做类似任务时,就是靠这批低置信度样本发现了一批标注框只框了半串葡萄的漏标情况,修正后 mAP 直接涨了 4 个点。从那以后我每次训完第一版模型,都会强制走一遍低置信度样本复查,这个习惯帮我省下了不少反复调参的时间。希望这份数据集和上面的流程能帮你把葡萄成熟度检测的项目快速跑起来。
本文还有配套的精品资源,点击获取