简介:这是一份芒果实例分割数据集,已转换为YOLOv8格式,可直接用于目标检测与实例分割模型的训练和验证,面向计算机视觉、深度学习和智慧农业相关开发者。数据内容围绕芒果个体识别展开,可服务于精准农业中的生长监测、自动化采摘机器人定位、果实分级质检、病虫害筛查以及储运环节的实时监控等应用场景。压缩包共1088个文件,其中主要为542张PNG图像和542个对应的TXT标签文件,并附带train.json、test.json等数据集配置说明及cache缓存文件,包体大小187.03MB,结构清晰便于直接接入YOLOv8训练流程。数据划分包含453条训练数据和91条验证数据,能够支撑常规训练与效果评估。目前已有761人学习下载,适合需要快速获取标注数据以开展芒果目标检测或实例分割项目的研究者与工程人员使用。
1. 芒果实例分割数据集:把果园场景拆成一颗一颗可操作的目标
做农业视觉项目最头疼的不是模型选型,而是标注数据有没有对到业务上。这个芒果实例分割数据集一共 544 张真实果园 RGB 图像,训练集 453 张、验证集 91 张,图像来自 Azure Kinect 相机在固定曝光和 5fps 采样下的采集流(文件名里的azure_rgb_5fps_2500expo就是这个意思)。相比普通目标检测框,实例分割要额外输出每个芒果的轮廓掩码,这对采摘机器人定位果柄、成熟度分级和遮挡场景下的计数都有直接价值。数据量不大,但场景单一、类目只有芒果一类,正好适合跑通 YOLOv8 实例分割的完整链路,也可以用来检验小数据集上的迁移学习和数据增强策略是否有效。
如果你是刚接触 YOLOv8 实例分割,手里只有这份 JSON 标注,这篇文章会带你完成格式核对、标签转换、配置文件编写和训练调参;如果你已经跑过 COCO 或别的分割训练,我还会说几个容易在遥控器阶段翻车的细节,比如 cache 文件怎么检查、验证集太小导致的波动怎么处理。
2. 先搞清楚芒果数据集的文件格式:COCO JSON、缓存文件与 YOLOv8 标签的关系
2.1 从文件列表反推数据来源与格式
打开压缩包后你会看到这些文件:
| 文件 | 类型 | 作用 |
|---|---|---|
train.json | COCO 风格标注 | 训练集标注,含 images/annotations/categories |
test.json | COCO 风格标注 | 验证集标注,结构与 train.json 一致 |
train.cache/test.cache | PyTorch 缓存 | ultralytics 扫描数据集后生成的注册表,避免每次启动都重新解析 |
azure_rgb_5fps_2500expo_*.png | 图像 | 图像本身,命名体现了采集配置 |
json里存的是多边形坐标和边界框,而 YOLOv8 的segment模式默认要求标签是txt文件,每行格式为class x1 y1 x2 y2 x3 y3 ...。这里的train.cache说明数据被 ultralytics 工具扫描过,但如果你的环境里 cache 是旧的或缺失的,就需要从 JSON 生成正确的 YOLO 标签文件。注意不要拿 COCO 格式的 JSON 直接填进data.yaml就给 YOLOv8 训练,你可能会看到 "no labels found" 或全部类别为空的结果。
2.2 YOLOv8 实例分割标签与 COCO JSON 的差异
在 YOLOv8 实例分割任务中,labels目录下每个.txt对应一张图像,每行代表一个实例。前四个数值是归一化边界框中心点坐标和宽高(xc, yc, w, h),随后是归一化多边形顶点坐标。COCO JSON 里则保存的是未归一化的bbox和segmentation列表。这里的差异不仅仅是单位换算,还牵扯到多边形是否闭合、边界是否裁剪到图像尺寸内。
另外,COCO 的segmentation可能有多个多边形(例如一个芒果被树叶部分遮挡被标成两段),而 YOLOv8 的segment标签要求一个实例对应一组闭合坐标。遇到这种数据,在转换前要决定是取最大轮廓还是合并所有轮廓,否则训练时 mask 会变形。
2.3 写一个转换器:把 train.json 转换成 YOLO 分割标签
我通常先写一个独立的转换脚本,不依赖 ultralytics 内部工具,方便处理特殊数据。下面这段代码基于 COCO 格式,遍历所有标注并输出为 YOLO segment 格式:
import json import os import numpy as np def coco_to_yolo_seg(json_path, img_dir, out_label_dir, img_width=1280, img_height=720): """ 将 COCO 风格 JSON 转换为 YOLOv8 实例分割格式 :param json_path: COCO 标注文件 :param img_dir: 图像所在目录 :param out_label_dir: 输出的标签目录 """ with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) # 建立 image_id 到文件名的映射 img_id_to_name = {img['id']: img['file_name'] for img in data['images']} # 建立 category_id 到 class_id 的映射,这里只有芒果,所以固定给 0 cat_id_to_class = {cat['id']: i for i, cat in enumerate(data['categories'])} os.makedirs(out_label_dir, exist_ok=True) # 按 image_id 聚合所有标注 anns_by_img = {} for ann in data['annotations']: img_id = ann['image_id'] anns_by_img.setdefault(img_id, []).append(ann) for img_id, anns in anns_by_img.items(): file_name = img_id_to_name[img_id] label_path = os.path.join(out_label_dir, file_name.replace('.png', '.txt')) lines = [] for ann in anns: cls_id = cat_id_to_class[ann['category_id']] bbox = ann['bbox'] # [x, y, w, h] 未归一化 x, y, w, h = bbox # 过滤掉无效或过小的框,避免训练崩溃 if w < 1 or h < 1: continue # 归一化边界框中心点 cx = (x + w / 2) / img_width cy = (y + h / 2) / img_height nw = w / img_width nh = h / img_height # 取 segmentation 中的第一个多边形 seg = ann['segmentation'][0] # 严格做一次裁剪与归一化,防止顶点越界 points = [] for i in range(0, len(seg), 2): px = min(max(seg[i], 0), img_width) / img_width py = min(max(seg[i + 1], 0), img_height) / img_height points.append(f"{px:.6f} {py:.6f}") # 每行至少要有一个多边形闭环(4 个点以上) if len(points) < 4: continue line = f"{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f} " + " ".join(points) lines.append(line) if lines: with open(label_path, 'w', encoding='utf-8') as f: f.write("\n".join(lines)) if __name__ == "__main__": coco_to_yolo_seg( "train.json", "./images/train", "./labels/train", img_width=1280, img_height=720 )这段脚本的核心是把 COCO 的[x, y, w, h]改成中心点坐标,并把多边形顶点全部归一化。这里img_width和img_height需要和实际图像尺寸一致,如果你的数据集图像不是固定分辨率,建议在循环里用cv2.imread(file_name).shape动态获取。写完后不要急着训练,先数一数生成的.txt数量和json中的图片数量是否一致,再用下面的校验脚本检查随机抽样结果。
2.4 数据校验:避免边界框和分割掩码错位
转换后最常见的故障是标签顺序和图像错位,也就是txt文件名与png对应不上。YOLO 会把文件名去掉扩展名后作为 key,如果图像是azure_rgb_5fps_2500expo_539.png,那么标签必须是azure_rgb_5fps_2500expo_539.txt,不能多一个空格或改大小写。
import os import random img_dir = "images/train" label_dir = "labels/train" imgs = [f for f in os.listdir(img_dir) if f.endswith(".png")] labels = [f for f in os.listdir(label_dir) if f.endswith(".txt")] img_stems = {os.path.splitext(f)[0] for f in imgs} label_stems = {os.path.splitext(f)[0] for f in labels} missing_labels = img_stems - label_stems orphan_labels = label_stems - img_stems print(f"缺失标签数: {len(missing_labels)}") print(f"多余标签数: {len(orphan_labels)}") # 随机抽 5 个标签,打印归一化坐标范围,检查是否在 0~1 内 for lbl in random.sample(list(label_stems), min(5, len(label_stems))): with open(os.path.join(label_dir, lbl + ".txt")) as f: first_line = f.readline().strip() print(lbl, first_line)如果缺失标签数很多,说明转换脚本里anns_by_img分组没有覆盖全,或者 JSON 中有iscrowd标注没被过滤。一般 COCO 数据集中iscrowd=1的物体是不参与实例分割训练的,这类标注应该跳过。
3. 配置数据集 YAML 并启动 YOLOv8 训练:453 条训练数据怎么用
3.1 目录组织与 data.yaml
YOLOv8 训练时不会直接读 JSON,而是通过data.yaml知道图像和标签放在哪里。推荐按下面结构组织:
mango_dataset/ ├── images/ │ ├── train/ │ │ ├── azure_rgb_5fps_2500expo_534.png │ │ └── ... │ └── val/ │ ├── azure_rgb_5fps_2500expo_549.png │ └── ... ├── labels/ │ ├── train/ │ │ ├── azure_rgb_5fps_2500expo_534.txt │ │ └── ... │ └── val/ │ ├── azure_rgb_5fps_2500expo_549.txt │ └── ... └── mango.yamldata.yaml的内容如下:
path: /path/to/mango_dataset train: images/train val: images/val names: 0: mango注意names里的索引必须和转换脚本里的 class_id 对应。这里只有一个类别,所以0: mango。如果你的 JSON 里还有别的类别,需要先跑一段代码统计categories列表,别凭想象写。
3.2 训练命令与关键参数说明
使用 ultralytics 官方包训练实例分割模型,命令如下:
yolo segment train \ model=yolov8s-seg.pt \ data=mango.yaml \ epochs=100 \ batch=8 \ imgsz=640 \ device=0 \ patience=20 \ project=./runs \ name=segment_mango参数含义如下:
| 参数 | 值 | 作用 |
|---|---|---|
model | yolov8s-seg.pt | 预训练分割模型,s 是 small,比 n 精度高,比 m 和 l 快 |
data | mango.yaml | 数据集配置文件路径 |
epochs | 100 | 训练轮数,小数据集建议先跑 100 |
batch | 8 | 输入并行数量,根据显存调整,GTX 1660Ti 跑 8 张 640 图像没问题 |
imgsz | 640 | 输入分辨率,芒果目标不算太小,640 够用 |
patience | 20 | 早停轮数,验证集 mAP 20 轮不涨就停 |
device | 0 | 使用第一张 GPU,CPU 训练可改为cpu |
训练结束后,runs/segment_mango下会生成best.pt和last.pt,同时有results.png包含 loss 曲线和 mAP 曲线。用yolo segment val单独验证:
yolo segment val model=runs/segment_mango/best.pt data=mango.yaml device=03.3 从 C2f 模块理解小数据集的训练开销
YOLOv8 的骨干网络用了 C2f 结构,替换了 YOLOv5 中的 C3。C2f 通过多个分支的 Cross-stage connection 增强梯度流,参数量比 C3 略高但计算量更可控。这个设计在 COCO 这类大目标数据集上收益明显,但在只有 544 张图的芒果场景里,你不需要用到yolov8x-seg这种大模型,更合理的起点是yolov8n-seg或yolov8s-seg。模型越大,小数据集上越容易早停后过拟合,而且训练时间翻倍,收益却极小。
我一般在拿到这种小数据集时先跑 30 个 epoch,只看best.pt的验证集 mAP50-95。如果 mAP50 能到 80 以上,再跑完整 100 轮;如果连 60 都不到,优先检查标签转换和数据增强,而不是换大模型。
4. 实例分割训练中的典型问题:小数据集、遮挡与验证集划分
4.1 类别不平衡和标签质量导致 mAP 波动
这个数据集只有一类,不存在类别不平衡问题。但 91 张验证集意味着每张图出现或缺少一个芒果都对 mAP 影响巨大。你可能会看到验证集 mAP 在几个 epoch 之间从 0.85 跳到 0.71,这是小验证集的正常抖动,不代表模型崩溃。
更好的做法是确认test.json中每张图像的平均实例数。写一个小脚本统计:
import json with open("test.json") as f: data = json.load(f) num_imgs = len(data['images']) num_anns = len(data['annotations']) print("验证集图像数:", num_imgs) print("实例总数:", num_anns) print("平均每张图实例数:", round(num_anns / num_imgs, 2)) # 统计边界框面积分布,过滤掉转换时可能丢掉的极小白点 from collections import Counter area_bins = Counter() for ann in data['annotations']: bbox = ann['bbox'] area = bbox[2] * bbox[3] if area < 32 * 32: area_bins['small'] += 1 elif area < 96 * 96: area_bins['medium'] += 1 else: area_bins['large'] += 1 print("面积统计:", dict(area_bins))如果small占比很高,说明数据里存在大量远距离小芒果,实例分割在边缘轮廓上会非常吃力。这时应该降低验证置信度阈值conf=0.001,否则默认 0.25 的阈值会把许多真实小目标过滤掉,导致 mAP 被低估。
4.2 小数据集上的数据增强参数调节
ultralytics 的默认增强对一般场景有效,但 453 张训练图在 100 轮内会反复看到同一张图,过强的几何增强反而让掩码学习不稳定。我一般会这样调整:
yolo segment train \ model=yolov8s-seg.pt \ data=mango.yaml \ epochs=100 \ batch=8 \ imgsz=640 \ hsv_h=0.015 \ hsv_s=0.5 \ hsv_v=0.4 \ degrees=10 \ translate=0.1 \ scale=0.4 \ fliplr=0.5 \ mosaic=0.5degrees=10表示最多旋转 10 度,芒果是自然物体,旋转增益不大;mosaic=0.5降低马赛克增强的使用概率,因为小数据集本身场景就单一,过度拼接反而会让模型学到拼接缝特征。如果你的图像是从视频里按 5fps 截取的,相邻帧之间的芒果形态非常接近,此时更强的scale=0.5或translate=0.2能模拟不同拍摄距离。
4.3 验证集只有 91 条,怎么读结果可可靠
不要只盯着val/目录生成的最后一行 mAP。建议用yolo segment val时加上save_json=True,把验证结果输出成predictions.json,然后用代码统计不同置信度区间和尺寸下的性能:
yolo segment val \ model=runs/segment_mango/best.pt \ data=mango.yaml \ conf=0.001 \ save_json=True打开生成的predictions.json,每个目标会带有category_id,score,bbox和segmentation。你可以按置信度排序,找出那些预测分数低但真实存在的芒果,看它们是不是被树枝遮挡或者光照太强。这个检查通常比调参数更有反馈价值。
5. 用 train.cache 快速定位训练数据异常:一个能省半天的小技巧
训练中途遇到过 "Dataset not found" 或者部分图片没参与训练的情况吗?与其从头扫描图像列表,不如直接读 YOLOv8 生成好的.cache文件。这个文件用torch.save保存,本质是一个字典,记录了每张图像的路径、尺寸、标签框数量、类别标签和分割多边形信息。只要你的代码路径不变,train.cache就能复用,省掉每次启动的数据解析时间。
import torch import time def inspect_cache(cache_path): start = time.time() data = torch.load(cache_path, weights_only=False) print(f"加载 cache 耗时: {time.time() - start:.2f}s") print("cache 结构字段:", list(data.keys())[:5]) # 检查是否存在 'labels' 和 'images' 键 for key in ['labels', 'images']: if key in data: item = data[key] print(f"[{key}] 数量:", len(item)) if key == 'labels': # 打印前 3 条数据的内容结构 for i, (img_id, info) in enumerate(item.items()): if i >= 3: break print(img_id, "标签框数:", len(info['bbox'])) # 找到异常:没有框的图像 if 'labels' in data: img_without_labels = [k for k, v in data['labels'].items() if len(v['bbox']) == 0] print("\n无标签图像数量:", len(img_without_labels)) if img_without_labels[:5]: print("示例:", img_without_labels[:5]) inspect_cache("train.cache")这个脚本最实用的地方是快速确认 cache 中的标签数量和实际 txt 是否一致。如果 cache 显示有图无标签,那说明你的labels目录里存在空 txt 或转换丢失。另外,当你调整过train.json再训练时,一定记得删除旧的train.cache和test.cache,否则 ultralytics 会按cache里的旧信息加载数据,等于白改。
还有一个更容易被忽略的坑:使用远程服务器或 NAS 挂载数据时,cache 文件会包含绝对路径。如果你把数据集从/data/mango换到/workspace/mango,不删 cache 的话,yolo 训练时会报路径不存在的错误。这种情况下先删除 cache,再设定环境变量DATASET_CACHE或直接重跑训练,让 YOLOv8 重新扫描并生成新 cache。每次我换机器或换目录,第一件事就是find . -name "*.cache" -delete,然后再启动训练,这个习惯省掉了我很多次“明明代码没错但数据读不到”的排查时间。
本文还有配套的精品资源,点击获取