新闻详情

新闻详情

首页 / 资讯中心 / 详情

番茄数据集实战:从标注格式到YOLO训练的完整指南

发布时间:2026/10/1 14:01:00来源:尧图网络
番茄数据集实战:从标注格式到YOLO训练的完整指南
简介这份数据集面向计算机视觉初学者、科研人员及目标检测开发者提供895张自然场景下的番茄圣女果/西红柿图像涵盖不同光照、拍摄角度、果实成熟度、遮挡与背景干扰等多样情况有助于提升模型泛化能力。标注采用PASCAL VOC格式的XML文件与PNG原图一一对应可直接用于YOLOv5等主流检测模型的训练也可转换为YOLO txt、COCO等格式省去自行采集图像和手工标注的大量重复劳动。资源共1788个文件包含895张PNG原图、892个XML标注文件以及1份数据集说明txt压缩包整体约180.4MB。需注意的是其中有3个XML标注文件编号0、1、10存在损坏建议使用labelImg等工具重新标注后再进行完整训练。这一数据集适用于毕业设计、课程项目、算法验证及农业视觉检测应用场景累计已有2868人次浏览/学习能帮助快速搭建目标检测实验流程并为后续模型优化提供数据基础。1. 番茄数据集不是一包图片先看清它是检测、分割还是多模态做视觉的同行应该都有过这种经历从某个开源站点拖下来一个“番茄数据集”解压一看里面是几百张红彤彤的圣女果照片和一堆 xml、json、txt然后卡在第一步——不知道这些文件该怎么拼成训练集。番茄圣女果/西红柿数据集说到底是面向目标检测、实例分割或图像分类任务的图像集合与对应标注它解决的是农业视觉里最具体的一类需求让模型在藤蔓、叶片、强光、遮挡条件下认出果实并给出位置或轮廓。适合谁用做采摘机器人、产量估算、成熟度分级或者只是想拿一个小规模、类别清晰的数据集跑通 YOLOv8 训练流程的入门者。这篇文章不谈某个特定仓库的下载链接而是把这个数据集的通用结构、清洗方法和训练落地路径讲透照着做能省下好几个晚上的调试时间。2. 一张番茄图的背后标注格式、文件布局与选型2.1 YOLO、COCO、VOC、分割掩膜四种格式怎么选拿到任何数据集第一件事是看标注格式不是看图片。番茄数据集最常见的是这四种形态它们的差异会直接决定你后续要写多少转换代码。格式文件形态典型内容适合场景YOLO txt每张图一个 txt与图片同名每行class_id x_center y_center width height归一化YOLOv5/v8 训练最省事COCO json整个数据集一个 annotations jsonimages / annotations / categories 三段结构检测与分割通用MMDetection、Detectron2VOC xml每张图一个 xmlobject 节点里写 bndbox 坐标早期检测项目需转换分割掩膜 mask每张图配一张 PNG或 hdf5像素级标签每类对应灰度值实例分割、成熟度面积估算我一般碰到番茄数据集会先看一眼 labels 目录是不是空的。很多网上下载的“数据集”其实只有图片标注要自己补这类情况稍后再讲。真正带标注的YOLO 格式优先度最高因为 YOLOv5/v8 训练自己的数据集时官方仓库直接吃这个格式不需要中间层转换。COCO2017 数据集结构里的“images / annotations / categories”三段式在番茄数据集里同样成立只是 categories 往往只有两个类ripe_tomato和unripe_tomato或者按品种分cherry_tomato、beef_tomato。如果你拿到的 JSON 格式不规范比如 annotations 里缺了area或iscrowd字段后续转 YOLO 时容易踩坑。2.2 从零做一个小样本番茄检测子集采集与标注规范如果手头没有现成标注常见的做法是拿手机在温室或菜市场拍 200300 张然后手动标注。这里有一个容易被忽视的规范同一串番茄上的果实密集且互相遮挡标注时不要试图把每个果标成完整矩形而是标可见部分。实例分割任务里尤其如此被遮挡的果肉区域不标让模型学会预测可见掩膜比硬标一个包含大量背景的框更有效。标注工具有不少选择但流程基本一致导入图片、画框或画多边形、写类别名、导出。以导出 YOLO 格式为例标注完的目录结构长这样tomato_dataset/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ └── val/ │ ├── img_100.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_001.txt │ │ └── ... │ └── val/ │ └── ... └── classes.txt第一步先把目录骨架建出来后面所有脚本都基于这个布局。classes.txt里每行写一个类别名顺序就是类别 ID训练时千万不能乱动这个文件否则标注 ID 全错位。标注时的核心参数有两个类别数二类还是三类取决于是否区分成熟度和最小目标面积。番茄小果在图像里可能只有十几个像素如果标注时把这种小目标全部忽略模型在推理阶段就永远学不会识别远距离小果。我的习惯是只要肉眼能分辨的果实就标哪怕只有 10×10 像素。3. 把原始图像变成可训练数据集清洗、转换与划分3.1 图片去重与坏图过滤不干净的数据集不值得训练很多番茄数据集是从视频抽帧得来的连续帧之间存在大量近似重复图片。用这类数据训练模型会对特定帧过拟合验证集指标虚高。去重脚本的核心思路是计算感知哈希pHash把相似度超过阈值的帧只保留一张。import os import shutil from PIL import Image import imagehash def deduplicate_images(src_dir, dst_dir, threshold5): os.makedirs(dst_dir, exist_okTrue) seen_hashes {} for fname in sorted(os.listdir(src_dir)): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue fpath os.path.join(src_dir, fname) try: img Image.open(fpath) h imagehash.phash(img) except Exception as e: print(f[跳过] 无法读取 {fname}: {e}) continue # threshold 表示汉明距离越小去重越严格 duplicate False for existing_h, existing_name in seen_hashes.items(): if h - existing_h threshold: duplicate True print(f[去重] {fname} 与 {existing_name} 相似跳过) break if not duplicate: seen_hashes[h] fname shutil.copy(os.path.join(src_dir, fname), os.path.join(dst_dir, fname)) if __name__ __main__: deduplicate_images(raw_frames/, images_clean/, threshold5)这段脚本的逻辑是先对每张图计算 pHash再和已保留的图逐一比较汉明距离。阈值threshold5表示两张图的感知哈希差异小于 5 时视为重复视频抽帧一般建议 35如果来源是相机连拍阈值可以放宽到 8。注意这里用的是shutil.copy而不是move保留原始文件是给自己留后悔药清洗完确认无误再手动删原始目录。坏图过滤也在这段里一并做了文件打不开或截断的直接打印跳过不会让它们混进训练集。3.2 标注格式转换VOC 转 YOLO 的脚本与坐标边界坑如果拿到的是 VOC 格式转换脚本是刚需。VOC 的 bndbox 是绝对像素坐标xmin, ymin, xmax, ymaxYOLO 需要的是相对图片宽高的归一化中心点坐标两个坑在转换时一定会遇到一是坐标越界二是宽或高为零的退化框。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_width, img_height, class_map): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): class_name obj.find(name).text if class_name not in class_map: continue # 未映射的类别直接跳过 class_id class_map[class_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标修正部分标注工具会生成越界值 xmin max(0, min(xmin, img_width - 1)) xmax max(0, min(xmax, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) ymax max(0, min(ymax, img_height - 1)) if xmax xmin or ymax ymin: print(f[警告] {xml_path} 中存在退化框已跳过) continue x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height # 归一化后仍可能在 [0,1] 之外做一次钳制 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) box_width min(max(box_width, 0), 1) box_height min(max(box_height, 0), 1) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) return lines # class_map 示例{ripe_tomato: 0, unripe_tomato: 1} # 图片宽高从图像文件读取不要从 xml 的 size 节点读部分数据集 size 节点是错的参数说明class_map的键必须与 xml 里的类别名完全一致大小写敏感。坐标钳制这段逻辑是血泪教训——有些公开数据集的 bndbox 会超出图像边界几个像素不处理的话YOLO 训练时 OpenCV 读取会直接报错。图片宽高建议用 PIL 读不要信 xml 里的 size 字段我遇到过三次 size 节点与真实图尺寸不一致的情况。3.3 训练集与验证集划分按场景分层不要随机切番茄数据集的划分比一般数据集更讲究因为同一株藤蔓的连续拍摄帧高度相关。随机划分会让训练集和验证集里出现“同源图像”导致验证分数虚高。常用的做法是按拍摄场景分层把同一个视频片段、同一株番茄的帧归为一组整组划分。import os import random import shutil def split_by_group(all_groups, train_ratio0.8, seed42): random.seed(seed) # 每个 group 代表一个场景/一段视频保证同组帧不跨集合 group_names list(all_groups.keys()) random.shuffle(group_names) split_idx int(len(group_names) * train_ratio) train_groups set(group_names[:split_idx]) val_groups set(group_names[split_idx:]) for group, files in all_groups.items(): target train if group in train_groups else val for f in files: src_img f dst_img os.path.join(tomato_dataset/images, target, os.path.basename(f)) src_txt f.replace(images, labels).replace(.jpg, .txt) dst_txt os.path.join(tomato_dataset/labels, target, os.path.basename(src_txt)) shutil.copy(src_img, dst_img) shutil.copy(src_txt, dst_txt) # all_groups 的结构{video_001: [frames/0001.jpg, frames/0002.jpg, ...], ...} # 帧文件名里的数字前缀可以用来构造 group按视频名或拍摄时间分桶这个脚本的核心是random.seed(42)固定随机种子让每次划分结果一致排查问题时不会被“重新划分导致结果不同”干扰。train_ratio0.8是常规值但如果数据集总量少于 500 张我建议把验证集压到 15%给训练集多留样本。分层划分的价值大于样本数量宁可训练集少 50 张也不能让验证集与训练集存在场景重叠。4. 数据集质量决定模型上限标注体检与增强策略4.1 标注质量量化检查类别均衡、框面积分布、标签错位训练前做一次标注体检能提前发现绝大多数“模型训不出来”的问题。体检脚本统计三个指标每个类别的样本数、边界框面积分布、类别 ID 是否有空洞。import os from collections import Counter def inspect_dataset(label_root, class_names): class_counter Counter() box_area_list {name: [] for name in class_names} total_boxes 0 empty_labels [] for split in [train, val]: label_dir os.path.join(label_root, split) for txt_name in os.listdir(label_dir): txt_path os.path.join(label_dir, txt_name) with open(txt_path, r) as f: lines f.readlines() if len(lines) 0: empty_labels.append(os.path.join(split, txt_name)) continue for line in lines: parts line.strip().split() class_id int(parts[0]) w float(parts[3]) h float(parts[4]) total_boxes 1 class_counter[class_id] 1 # 归一化面积换算成像素假设 640x640 box_area_list[class_names[class_id]].append(w * h * 640 * 640) print(每类框数量:, dict(class_counter)) print(空标注文件数:, len(empty_labels)) for cls, areas in box_area_list.items(): if areas: areas.sort() print(f{cls} 面积中位数: {areas[len(areas)//2]:.0f} px², 最小: {areas[0]:.0f} px²) else: print(f{cls} 没有标注框) # 检查类别 ID 是否从 0 连续 existing_ids sorted(class_counter.keys()) expected list(range(len(class_names))) if existing_ids ! expected: print(f[异常] 类别 ID 不连续: {existing_ids}, 期望: {expected}) if __name__ __main__: inspect_dataset(tomato_dataset/labels, [ripe_tomato, unripe_tomato])这个脚本会暴露三类典型问题类别严重不平衡比如成熟果是青果的五倍、小目标占比过高大量框面积小于 1000 像素、空标注文件混入数据集。面积中位数的参考价值很高——如果大多数框在 640×640 尺度下只有几百像素说明相机离番茄远或果实小YOLOv8 的默认 anchor 可能需要调整。类别 ID 不连续的问题常出现在手动编辑过classes.txt的数据集里不检查的话训练直接崩。4.2 针对藤蔓遮挡与成熟度差异的增强策略番茄数据集的增强不能照搬通用目标检测的配置。圣女果的特点是果实密集、相互遮挡、叶片遮挡严重而且成熟度直接体现在颜色上——青果和红果的 RGB 分布差异巨大。如果增强策略里加了过强的色彩抖动会把青果的颜色扰动成红果的色调模型就学废了。我一般会这样配增强参数以 YOLOv8 的augment参数为例hsv_h0.015色调偏移压到很低、hsv_s0.5、hsv_v0.4。翻转和旋转可以放开degrees10就够番茄藤蔓没有方向性但果实形态受重力影响旋转角度太大会出现“番茄长在叶子上方”的失真样本。马赛克增强mosaic1.0保留它对小目标检测的提升显著mixup 开到0.2即可太高会让果实纹理变糊影响成熟度分类的精度。针对遮挡问题的另一个有效操作是copy-paste 增强把一张图里的番茄实例切下来随机贴到另一张藤蔓背景图上同时合并标注。这个操作对密集果实场景非常有效因为真实数据里“被遮挡的番茄”样本永远不够。实现上可以用imgaug的CopyPaste或自己写几十行脚本核心是要保证粘贴后的果实大小和光照方向与背景图一致否则模型会学到错误的阴影特征。4.3 留出验证集确保评估结果可信增强做完了还有一个容易被忽略的动作把增强后的样本和原始样本混在一起前先确认验证集用的是原始图片。验证集如果也做增强评估出来的 mAP 是“增强后的 mAP”不是真实场景的指标。常见做法是训练脚本里对验证集只做 resize 和归一化不做随机翻转、马赛克。这一点在 YOLOv8 里是默认行为但如果你自己写训练循环很容易顺手把训练用的 transform 套到验证集上。5. 番茄数据集实战的五个高频坑现象、原因与解法5.1 验证集 mAP 很高实拍一张就翻车现象训练时验证集 mAP 到 0.85拿着训练好的模型去温室拍一张新照片检测框乱跳漏检严重。原因数据划分没有按场景分层验证集里混入了与训练集同一视频连续帧的近似图模型等于“背过答案”。论文里叫帧间信息泄露在抽帧得到的数据集里几乎必现。解决重新按视频片段分组划分数据保证任何验证集图片与训练集图片不来自同一段连续拍摄。划分脚本就是上文 3.3 节那个不要偷懒用随机切分。5.2 标注里把果萼当成番茄现象训练后模型对叶子上的节点、果柄末端产生大量误检置信度还特别高。原因标注阶段操作者把果萼番茄顶部那圈绿色叶子状结构框进 bbox或者干脆单独标成一个小果。番茄的萼片和未成熟果在颜色、纹理上高度相似模型学到的是“绿色圆形”就判定为目标。解决标注规范里明确写“从果柄末端开始标不包含果萼”清洗时检查所有标注框里是否大量出现高宽比接近 1、但中心点位置在另一个大框边缘的小框这类框大概率是误标。脚本里可以加一条规则如果一个小框 80% 的面积落在另一个同类框内标记为可疑样本人工复核。5.3 数据增强把青果变红了现象训练完的模型对青果漏检率高但对染了红色调的叶子误检严重。原因hsv_h色调增强范围设置过大。青果的 HSV 色相与红果差异很大增强时色相偏移把青果的色调挪到红果区间模型被迫在“红色”和“绿色”之间反复横跳最后学成了一个模糊的中间态。解决把hsv_h降到 0.01~0.02色相偏移幅度控制在 5 度以内。成熟度分类任务最好完全关闭随机色相扰动只保留饱和度和明度增强。5.4 全部 resize 到 640 后小目标消失现象训练 loss 不降验证集小果全部漏检但大果检测正常。原因数据集里大量小果在原始图里占 20×20 像素resize 到 640 后只剩 12×12 像素特征图上的响应极其微弱。模型不是没学到特征是输入分辨率不够。解决训练输入尺寸提到 960 或 1280对应 YOLOv8 里设imgsz960同时把rectTrue开启让图片按原始宽高比不裁剪地传入网络。显存不够时优先保证训练尺寸降低 batch size 或开启梯度累积来换显存。5.5 训练中途报标签越界错误现象训练跑了一个小时突然报IndexError: index 3 is out of bounds for axis 0 with size 3之类的错。原因某个标注 txt 里写了类别 ID 3但模型配置文件里nc2。多数情况是classes.txt被改过标注文件和类别文件对不上。有时是转换脚本把未映射类别错误地给了默认 ID而不是跳过。解决先用 4.1 节的体检脚本过一遍全部标注确认类别 ID 最大值小于nc。再检查转换脚本里未映射类别的处理分支我当时就是漏了continue导致一个不在class_map里的类被赋了默认值。6. 用分布对比脚本验证数据集可用性再决定要不要继续投入训练前最后一个值得做的事是用一个轻量脚本对比训练集和验证集的标注分布确认这次划分值得投入算力。比较两个维度bbox 面积分布和类别频率分布如果差异过大说明划分方式有问题模型训练再久也救不回来。import numpy as np from collections import Counter def compute_distribution(label_dir, img_size640): areas [] classes [] for txt_name in os.listdir(label_dir): with open(os.path.join(label_dir, txt_name), r) as f: for line in f.readlines(): parts line.strip().split() classes.append(int(parts[0])) w float(parts[3]) * img_size h float(parts[4]) * img_size areas.append(np.sqrt(w * h)) # 用 sqrt 减少极端值影响 return np.array(areas), Counter(classes) train_areas, train_cls compute_distribution(tomato_dataset/labels/train) val_areas, val_cls compute_distribution(tomato_dataset/labels/val) # 面积分布对比中位数和分位数 print(Train 面积中位数:, np.median(train_areas)) print(Val 面积中位数:, np.median(val_areas)) # 类别频率对比计算归一化占比 train_total sum(train_cls.values()) val_total sum(val_cls.values()) for cls_id in sorted(set(train_cls.keys()) | set(val_cls.keys())): train_ratio train_cls.get(cls_id, 0) / train_total val_ratio val_cls.get(cls_id, 0) / val_total print(f类别 {cls_id}: train{train_ratio:.3f} val{val_ratio:.3f})如果验证集面积中位数比训练集大 30% 以上或者某个类别在训练集占比 60%、在验证集占比 20%就重新划分不要硬训。面积的 sqrt 处理是为了避免极端大框对均值的干扰用中位数更稳健。这个脚本我每次拿到新数据集都会先跑一遍它帮我在三个不同数据集的早期阶段发现了划分问题省下来的训练时间比写脚本的时间多得多。番茄数据集这类农业视觉方向数据质量的天花板效应比模型结构更明显投入时间清洗数据回报率远高于换一个更复杂的骨干网络。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

大模型性价比压测实战:从成本延迟到业务完成率 2026/10/1 14:43:17

大模型性价比压测实战:从成本延迟到业务完成率

1. 这场“同日开打”根本不是发布会,而是开发者社区的集体压力测试 “突发!GPT-6 Sol与Claude Opus 5.5同日开打”——看到这个标题,我第一反应是点开链接前先摸了摸自己的显卡散热器温度。不是因为兴奋,而是条件反射式地怀疑&am…

阅读更多 →
NiTi合金LMD工艺优化复现:BP神经网络+NSGA-II多目标寻优实战 2026/10/1 14:43:17

NiTi合金LMD工艺优化复现:BP神经网络+NSGA-II多目标寻优实战

简介:这份资源面向材料科学、机械工程与增材制造领域的研究人员及工程师,聚焦NiTi形状记忆合金激光金属沉积工艺参数的多目标优化难题。内容以BP神经网络结合NSGA-II遗传算法为主线,通过30组小样本单道实验建立显微硬度、粗糙度与沉积速率的预…

阅读更多 →
OpenAI智能体Dots:Meta Muse引爆个人Agent后,OpenAI发布dots正面迎战 2026/10/1 14:43:11

OpenAI智能体Dots:Meta Muse引爆个人Agent后,OpenAI发布dots正面迎战

“我等这款产品已经等了很多年。这就是我一直想要的与AI共事的方式。”当地时间9月29日,在OpenAI年度开发者大会DevDay上,萨姆奥尔特曼用这句话介绍全天候在线智能体Dots。它拥有自己的云端电脑,可以在用户离开后继续处理任务。按照OpenAI的设…

阅读更多 →
给 Codex 装上生图 SKILL:TaoToken 统一 Key 接入图像生成能力 2026/10/1 14:43:11

给 Codex 装上生图 SKILL:TaoToken 统一 Key 接入图像生成能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Linux镜像站ISO下载路径与命名规则全解析 2026/10/1 14:43:11

Linux镜像站ISO下载路径与命名规则全解析

先问一句:你上一次去镜像站下载 Linux 系统安装 ISO 的时候,是不是也经历过那种“目录里套目录,点进去全是不认识的版本号”的崩溃感?我印象特别深的是早年为了给一台老服务器装 CentOS,找遍镜像站终于打开了正确的目录…

阅读更多 →
大语言模型与大模型有什么区别?一文讲透架构、部署与选型 2026/10/1 14:43:11

大语言模型与大模型有什么区别?一文讲透架构、部署与选型

1. 先搞清楚:这俩词到底是不是同一个东西 团队里最近招人,有位候选人简历写“大模型应用开发工程师”,面试时聊细节,发现他做的全是基于大语言模型的文档问答。这本身不是问题,问题出在另一个候选人写“大语言模型工程…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉