简介:面向葡萄拣选与品质分选场景的YOLO系列算法目标检测数据集,覆盖斑点葡萄、腐烂葡萄、成熟葡萄与未成熟葡萄四类目标,可直接用于目标检测模型的训练、验证与测试。压缩包共496个文件,包含165张JPG图像、165个YOLO格式TXT标注、165个VOC格式XML标注及1份data.yaml配置,整体大小13.85MB,已按训练需求完成目录划分,无需额外整理转换即可接入YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流版本。标签格式同时提供归一化中心点坐标与宽高的YOLO标准格式,以及XML标注的VOC格式,便于在不同框架间迁移使用。data.yaml中已写明类别与路径,适合刚入门目标检测的开发者一边上手训练一边理解数据组织方式。目前已有101人浏览学习,对于需要构建农业视觉检测原型或进行算法对比实验的读者来说,是一份小而完整的实践数据资源。
1. 一个能直接开训的葡萄数据集:YOLO 与 VOC 双格式标签
做葡萄分拣或果园巡检项目时,最尴尬的就是数据卡脖子:网络结构调好了,训练脚本写好了,COCO 里葡萄样本却只有零星几张,成熟度、腐烂程度这类业务类别根本覆盖不到。这个 yolo 算法葡萄数据集就是冲这个问题来的:165 张果园实拍图像,带完整标签,覆盖拣选点、斑点葡萄、腐烂葡萄、成熟葡萄、未成熟葡萄五类;YOLO txt 和 VOC xml 两种标签格式都备好,训练验证集已划分,data.yaml 也生成完毕。
适合谁用?两类人最省事:正在用 YOLO 系列跑检测、不想把时间耗在标注上的工程师;拿目标检测做毕设、需要解压即训数据的同学。165 张不算多,但结构完整、标签规范,足够你把全流程跑通,再往自己的场景迁移。
2. 动手前先拆包:目录骨架、label 坐标与 data.yaml
这个数据集的核心是“打开就能训”,但打开之前,先花十分钟把内部结构摸清楚。五种类别分别对应一个类别索引,索引由 data.yaml 决定,如果这块不先理清,后面训练跑得再顺,预测结果也可能全是错的。
2.1 解压后的文件骨架与命名规律
下载下来是一个 zip,先进系统解压。Linux 或 mac 环境用 unzip:
unzip yolo算法-葡萄数据集数据集-165张图像带标签-拣选点-斑点葡萄-腐烂的葡萄-成熟葡萄-未成熟葡萄.zip -d grape_datasetWindows 上直接右键解压就行。提醒一句:zip 文件名带中文,Linux 下解压前先确认系统 locale 是 UTF-8,否则解出来的文件名会乱码,后面 data.yaml 里路径全对不上。我一般先把 zip 重命名成 grape.zip 再解压,省掉这一档麻烦。
解压后的目录结构,这类打包一般长这样:
grape_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels-yolo/ │ ├── train/ │ └── val/ ├── labels-voc/ │ ├── train/ │ └── val/ └── data.yaml实际目录名可能略有差异,但逻辑是同一套:images 里放 165 张 jpg,labels-yolo 里放同名 txt,labels-voc 里放同名 xml,data.yaml 描述类别和路径。图像文件名类似 img_0771_16.jpg,img_ 后面第一段基本是拍摄场景编号,下划线后是单图序号;同一个场景的多张图在光照和角度上接近,如果你的后续数据是自采的,最好让同场景图像只出现在训练集或验证集,避免数据泄漏拉高虚假分数。
labels-yolo 和 labels-voc 一一对应,相当于同一个框用两套坐标体系各写了一份。为什么这么干?YOLO 系框架读 txt 最顺,VOC 系框架(比如 Faster R-CNN、SSD)读 xml 最顺,一份数据两边都能用,省得自己写转换。zip 里已经划分好 train/val,训练前不用再手动 split。
2.2 YOLO txt 五个数字的含义
挑一张训练图对应的 txt,打开是这种格式:
# 对应 img_0771_16.jpg 2 0.5123 0.4471 0.1832 0.2414 0 0.6831 0.3256 0.1422 0.1788每行五个数,空格分隔。第一列是类别索引,从 0 开始,五类就是 0 到 4,具体顺序看 data.yaml 的 names;后面四列依次是 x_center、y_center、width、height,全部做了归一化,也就是用真实像素坐标除以图像宽高得到的比例值,范围在 0 到 1 之间。举例:0.5123 乘图像宽度 1280 约等于 656 像素,这就是检测框中心点的 x 坐标;width 列 0.1832 乘 1280 约等于 234 像素,是框的实际像素宽。
提示:txt 里没有类别名,只有索引。类别名和索引的对应关系全部由 data.yaml 决定,改 names 顺序等于把所有标签重新定义了一遍。
这段格式说明不是废话,因为很多人直接拿 txt 跑训练,遇到“训练正常但结果全错”时会茫然,根源就是没理解类别索引与 names 的绑定关系。顺手做一个快速统计,看看每类框数是否合理:
awk '{print $1}' labels-yolo/train/*.txt | sort | uniq -c如果某个类别 id 一次没出现,或者出现了大于 4 的数,说明标签有问题,训练前先查清。
2.3 VOC xml 与 txt 的对应关系
VOC 格式的 xml 核心标签是 object 里的 name 和 bndbox:
<annotation> <filename>img_0771_16.jpg</filename> <size> <width>1280</width> <height>720</height> </size> <object> <name>rotten_grape</name> <bndbox> <xmin>456</xmin> <ymin>215</ymin> <xmax>672</xmax> <ymax>389</ymax> </bndbox> </object> </annotation>xml 里面是像素坐标和类别英文名,人眼可以直接判断这张图标注得对不对,排查具体某张图时比 txt 方便得多;但 YOLO 训练时要求归一化数值加类别索引,所以两套格式里的同一目标,坐标体系不同、语义相同。xml 里的 name 和 data.yaml 的 names 顺序是隐式绑定的:txt 第一列的 2 指的是 data.yaml 里索引为 2 的那个类别名,而 xml 里直接写了名字,转换时靠映射字典做关联。
2.4 data.yaml:训练入口配置
最后看 data.yaml,这是所有 YOLO 系框架的训练入口:
train: D:/datasets/grape/images/train val: D:/datasets/grape/images/val nc: 5 names: 0: pick_point 1: spot_grape 2: rotten_grape 3: ripe_grape 4: unripe_grapetrain 和 val 指向图片目录,注意不是标签目录。ultralytics 会根据图片路径自动到同名的 labels 目录找同名 txt,所以图片和标签的目录名、文件名必须对齐。nc 是类别数,必须等于 names 的长度,写错会让输出维度直接不匹配。names 的索引顺序就是 txt 第一列的语义。
路径写法上,绝对路径最省心,但换机器跑要改成新机器的路径;相对路径比如 images/train,要求终端工作目录必须位于 grape_dataset 下。这个数据集从摘要说明看,适用 yolov5、yolov7、yolov8、yolov9、yolov10、yolo11 全系,yolov8 及之后的版本都读这套 data.yaml,yolov5 的官方仓库同样兼容,字段名一致,直接指过去就能用。
3. 用 YOLOv8 把 165 张图训出第一版权重:命令与超参取舍
3.1 环境与预训练权重准备
用 YOLOv8 训练自己的数据集,最省事的入口是 ultralytics 这个库。有人习惯 YOLOv5 的官方仓库,也能跑,但 ultralytics 把训练、验证、推理统一成了 yolo 命令行,后续切 v11 不用改习惯。安装:
pip install ultralytics建议 Python 3.9 以上。如果机器上已经装过 CUDA 版 PyTorch,先装好 torch 再装 ultralytics,避免 pip 自动拉一个 CPU 版 torch 下来,训练速度差一个数量级。装完顺手验证一下 GPU 是否可用:
python -c "import torch; print(torch.cuda.is_available())"返回 True 就是 GPU 环境,False 就准备用 CPU 慢慢跑。这个数据集 165 张图,CPU 也不是不能跑,但 imgsz 640 的前提下,七八十轮还是有点熬人。
预训练权重直接用官方的 yolov8n.pt,第一次执行训练命令会自动下载。网络不给力的话,提前把权重文件下载好放当前目录,ultralytics 会在本地找到同名文件,不会再走网络。
3.2 启动训练:命令与参数取舍
第 2 章已经把 data.yaml 检查过一遍,现在终端切到 grape_dataset 目录,跑:
yolo detect train \ data=D:/datasets/grape/data.yaml \ model=yolov8n.pt \ epochs=80 \ imgsz=640 \ batch=16 \ device=0 \ patience=20各参数怎么选,我按这个数据集的实际规模给一份参考:
| 参数 | 建议值 | 说明 |
|---|---|---|
| model | yolov8n.pt | 样本只有 165 张,n 级参数量最小,s/m 更容易过拟合 |
| epochs | 60-100 | 带早停的合理区间,不要一上来写 300 |
| imgsz | 640 | 葡萄果粒算中小目标,640 性价比最高 |
| batch | 16 | 8GB 显存附近安全,显存小就降到 8 |
| patience | 20 | val 连续 20 轮不涨就提前停,小数据集很实用 |
几个参数的逻辑:imgsz 调到 960 对更小的果粒召回有提升,但训练时间和显存占用翻倍,165 张图建议先用 640 把流程跑通,再回来调分辨率。batch 不是越大越好,小数据集上 batch 太大反而更容易收敛到尖锐极值,16 够用。patience 写成 20,val 连续 20 轮不涨就直接停,省下来的时间拿去分析错误样本。
调参这东西一半是玄学,但有几个原则不玄:先保证 loss 和 mAP 曲线正常,再谈刷分;先确认各类别都有召回,再谈总 mAP。
3.3 训练产物与曲线判断
训练结束,产物在 runs/detect/train/ 下,最关键的是 weights 里的 best.pt 和 last.pt。best 是按验证集 mAP 保存的最优权重,last 是最后一轮;后续验证、推理都直接用 best.pt。
results.png 把每轮的 loss、mAP、P/R 画在一起。小数据集的典型曲线是:训练 loss 一路降,val loss 前 20 轮跟着降,然后拐头向上——这就是过拟合信号。这时候优先加大数据增强而不是加模型复杂度,或者干脆用 best.pt 收工。除了 loss 曲线,labels.jpg 和 labels_correlogram.jpg 这两张图值得看,它们画出所有标注框的位置和尺寸分布,能帮你快速发现框中心点挤在图像边缘、框尺寸异常这类标签问题,属于训练日志里最容易忽略的黑匣子。
3.4 验证与推理:看指标更要看图
验证集上跑一轮正式评估:
yolo detect val \ data=D:/datasets/grape/data.yaml \ model=runs/detect/train/weights/best.pt输出里 mAP50 和 mAP50-95 看一眼即可。165 张图、5 个类别,不要指望迁移学习一轮就刷到 0.9,重点看每个类别的 recall 是否均衡。推理看实际效果:
yolo predict model=runs/detect/train/weights/best.pt source=D:/datasets/grape/images/val预测图像输出在 runs/detect/predict 下,一张一张打开看:框贴不贴果粒、类别标得对不对、腐烂和斑点会不会互相混淆。这一步比任何指标都直观,也是决定要不要回头调标注的根据。
4. 标签格式互转:XML 转 TXT 脚本与可视化校验
4.1 为什么自己还需要转格式
数据集已经准备了两种格式,直接拿来训练就行。但实际场景里,你很可能要用自己的图像扩数据——从其它采集批次里挑出斑点葡萄、腐烂葡萄的样本,而这些数据的标注可能是 JSON(LabelMe)或 XML(VOC)。把 XML 转成 YOLO txt 是绕不开的一步。
另一个用途是校验:转换过程能暴露标注问题,比如某个框宽高为 0、坐标超边界、类别名和 names 对不上,这些都是训练时的隐形炸弹。
4.2 XML 转 YOLO TXT 的 Python 实现
import xml.etree.ElementTree as ET import os # 类别顺序必须与 data.yaml 中的 names 一致 CLASS_MAP = { "pick_point": 0, "spot_grape": 1, "rotten_grape": 2, "ripe_grape": 3, "unripe_grape": 4, } def xml_to_yolo(xml_path, txt_path): tree = ET.parse(xml_path) root = tree.getroot() 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"): name = obj.find("name").text if name not in CLASS_MAP: print(f"跳过未知类别: {name}") continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) # 批量转换 xml_dir = "labels-voc/train" txt_dir = "labels-yolo/train" os.makedirs(txt_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if xml_name.endswith(".xml"): xml_to_yolo( os.path.join(xml_dir, xml_name), os.path.join(txt_dir, xml_name.replace(".xml", ".txt")) )脚本先解析 XML 的 size 拿图像宽高,再遍历 object 列表,把 bndbox 的像素坐标归一化成比例值。核心计算就四行:中心点坐标 = 左右相加除 2 再除以图像宽;宽高 = 像素差除以图像宽高。CLASS_MAP 是关键,如果数据集 data.yaml 的 names 顺序和你写的不一致,转换结果就错位,所以动手前先打印一份 data.yaml 的 names。
边界问题:如果某些 xml 的 bndbox 缺失,或者节点值不是数字,float() 会抛异常。建议用 try/except 包住循环体,打印文件名和 object 信息,再决定修标注还是删掉这条记录。
4.3 画框回显:用 OpenCV 检查标签对齐
转换完,拿一张图和 txt 画框:
import cv2 img_path = "images/train/img_0771_16.jpg" txt_path = "labels-yolo/train/img_0771_16.txt" img = cv2.imread(img_path) if img is None: raise FileNotFoundError(f"图像读取失败: {img_path}") h, w = img.shape[:2] with open(txt_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls, cx, cy, bw, bh = map(float, parts) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite("check_visual.jpg", img)用 cv2.imwrite 而不是 imshow,因为很多人是在服务器或云主机上跑,没有显示器。框画出来后,对照原图检查三点:框是否贴住果粒边缘、类别标号和视觉是否一致、有没有框画到图像外。cx、cy 超出 0-1 范围的标注行会直接画到图外,说明原始标注有脏数据,训练前必须清理。
5. 避坑篇:葡萄小样本训练的高频翻车现场与修复
这一章是血泪经验集中地。165 张图的小数据集,训练本身不难,难的是各种环境问题和标签问题,挑四个最常遇到的展开。
5.1 mAP 一直是 0:标签路径或类别索引错位
现象:训练能跑,loss 也在降,但验证阶段 mAP50 始终是 0,预测结果一片空白。
原因:最常见的是两种。一种是 data.yaml 里 train/val 路径配错了,ultralytics 没找到 txt 标签,自动把全图当作背景训练;另一种是 txt 里的 class id 和 data.yaml 的 names 顺序对不上,比如 txt 第一列写着 0,但 data.yaml 里 0 是 pick_point,模型学到的语义和标注不一致。
解决:先停训练,到 labels-yolo/train 随便打开一个 txt,看第一列最大值是否小于 nc;再核对 data.yaml 的 names 顺序。最稳的是用第 4 章的画框脚本,把 labels-yolo 的标签真实画到图上,肉眼看类别 id 对不对。这一步能拦截掉绝大多数“训练正常但全错”的问题。
5.2 中文路径导致图片读不进
现象:数据集放在 D:\桌面\葡萄数据集\grape 这种路径下,训练开始后日志报 FileNotFoundError,或者某个 epoch 开始全部 loss 变 0。
原因:ultralytics 底层走 OpenCV 和路径拼接,Windows 下中文字符路径容易出现编码错乱,cv2.imread 返回 None,图像进不去,训练自动跳过。这跟数据集本身没关系,纯环境问题。
解决:把整个数据目录挪到纯英文路径,比如 D:\datasets\grape,data.yaml 里的 train、val 同步改成绝对英文路径。另外 zip 包解压时如果文件名带中文,先重命名 zip 再解压,文件名也保持英文,从头避免编码问题。
5.3 小数据集跑到 300 轮必过拟合
现象:训练 loss 一路下降,val loss 到 40 轮还在降,60 轮之后拐头向上,mAP 停在 0.7 上下不再涨,best.pt 保存的却是很早的轮次。
原因:165 张图对小检测模型来说样本量严重不足,300 epoch 足够模型“背”下训练集。葡萄果粒视觉模式相近,成熟和未成熟之间边界模糊,背书式的过拟合更容易发生。
解决:epochs 控制在 60-100,打开 mosaic、mixup 增强,用 yolov8n 而不是 yolov8m。还有一个习惯:训练命令里显式写 patience=20,val 连续 20 轮不降就提前收工,省下来的时间拿去补数据或调标注。
5.4 训练中断 nan:txt 里藏着越界或零尺寸标注
现象:训练跑到某个 epoch,loss 突然变成 nan,程序直接中断,重启训练后一样。
原因:个别 txt 标注行里 width 或 height 为 0,或者中心点坐标超出 0-1(比如 1.2),IoU 计算时除零或产生无效梯度,模型参数直接冲爆。半自动标注生成的数据集里常有这种行。
解决:训练前扫一遍标签:
awk '{if($2<0||$2>1||$3<0||$3>1||$4<=0||$5<=0) print FILENAME, $0}' labels-yolo/train/*.txt把输出里对应的那几张图单独挑出来,人工确认标注是否有问题,修正后重新生成 txt。我一般把这段 grep 写成一个固定脚本,每次换数据集先跑一遍再训练,这个习惯帮我躲过很多次训练中断。
6. 进阶:少数类漏检怎么办?从混淆矩阵到实例粘贴增强
6.1 训练前先看类别分布
训练完第一版,先别急着调结构。打开 runs/detect/train/ 下的 confusion_matrix.png,如果腐烂葡萄那一列大量落进“背景”或“斑点葡萄”,说明模型根本没把它当独立类别学。这时回看数据分布:五个类里成熟葡萄可能占了大半,腐烂葡萄只有零头,类别不均衡直接导致 recall 偏科。
6.2 用混淆矩阵定位漏检规律
混淆矩阵是定位漏检规律最快的手段。如果腐烂葡萄被大量预测成斑点葡萄,说明这两类在图像上长得太像,标注边界也没有统一标准,需要回到标签上做清洗,而不是加网络复杂度;如果腐烂葡萄大量预测成背景,说明样本量太少,该走扩充路线而不是调参路线。
6.3 实例粘贴增强:少数类训练集扩充
复制粘贴增强是我常用的做法,把少数类实例从原图抠出来,随机贴到训练图的空白区域:
import cv2, random def paste_instance(src_img, obj_img, box): h, w = src_img.shape[:2] oh, ow = box[3] - box[1], box[2] - box[0] x0 = random.randint(20, w - ow - 20) y0 = random.randint(20, h - oh - 20) src_img[y0:y0 + oh, x0:x0 + ow] = obj_img return src_img, (x0, y0, x0 + ow, y0 + oh)再把新框坐标归一化后追加到对应 txt 里。注意两点:贴的位置不要盖住已有目标,粘贴面积别超过原图 20%,否则模型学到的是“巨大目标”而不是“腐烂葡萄实例”。虽然 165 张图的增强终究替代不了真实采样,但对稳住少数类的 baseline 非常有用。
我自己做葡萄分拣那阵,第一版模型对腐烂葡萄的召回率只有 0.31,翻车的不是网络结构,而是数据分布。从那以后,我每次拿到新数据集,都强制先跑一遍类别统计和混淆矩阵,再决定调参方向。希望帮到你。
本文还有配套的精品资源,点击获取