news 2026/10/11 20:20:38

番茄实例分割数据集实操:从COCO转YOLO-seg到训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
番茄实例分割数据集实操:从COCO转YOLO-seg到训练避坑指南

简介:这份番茄实例分割数据集面向农业AI、计算机视觉与智能农业开发者,包含1286张真实农业环境采集的JPEG图像,覆盖坏番茄、好番茄、绿番茄和茎部四个类别,每个实例均带精细多边形标注,以YOLO格式组织,可直接用于实例分割模型训练与迁移学习。四类目标涵盖番茄不同成熟度与部位,能帮助模型学习外观差异,支撑农业机器人采摘、田间质量监测、学术研究等应用场景。压缩包共2000个文件,其中1286个txt标注文件与712个jpg图像一一对应,txt内为各实例多边形顶点坐标,另附1个yaml数据配置和1个docx说明文档,整体约961.94MB,目录结构清晰,便于导入主流深度学习框架使用。已有83人学习下载,尤其适合农业AI监控系统开发、智能农业机器人集成及高校农业视觉课题,可帮助快速搭建检测分割模型,提升真实农田环境下的鲁棒性。

1. 番茄实例分割数据集:做采摘机器人前,先把这包数据拆明白

做温室采摘机器人的时候,最头疼的不是机械臂抓得准不准,而是视觉系统根本分不清眼前这一串到底是几个番茄。用 yolo 目标检测框住一整串时,成熟果、青果、叶子全混在一个框里,类别对但边界全靠猜,果实计数和采摘点定位双双翻车。番茄实例分割数据集就是为这个场景准备的:每个番茄都标成独立实例,用多边形给出像素级边界,成熟果和青果分开类别,能直接喂给 YOLOv8-seg 或 Mask R-CNN 这类分割模型。对做农业视觉、采摘机器人、果实测产的人来说,这包数据是能省下两三个月标注时间的后悔药。压缩包拿到手先别急着训练,按下面几步把它拆干净,再谈炼丹。

2. 拆开 zip 先体检:目录结构、标注格式与样本家底统计

2.1 解压与目录结构盘点

下载回来的 zip 不是直接扔进训练脚本就完事,我一般先建一个干净目录再解压,避免文件散落得到处都是。zip 解压这一步没什么技术含量,但目录组织决定了后面所有命令的路径写法,值得认真看一眼。

unzip tomato_seg_dataset.zip -d tomato_seg find tomato_seg -maxdepth 2 -type d | sort

unzip -d指定了解压目标目录,这样 zip 里即使有多层嵌套目录,也都被收纳在tomato_seg下面,不会污染当前工作目录。find输出的是两层以内的目录树,重点确认两件事:图片放在哪个目录,标注放在哪个目录。

农业视觉数据集最常见的组织方式是images/和annotations/平级,图片是温室里不同光照、不同角度拍的 JPG,标注是 JSON;另一种是images/配labels/,每个图片对应一个同名 txt,这是 YOLO 系列的直读格式。这份番茄数据集解压后大概率是第一种,图片分辨率集中在 640 到 1920 之间,单张里果实数量从几个到几十个不等,重叠和叶片遮挡是常态。先看清目录结构,后面统一格式时才知道该往哪个路径写。

2.2 先判断标注格式:COCO JSON 还是 YOLO txt

拿到标注文件第一件事是判断格式,不要上来就写转换脚本。一个命令就能区分:

ls tomato_seg/annotations/ head -c 400 tomato_seg/annotations/*.json | python -m json.tool | head -30

如果annotaions目录下面是.json,打开看内容。重点找两个字段:images里记录了每张图的文件名和宽高,annotations里有segmentation。当segmentation的值是一个 list of list,比如[[345.2, 187.4, 362.1, 188.9, ...]],说明是 COCO 多边形格式,每个内层列表是一串 [x1, y1, x2, y2, ...] 的绝对坐标,直接对应图片像素位置。如果看到counts和size字段,那是 RLE 编码,属于 COCO 的另一种掩膜表达,转 YOLO 之前得先用pycocotools的mask.decode把 RLE 换成二进制掩膜,再找轮廓点。

判断错了格式,后面的脚本全白写。这一步花两分钟,能避免后面所有转换环节的连锁翻车。

2.3 用脚本摸清家底:类别分布、实例数量与图片分辨率

在动手转换之前,我会先跑一个统计脚本,把这份行业数据集的实际规模摸清楚。这不是走过场——类别分布直接决定后面要不要做样本均衡,分辨率范围决定训练时imgsz选 640 还是 960。

import json, glob from collections import Counter cat_counter = Counter() inst_total = 0 img_n = 0 img_w = [] img_h = [] for ann_path in sorted(glob.glob("tomato_seg/annotations/*.json")): with open(ann_path, "r", encoding="utf-8") as f: data = json.load(f) cat_map = {cat["id"]: cat["name"] for cat in data["categories"]} for ann in data["annotations"]: inst_total += 1 cat_counter[cat_map.get(ann["category_id"], "unknown")] += 1 for img in data["images"]: img_n += 1 img_w.append(img["width"]) img_h.append(img["height"]) print("图片数量:", img_n) print("实例总数:", inst_total) print("类别分布:", dict(cat_counter)) print("分辨率范围: {}x{} ~ {}x{}".format(min(img_w), min(img_h), max(img_w), max(img_h)))

脚本逻辑不复杂:遍历所有 json,用category_id去categories里查类别名,逐条累加;顺带记录每张图的宽高。输出里最值得盯的是类别分布,如果mature_tomato有上万实例而immature_tomato只有几百,训练时青果类别大概率学不动,后面要提前做处理。分辨率范围的作用是给imgsz定档:全部在 1280 以下的图用 640 训练完全够,如果存在 2000 以上的大图,可以考虑训练时开到 960,但显存和训练时长也要跟着涨。

3. 统一成 YOLO-seg 格式:转换脚本与标注校验

3.1 为什么选 YOLO-seg 而不是直接训 COCO

不少新手拿到这份数据会直接去找 Mask R-CNN 的 mmdetection 配置,跑通一轮就花掉半天。我的习惯是先统一到 YOLO-seg 格式,理由很实际:

维度COCO JSON + Mask R-CNNYOLO-seg txt + Ultralytics
数据增强需要自己配置管线,mask 是否同步变换要看代码内置 mosaic、flip、copy-paste,mask 自动同步
训练入口配 mmdet 的 config,改 backbone、head一行命令指定模型和 yaml
部署导出转 ONNX 要单独处理 mask head 和 roi alignyolo export一条命令,NMS 封装好
可视化要写 pycocotools 的 show 逻辑model.predict(save=True)输出带掩膜图

对于番茄这种目标密集、互相遮挡的场景,训练时增强掩膜必须跟着图一起变换,Ultralytics 的生态在这点上做得省心。它不是精度上限最高的方案,但它是从数据到部署链路最短的方案,适合先把基线跑起来。

3.2 COCO JSON 转 YOLO-seg txt:转换脚本与参数说明

转换的原理一句话讲清:把 COCO 多边形里的绝对坐标,除以图片宽高得到 [0,1] 之间的归一化坐标,按类别id x1 y1 x2 y2 ...的格式写进 txt,一个实例占一行。下面是完整脚本:

import json, os, glob CLASS_MAP = {"mature_tomato": 0, "immature_tomato": 1} IMG_DIR = "tomato_seg/images" ANN_DIR = "tomato_seg/annotations" OUT_DIR = "tomato_seg/labels" os.makedirs(OUT_DIR, exist_ok=True) def normalize_polygon(polygon, img_w, img_h): """把 COCO 绝对坐标多边形归一化到 [0,1]""" if len(polygon) < 6: return None points = [] for i in range(0, len(polygon), 2): x = polygon[i] / img_w y = polygon[i + 1] / img_h points.append(round(x, 6)) points.append(round(y, 6)) return points for ann_path in sorted(glob.glob(os.path.join(ANN_DIR, "*.json"))): with open(ann_path, "r", encoding="utf-8") as f: data = json.load(f) img_map = {img["id"]: img for img in data["images"]} cat_map = {cat["id"]: cat["name"] for cat in data["categories"]} img_to_anns = {} for ann in data["annotations"]: img_to_anns.setdefault(ann["image_id"], []).append(ann) for img_id, anns in img_to_anns.items(): img = img_map[img_id] base = os.path.splitext(os.path.basename(img["file_name"]))[0] out_path = os.path.join(OUT_DIR, base + ".txt") lines = [] for ann in anns: cls_id = CLASS_MAP.get(cat_map.get(ann["category_id"])) if cls_id is None: continue if not ann["segmentation"]: continue polygon = ann["segmentation"][0] norm = normalize_polygon(polygon, img["width"], img["height"]) if norm is None: continue lines.append(f"{cls_id} " + " ".join(map(str, norm))) if lines: with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))

脚本里几个参数要按实际数据改:CLASS_MAP的键名要和 json 里categories的 name 严格一致,值从 0 开始连续编号;IMG_DIR和ANN_DIR换成解压后的实际路径。normalize_polygon里我加了一个len(polygon) < 6的判断,多边形至少 3 个点,少于 6 个数值的标注是有问题的,直接丢弃比让训练时报错更干净。

关于ann["segmentation"][0]我要多说一句:COCO 允许一个实例有多个多边形块,比如番茄被叶子挡住一半,标注可能拆成两块。YOLO-seg 的一行只能表达一个闭合多边形,直接取第一块会把同一个果实当成两个实例训练。常见做法是取面积最大的一块,或者用cv2.convexHull把多块合并成外接凸包,后者对采摘点估算更友好。

3.3 校验转换结果:可视化叠加与坐标越界检查

转换脚本跑完不要直接开训,先做一次可视化校验。这一步能看出点序是否错乱、归一化是否越界、类别是否画对,比任何日志都直观。

import cv2, glob, numpy as np from pathlib import Path os.makedirs("check", exist_ok=True) for txt_path in sorted(glob.glob("tomato_seg/labels/*.txt")): base = Path(txt_path).stem img_path = os.path.join("tomato_seg/images", base + ".jpg") img = cv2.imread(img_path) if img is None: print("缺图:", txt_path) continue h, w = img.shape[:2] with open(txt_path) as f: for line in f: parts = list(map(float, line.strip().split())) cls_id = int(parts[0]) coords = np.array(parts[1:], dtype=np.float32).reshape(-1, 2) if (coords < 0).any() or (coords > 1).any(): print("坐标越界:", txt_path, base) continue coords[:, 0] *= w coords[:, 1] *= h color = (0, 0, 255) if cls_id == 0 else (0, 255, 0) cv2.polylines(img, [coords.astype(np.int32)], True, color, 2) cv2.imwrite(os.path.join("check", base + ".jpg"), img)

这个脚本干两件事:一是把归一化坐标乘回原图宽高画线,肉眼直接看多边形贴不贴果实边缘;二是用(coords < 0).any() or (coords > 1).any()捕捉越界坐标——只要出现越界,说明转换脚本里宽高用反了,这是最容易踩的坑。抽 20 张图看一遍,标注是黑匣子还是能用的数据,基本就有数了。

4. 用 YOLOv8-seg 训练番茄分割模型:配置、超参与指标解读

4.1 数据目录摆放与 tomato.yaml 配置

转换完成后要把数据按训练集和验证集分开。Ultralytics 的默认逻辑是images/train和images/val两个目录,对应的labels目录保持相同结构。我一般按 8:2 划分,用固定随机种子保证可复现:

mkdir -p tomato_seg/images/{train,val} tomato_seg/labels/{train,val} python - <<'PY' import glob, random, shutil, os imgs = sorted(glob.glob("tomato_seg/images/*.jpg")) random.seed(42) random.shuffle(imgs) val_n = int(len(imgs) * 0.2) for i, p in enumerate(imgs): sub = "val" if i < val_n else "train" shutil.move(p, f"tomato_seg/images/{sub}/") base = os.path.splitext(os.path.basename(p))[0] lbl = f"tomato_seg/labels/{base}.txt" if os.path.exists(lbl): shutil.move(lbl, f"tomato_seg/labels/{sub}/") PY

划分脚本里的0.2是验证集比例,数据量少于 500 张时可以改成 0.15。注意我用了random.seed(42),这样每次跑划分结果一致,不会今天训和明天训验证集不同,指标没法对比。

划分完成后写tomato.yaml:

path: /data/tomato_seg train: images/train val: images/val names: 0: mature_tomato 1: immature_tomato

path建议写绝对路径,相对路径在换工作目录或换机器时经常找不到文件;names的编号必须和转换脚本里的CLASS_MAP完全一致,编号错了模型会把成熟果当青果训。caltech 这类公开数据集可以下完就训,这种自备数据集最怕的就是名字映射不一致,训练时不报错,但指标全错。

4.2 训练命令与关键超参:从快速验证到正式训练

先跑一个快速验证,确认数据管线通、loss 会下降,再上完整训练。我会先用 50 epoch 跑一轮:

yolo segment train \ data=tomato.yaml \ model=yolov8s-seg.pt \ epochs=50 \ imgsz=640 \ batch=16 \ patience=20 \ device=0

参数说明:model=yolov8s-seg.pt表示从官方预训练权重继续微调,番茄这类目标天然适合迁移学习,从零训练实例分割模型收敛又慢又差,所以这个参数基本不动;imgsz=640对应前面统计的分辨率,如果图片里小番茄占比高、单果像素不到 30x30,可以升到 960 提升小目标召回,但训练时间大约涨 2 倍;batch=16按显存定,6GB 显存降到 8,12GB 以上可以开 16;patience=20表示验证集 20 轮不涨就早停,防止后程过拟合浪费时间。

验证能正常出指标之后,再上正式训练:

yolo segment train \ data=tomato.yaml \ model=runs/segment/train/weights/best.pt \ epochs=200 \ imgsz=640 \ batch=16 \ patience=40 \ device=0

这里把model换成了上一轮生成的best.pt,相当于在已经收敛的基础上继续精调,比直接用yolov8s-seg.pt重训收敛更快。注意第二次训练的epochs是从第一轮基础上继续算的,不是从头开始。

提示:首次跑通别追求精度,先确认训练日志里三个 loss(box/seg/cls)都在下降,再回去调超参。数据没洗干净之前,堆 epoch 只会放大标注错误。

4.3 训练指标怎么读:别只盯 box mAP

训练结束后,runs/segment/train目录里的results.csv会给出全部指标。番茄重叠遮挡场景下,我重点看这几个:

指标关注点
box mAP50检测框定位好坏,番茄场景通常会虚高,参考价值有限
mask mAP50掩膜与标注 IoU 大于 0.5 的比例,决定后端能不能算对采摘点
mask mAP50-95对边缘精度的敏感度,叶片遮挡严重时这个值会明显偏低
seg_loss掩膜分支的收敛情况,训练末尾还在震荡说明标注噪声大

如果mask mAP50上 0.85 但mask mAP50-95只有 0.5 左右,说明掩膜边缘不够干净,常见原因是标注多边形点数太少、贴边不严,或者转换时取多边形分块取错了。这种时候回看 2.3 的可视化图,比换模型换 loss 都有效。另外看曲线时注意val/mask_mAP50和val/box_mAP50的差距——两个指标差距过大,基本可以断定掩膜分支没学好,问题大多出在数据侧而不是模型侧。

5. 避坑笔记:番茄实例分割训练最容易翻车的五个问题

坑一:seg_loss 卡住不动,验证集掩膜糊成一团。

现象:训练日志里 cls_loss 一路下降,seg_loss 前 20 轮降完就再也不动,验证集里预测掩膜像撒了一团浆糊。原因:标注多边形里有自交多边形,或者一个果实被拆成两块标注但转换脚本取到的是碎片那块。解决:回到 3.3 的校验脚本,把有问题的图挑出来,自交多边形用cv2.convexHull修一遍,多分块的实例改成取面积最大块。

坑二:mAP50 高到 0.9+,换到新棚直接崩。

现象:在验证集上表现完美,拿到另一个温室拍的照片,误检率暴涨,叶片被大量当成番茄。原因:这份数据集大概率来自单一温室或单一光照条件,背景纹理被模型当成了特征,这就是场景过拟合。解决:把它当基线而不是终点,做 HSV 颜色抖动、随机光照扰动这类增强,再去补拍不同角度的数据。番茄分割这类农业视觉项目,数据多样性比模型大小值钱得多。

坑三:成熟果 AP 0.85,青果只有 0.4。

现象:两类番茄的 AP 差距悬殊,青果老是漏检。原因:青果本身和叶片颜色接近,特征区分度低,加上类别实例数不平衡,模型把学习重心全压在了成熟果上。解决:先看 2.3 统计的类别分布,给青果做过采样或加大copy_paste增强概率;Ultralytics 里还可以给损失按类别加权,把cls的权重往青果偏一点。

坑四:转换后掩膜跑到图片外面,形状严重变形。

现象:可视化校验时,多边形飞到图外,或者图形像被拧过一样。原因:归一化时宽高用反,写了x / img_h、y / img_w。这种错误特别容易在复制脚本时手滑,而且训练不会报错,只会在指标上表现诡异。解决:转换函数统一写成x / img_w、y / img_h,校验脚本里加if (coords < 0).any() or (coords > 1).any(): print("越界"),有越界一刀切定位。

坑五:训练到一半图片读取报错,程序直接崩。

现象:跑到第 37 轮,cv2.imread返回 None,resize 时报空图错误。原因:图片路径里有空格、中文或括号,OpenCV 的imread对这些字符别扭;还有可能是图片扩展名大小写不一致,*.jpg匹配不到.JPG。解决:解压后第一时间做文件名规范化,全部转小写、空格换下划线,统一成.jpg。这种问题不会在训练前暴露,只会让你下午的时间白费。

6. 从权重到部署:预测可视化、TTA 与 ONNX 导出

6.1 批量预测与掩膜面积过滤

训练完拿到best.pt,先在验证集上做一次批量预测,看看真实输出长什么样。

from ultralytics import YOLO import numpy as np model = YOLO("runs/segment/train/weights/best.pt") results = model.predict("tomato_seg/images/val", conf=0.35, imgsz=640) for r in results: if r.masks is None: continue masks = r.masks.data.cpu().numpy() # shape: N x H x W for m in masks: area = (m > 0.5).sum() if area < 200: print("小碎片掩膜,滤掉")

conf=0.35是分割任务的常用起始阈值,番茄重叠严重时降到 0.25 能提高召回,但小碎片误检也会变多;面积 < 200的过滤是按 640 分辨率设的经验值,如果你用 960 训练,阈值提到 400 左右。这段逻辑直接接在predict之后,后面下游做采摘点计算时就不会被零散掩膜干扰。

6.2 TTA 推理与 ONNX 导出

部署前把权重导出成 ONNX,方便在工控机上跑:

yolo export model=runs/segment/train/weights/best.pt format=onnx opset=12 simplify=True

opset=12兼容性最好,老版本 ONNX Runtime 也能加载;simplify=True会做计算图优化,剪掉一些冗余节点。导出后在推理时用 TTA 提升遮挡场景的稳定性:

yolo predict model=best.pt source=test_imgs/ augment=True

augment=True会做水平翻转和多尺度预测再融合,重叠严重的番茄图像上能涨 1-2 个点 mask AP,代价是单张推理时间翻 2-3 倍。实时采摘场景可以只在离线测产时开 TTA,在线抓取关掉。

这几个工具串起来就是我每次拿到新分割数据集的固定流程:解压先体检、格式统一、可视化校验、小跑 50 epoch、再避坑调优。有一次我跳过校验直接开训,seg_loss 三天没动静,回去翻发现是转换脚本把宽高写反,白白烧了两天 GPU。从那以后,任何数据集进训练前都必须强制走一遍统计加可视化基线,权重可以换,流程不能省。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 20:20:05

Windows下openclaw命令行工具安装实战与常见问题排查

最近有个工具需要在Windows环境里部署&#xff0c;就是标题里这个openclaw。折腾了一下午&#xff0c;踩了几个不大不小的坑&#xff0c;把过程完整记录下来。如果你也是Windows用户&#xff0c;正准备安装openclaw&#xff0c;或者只是想把这类命令行工具在Windows上装明白&am…

作者头像 李华
网站建设 2026/10/11 20:11:55

基于YOLOv8的路面裂缝检测系统:中英文双版实战

1. 路面裂缝检测这个方向&#xff0c;为什么值得用YOLOv8重做一遍道路养护这个行当里&#xff0c;裂缝检测一直是个绕不开的活。早些年靠老师傅拿粉笔在路面上画框、拿本子记桩号&#xff0c;后来有了半自动的图像处理工具&#xff0c;但真正让一线养护队头疼的问题始终没变&am…

作者头像 李华