☰
番茄数据集YOLO训练实战:标注校验、避坑与部署
2026/10/7 5:49:50 网站建设 项目流程

简介:一份面向目标检测学习与实战的完整数据集包,包含150张高清番茄图像及一一对应的150个YOLO格式txt标注文件,并附带类别配置xml及训练缓存文件,可直接用于YOLO系列模型的训练与验证。压缩包目录清晰,jpg与txt一一对应,便于直接划分训练集和验证集。资源包共303个文件,整体大小约387.26MB,所有图片与标签均由资深算法工程师手工标注、逐张核对,标注准确度高,与站内现有番茄数据集均有明显区别,适合计算机、电子信息、数学等专业的学生用于课程设计、期末大作业或毕业设计。已有325人学习下载。除数据外,还注重参数化编程与注释明细,参数可方便更改,能帮助读者快速理解目标检测训练流程,节省自行采集和标注数据的时间;如需更多数据集与仿真源码,可前往作者博客的下载列表按需查找。

1. 拿到一个“已标注”的番茄数据集,先别急着训练

做农业视觉的人,比如果实计数、大棚巡检、智能采摘机器人,第一道坎往往不是模型结构,而是数据。自己扛着相机去棚里拍几千张高清图,再花几周时间标注,光想想就劝退。这个标题给出的东西,就是把“拍图+标注”这段最耗时的工作提前做完了——一个已经标注好的番茄高清数据集,配好对应的标注文件,理论上解压后写个 yaml 就能开始训练。适合两类人:一类是刚入 YOLO、想用真实数据集跑通全流程的新手,另一类是接到农业检测需求、想快速出原型验证方案的工程师。但“已标注”不等于“规范标注”,这几个月我帮人排查训练翻车时,见过太多在标注文件上栽跟头的案例,所以这份数据集怎么验、怎么训、坑在哪,下面一篇讲透。

2. 先搞懂 YOLO 标注文件:番茄数据集里的 txt 到底写了什么

2.1 YOLO 的 txt 标注格式:五行数字背后的归一化规则

目标检测的数据集,最核心的文件不是图片,而是标注文件。YOLO 系列(从 YOLOv5 到现在的 YOLOv8/v11,Ultralytics 仓库)统一使用同名 txt 文件作为标注:图片是0001.jpg,标注就是0001.txt,两者放在对应的 images 和 labels 目录下。txt 里每一行代表一个目标,一行的格式永远是五个数值:

class_id x_center y_center width height

注意是中心点坐标和宽高,不是左上角坐标,而且全部做了归一化。所谓归一化,就是除以图片的原始宽高,所以这四个数值理论上都在 0 到 1 之间。第一列的 class_id 是整数,从 0 开始计数,对应你在 yaml 文件里定义的类别顺序。举个例子:类别是0: tomato,那任何一行的第一列只能是 0,如果数据集里把青果和红果分成了两个类,那就会出现 0 和 1 两行取值。

拿到这个番茄数据集的 RAR 包后,第一件事不是急着解压训练,而是先抽样打开几个 txt 看看内容。我之前收到过一份“标注好的数据集”,解压一看 txt 里全是乱码——标注工具导出时用了 UTF-16 编码,YOLO 训练时读不出来,loss 直接变成 nan。还有一种更隐蔽的情况:标注工具导出的是左上角坐标加宽高的四数值格式,但打包者没转成 YOLO 的中心点格式就硬塞进了 txt。这类文件从肉眼上看每行四个数,但训练出来的框全部往图片右下角偏,因为 x_center 被填进了左上角的 x 值,训练时模型完全学不到正确的目标位置。

2.2 番茄目标为什么难标:遮挡、串果与颜色歧义

番茄检测看起来比行人检测简单,实际标注难度不小。番茄是藤蔓植物,果实成串生长,一张图里常常十几个果实挤在一起,互相遮挡严重。标注人员在框选时,遇到被叶子挡了一半的番茄,到底是框半个、还是干脆不标?不同标注人员的习惯完全不一样。这个不一致会直接影响训练结果——模型看到两个相似的果实,一个标了完整框,另一个没标,它就会困惑“这到底算不算正样本”。

颜色歧义是另一个番茄数据集特有的问题。青果和成熟果在颜色上差异很大,有的标注员会刻意把青果标成另一个类别(比如green_tomato和red_tomato),有的则统一标成tomato。你拿到手的数据集类别定义是什么,直接决定了模型能学到的语义。如果业务上只需要检测“有没有番茄”,那二分类(青果/红果)反而会把任务变复杂,因为同一个模型要同时学两个类别的特征,数据量不够时很容易顾此失彼。遇到这种情况,我一般会写个脚本把多个类别合并成一个,再重新训练。

2.3 拿到 RAR 后的第一步:解压后先写个标注质量检查脚本

说了这么多,先动手。RAR 解压后,先用 find 命令看目录结构,确认图片和标注是不是分好了目录:

# 解压后先看目录结构,确认图片和标注的存放方式 find /path/to/tomato_dataset -type f | head -50

常见的打包方式有两种:一种是干净的images/和labels/两个目录,另一种是图片、标注混在一起散装。如果是混在一起,就需要用下面的 Python 脚本做一个初步体检:

import os from collections import Counter # 遍历数据集根目录,统计图片和对应的标注文件情况 # 假设图片是 jpg/png,标注是 txt base = "/path/to/tomato_dataset" exts = (".jpg", ".jpeg", ".png", ".bmp") imgs, labels, orphans = [], [], [] img_names = set() for root, dirs, files in os.walk(base): for f in files: # Windows 解压可能产生 ._ 开头的系统文件,直接跳过 if f.startswith("._"): continue name, ext = os.path.splitext(f) if ext.lower() in exts: imgs.append(os.path.join(root, f)) img_names.add(name) elif ext.lower() == ".txt": labels.append(os.path.join(root, f)) label_names = set(os.path.splitext(f)[0] for f in labels) # 有图片但没有标注,以及有标注但没有图片的情况 no_label_imgs = [n for n in imgs if os.path.splitext(n)[0] not in label_names] no_img_labels = [n for n in labels if os.path.splitext(n)[0] not in img_names] print(f"图片总数: {len(imgs)}") print(f"标注总数: {len(labels)}") print(f"缺标注的图片: {len(no_label_imgs)}") print(f"无图片的标注: {len(no_img_labels)}")

这段脚本的核心逻辑是比对图片和标注的同名关系。图片有标注才参与训练,标注没有对应图片则说明文件是多余的,两者都会干扰后续流程。另外注意一个细节:脚本里专门跳过._开头的文件,这是在 Mac 上解压 Windows 打包的 RAR 时常遇到的问题,系统会自动生成一堆._xxx.jpg元数据文件,不清理掉的话训练时会报图片读取错误。

3. 用 Ultralytics 跑通番茄检测:目录结构、YAML 与最小训练命令

3.1 把散装目录整理成 YOLO 标准结构:一个 Python 脚本搞定

YOLO 训练要求图片和标注严格分目录,而且 train/val 两个集合都要有 images 和 labels 子目录。数据集的原始打包往往不满足这个要求,最常见的两种情况:一是 images 和 labels 混在一个目录里,二是整个数据集没有划分训练集和验证集,只有一份完整的图片。这样直接开训,验证集和训练集重叠,mAP 会虚高到你不敢信。

建议先手工把少量图切到 val 里(比如 10%),剩下做 train,再用脚本把目录归整。下面这个脚本也是我常用的做法,按文件名哈希值做近似随机划分,保证同串番茄的连续帧不会全部挤在同一个集合里。

import os, shutil, hashlib src_imgs = "/path/to/tomato_dataset/images" # 假设原图都在 images 下 src_lbs = "/path/to/tomato_dataset/labels" # 标注在 labels 下 dst = "/path/to/tomato_yolo" val_ratio = 0.1 for split in ["train", "val"]: for sub in ["images", "labels"]: os.makedirs(f"{dst}/{sub}/{split}", exist_ok=True) for fn in sorted(os.listdir(src_imgs)): name, ext = os.path.splitext(fn) # 用文件名做哈希,保证同名的 jpg 和 txt 进同一个集合 h = hashlib.md5(name.encode()).hexdigest() split = "val" if int(h, 16) % 100 < val_ratio * 100 else "train" img_src = os.path.join(src_imgs, fn) lb_src = os.path.join(src_lbs, name + ".txt") if os.path.exists(lb_src): shutil.copy2(img_src, f"{dst}/images/{split}/{fn}") shutil.copy2(lb_src, f"{dst}/labels/{split}/{name}.txt") else: print(f"跳过无标注图片: {fn}")

脚本的逻辑不复杂:遍历图片,用文件名 MD5 哈希的前几位判断这条样本进 train 还是 val。为什么不直接random.shuffle?因为哈希划分是确定性的,同一张图无论脚本跑多少遍,划分结果都一样,方便复现。如果原数据集已经带了标注,直接复制;缺标注的图片直接跳过并打印出来,这些图即使留在目录里也只会让训练报空图警告。

3.2 写对 data.yaml:路径、类别与名称的三处易错点

目录整理好后,在项目根目录建一个tomato.yaml。这个文件是训练时的唯一数据入口,YOLO 的 CLI 和 Python API 都认它:

# tomato.yaml —— 番茄检测的数据集配置文件 path: /path/to/tomato_yolo # 数据集根目录,建议写绝对路径 train: images/train # 相对 path 的 train 图片目录 val: images/val # 相对 path 的 val 图片目录,不能省略 nc: 1 # 类别数,必须和 names 里的元素个数一致 names: 0: tomato # 下标从 0 开始

三处容易出错的地方:第一,path必须是绝对路径,尤其是不在终端当前目录下运行时,相对路径会让 Ultralytics 在拼接current_dir + images/train时彻底找不到文件;第二,val不能省略,训练过程会定期用 val 集做验证,判断模型有没有过拟合;第三,nc和names的长度如果不一致,Ultralytics 不会报错,但类别序号会错乱——训练能跑完,评估图表全乱。另外提醒一下,验证集的路径可以写成val: images/val,就算文件名也叫 test,也建议走 val 这个名字,因为它参与训练过程中的模型选择。

3.3 最小训练命令与参数选择:从 yolo11n 到 yolo11s

Ultralytics 统一封装了yolo命令,做环境配置时只需一行:

pip install ultralytics

然后是最小训练命令:

yolo detect train \ data=tomato.yaml \ model=yolo11n.pt \ epochs=100 \ imgsz=640 \ batch=16

各参数含义和选型逻辑:

  • model=yolo11n.pt:n 是 nano 版本,参数量最小、显存占用最低。新手或数据集规模只有几百张时,先用 nano 把流程整体跑通,比直接上 yolo11s/x 明智得多。跑通后再用model=yolo11s.pt在runs/detect/train/weights/里已有权重的基础上继续训练或重训。
  • epochs=100:100 轮是起步值。番茄这种单类检测任务,通常 60 轮左右 loss 就稳定了,100 轮给足余量。如果验证集 loss 在 60 轮后开始反弹,说明已经过拟合,Ultralytics 会自动保存 best.pt,不用太担心。
  • imgsz=640:默认输入尺寸。番茄是中小目标,尤其果实密集、单果占比小,用 640 会把很多小果实的特征下采样丢掉。如果你的显卡显存不超过 8GB,先用 640 热身,后面再评估要不要上 1280。
  • batch=16:16 是多数 8GB 显存独显的默认值。显存不够时报的是 CUDA out of memory,不是训练代码本身的错,把 batch 降到 8 或 4 即可。

训练过程中,终端会实时打印每轮的 box_loss、cls_loss、dfl_loss 和验证集 mAP。如果前三者在 10 轮后还稳定在一个大数值不降,问题几乎都出在标注文件上,这时先停下来查数据,别等到 100 轮跑完再懊恼。

4. 标注数据避坑:训练翻车前先查这五类问题

4.1 空标注文件:训练报 “All train images are empty” 的两种真相

  • 现象:训练启动没几秒就报错,提示所有训练图片为空,或者说图片里找不到任何标注目标。
  • 原因:第一种是 txt 文件本身就 0 字节,也就是这张图在标注时被遗漏了;第二种是文件名对不上,图片叫IMG_001.jpg,标注却叫img_001.txt,程序找不到对应关系,被当作空标注跳过。
  • 解决:用第 2 章的检查脚本先把“缺标注的图片”全部打印出来,如果只有少数几张,直接删除或补标;如果大面积缺失,就要怀疑数据集在传输或解压时出了问题,重新解压一次 RAR 包对比文件数量。空的 txt 文件用一条命令就能找到:
find /path/to/tomato_yolo/labels -name "*.txt" -size 0 -print

看到输出的文件列表后,把这些对应的图片从训练目录中移出去。空 txt 留在目录里不会直接报错,但它会稀释训练信号,等于让模型对着无目标的图片学会“什么都不检测”。

4.2 文件名大小写与后缀错位:Linux 下才暴露的坑

  • 现象:数据集在 Windows 上预览一切正常,传到 Linux 服务器上训练后,val 正确率奇低,或者某些图片根本加载不出来。
  • 原因:Windows 的文件系统不区分大小写,IMG_001.JPG和IMG_001.jpg是同一个文件,但你 RAR 里可能两种都有,实际是两张不同的图。在 Windows 上解压后,系统会强制合并;到了 Linux 的 ext4 文件系统,这俩成了不同文件,图片能看,但标注文件名匹配不上了。
  • 解决:解压后立即执行一次统一后缀名和大小写规范化的脚本,把所有图片后缀统一成.jpg、标注统一成.txt,并把文件名中残留的特殊符号(比如空格、中文括号)一并替换成下划线。顺手把重复文件找出来:
find /path/to/tomato_dataset -type f | sort | awk -F/ '{print $NF}' | sort | uniq -d

当终端的输出里出现同名不同后缀的记录时,你已经踩进这个坑了,别侥幸,重命名后再走一遍校验脚本。

4.3 坐标越界与反向框:标注工具导出格式不兼容

  • 现象:训练能跑通,但画出来的标注框全都偏离目标,或者框的尺寸异常,不是特别大就是特别小,验证的 PR 曲线离谱。
  • 原因:标注工具导出的坐标不是 YOLO 中心点格式。最常见的是导出成左上角坐标形式,行内五个数字是class x_min y_min x_max y_max或class x_min y_min width height,而 YOLO 要求的是x_center y_center width height,还做了归一化。前两种格式填进 YOLO 里,模型会把图片左上角区域当成目标中心,效果自然全乱。
  • 解决:写一个转换脚本,读取数据集的 txt,判断数值特征——如果同一行的后四个数字中有两个明显大于 0.5,说明不是归一化坐标。拿到图片真实宽高后,把左上角格式换算回中心点归一化格式。转换时务必保留一份原始备份,别覆盖原文件。换算公式就是初中的坐标平移:
# 左上角坐标转 YOLO 中心点坐标 x_center = (x_min + (x_max - x_min) / 2) / img_width y_center = (y_min + (y_max - y_min) / 2) / img_height width = (x_max - x_min) / img_width height = (y_max - y_min) / img_height

4.4 图片与标注对不上:mAP 异常的隐形元凶

  • 现象:数据集来自多个渠道,训练过程 loss 正常下降,但 val 的 mAP50 卡在 0.3 左右上不去;画检测结果时发现模型把叶子和小枝条也当成番茄。
  • 原因:images/目录下混进了一批网络爬取或手机拍的未标注图,或者标注文件是从另一个数据集拷贝过来的,内容完全对不上。
  • 解决:校验脚本只能比对文件数量,对不上内容是查不出来的。唯一可靠的办法是可视化抽查:随机抽出二三十张训练图,把真实标注框画上去,肉眼检查。代码很短,OpenCV 就能完成:
import cv2, os, random # 随机抽查 20 张 train 图片,把标注框画出来检查是否为番茄的真实位置 img_dir = "/path/to/tomato_yolo/images/train" label_dir = "/path/to/tomato_yolo/labels/train" files = random.sample(os.listdir(img_dir), 20) for f in files: img = cv2.imread(os.path.join(img_dir, f)) h, w = img.shape[:2] txt_path = os.path.join(label_dir, os.path.splitext(f)[0] + ".txt") if not os.path.exists(txt_path): continue for line in open(txt_path): parts = line.strip().split() if len(parts) != 5: continue x_c, y_c, bw, bh = map(float, parts[1:]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(f"check_{f}", img)

抽查结果里只要超过三张框的位置和番茄明显不对应,别纠结,直接把这批标注文件回炉重做。番茄检测的应用场景容不得侥幸,一个框错位的标注会把模型对“番茄”的语义理解带偏。

4.5 误把分割多边形当检测框:第 6 个数字的秘密

  • 现象:txt 里每行不止 5 个数字,而是十几个甚至几十个数字,训练时 loss 是 nan。
  • 原因:这份数据集原本是实例分割标注,导出成 YOLO 的 segmentation 格式(第一列是类别,后面每两个数字一组是顶点坐标),被当成检测数据集打包了。目标检测模型只认class x_center y_center w h五个数,行内多出来的数字会让解析器读错字段。
  • 解决:想用这份多边形标注做检测,有两种路径。一是把分割格式转成检测格式:取多边形外接矩形的左上和右下角点,再走进第 4.3 节的中心点换算流程。二是在训练命令里加task=segment,改用分割模型跑。不过如果你的目标是落地检测功能,我建议走第一条路,因为用分割标注转出来的矩形框往往比原矩形框标注更贴合目标——标注人员画分割多边形时注意力更集中,边界抠得更准。

5. 训练参数与效果评估:让番茄模型真正可用

5.1 训练曲线怎么读:box_loss、cls_loss 和 dfl_loss 的正常形态

训练过程中,终端输出的三个 loss 是最直观的健康信号:

  • box_loss:预测框和真实框的 IoU 损失,数值在 0.8–1.5 区间逐步下降,属于正常;如果一开始就低于 0.1 或在某轮突然归零,说明标注框本身有问题(比如所有框都缩成一个点)。
  • cls_loss:分类损失,番茄这种单类任务应该很快降到 0.1 以下。如果 50 轮后还在 0.3 以上打转,大概率是背景误检严重,需要查数据里有没有大量“没有番茄但纹理类似藤蔓”的负样本图。
  • dfl_loss:分布聚焦损失,影响框的边界精度,波动幅度比前两者大,只要整体趋势向下就不用干预。

训练结束后,Ultralytics 会在runs/detect/train/下生成results.csv,每一行一个 epoch。用 pandas 打开看趋势,比盯着终端滚动要直观得多:

import pandas as pd import matplotlib.pyplot as plt # 读取训练结果,画出 loss 和 mAP 的曲线 df = pd.read_csv("runs/detect/train/results.csv") # 列名形如 train/box_loss、val/box_loss、metrics/mAP50(B) plt.plot(df["train/box_loss"], label="train box_loss") plt.plot(df["val/box_loss"], label="val box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png")

画完之后重点看两条 box_loss 曲线的关系。train loss 继续下降但 val loss 开始反弹,就是过拟合的典型信号。解决方案不是加数据增强,而是先降低模型规模:yolo11n换yolo11s是加参数量,反过来才是减;另一个手段是提前到 val loss 最低的那一 epoch 取权重,Ultralytics 保存的 best.pt 就是按 val 指标最优点选的,直接用即可。

5.2 评估指标怎么挑:mAP50-95 之外,番茄场景一定要看 PR 曲线和混淆矩阵

训练完成后的终端输出会打印mAP50和mAP50-95两类指标。mAP50 是指 IoU 阈值在 0.5 时的平均精度,番茄这种单类密集小目标场景,mAP50 高于 0.8 就算可用;mAP50-95 是对 IoU 从 0.5 到 0.95 区间做平均,标准更严。很多新手只盯 mAP50-95,数值一低就以为模型废了,其实对果实检测来说,框稍微偏一点不影响后续计数和采摘决策,mAP50 比 mAP50-95 更贴近实际需求。

更值得看的是混淆矩阵和 PR 曲线,训练后 Ultralytics 会自动生成confusion_matrix.png和PR_curve.png。混淆矩阵要关注两个角落:背景被误检成番茄的比率(假阳性),以及番茄被漏检的比率(假阴性)。番茄场景里假阳性通常是叶子被识别成果实,假阴性则是严重遮挡或青果和背景颜色接近。PR 曲线的面积就是 AP,曲线右上方越接近 1,说明模型在保持高召回的同时还能高精率,这是“精度和召回不可兼得”这一矛盾在模型上的最终投影。

看指标的正确姿势是带上业务视角。如果用途是给采摘机器人定位,假阴性(漏检)比假阳性(误检)致命得多,因为漏掉一个果实等于损失一个产量,误检叶子只会让机械臂多挥一下。这种情况可以调低置信度阈值,用召回换精度。

5.3 调参切入点:imgsz、batch、epochs 的联动关系

模型能跑通后,接下来是让精度再上一个台阶。第一个切入点永远是输入尺寸。番茄果实密密麻麻,单颗果在原始高清图里可能只占 30×30 像素,640 分辨率输入会把这样的目标直接下采样到接近 10×10,特征几乎消失。显存允许的话(8GB 以上显存且不需要跑推理的机器),从imgsz=640直接跳到imgsz=1280是番茄检测最常见的涨点手段,效果往往比换更大的模型明显。

第二个切入点是 batch。很多人误以为 batch 越大越好——在目标检测场景,batch 大小受显存限制,调大的直接代价不是精度,而是可能触发 CUDA out of memory。batch 影响的是梯度估计的稳定性,番茄数据集通常小于 1000 张,batch 16 已经足够,盲目上 64 只会让每个 epoch 的步数少得可怜,模型根本没机会充分收敛。第三个切入点是 epochs,但这里有个隐性关联:输入尺寸放大到 1280 后,单 epoch 时间变成原来的 4 倍(面积与分辨率成正比),本来 100 轮 2 小时跑完,现在要 8 小时。所以调整顺序建议是先小图跑通、确认模型有效,再放大 imgsz 做精调,别上来就 1280 起步。

对于数据集本身,还可以考虑一个后处理技巧:把小图切块训练。把 4K 高清图裁成 640×640 的块,配合原始标注框的坐标偏移重新生成标注,等于把数据集扩大了数倍。这种做法在番茄、葡萄这类小目标密集场景很常见,代价是推理时也要做同样的切块拼接,工程复杂度上一个台阶。如果你的场景是固定摄像头对着同一片作物,切块是值得投入的路线;如果是移动机器人,输入分辨率会跟随硬件走,先不要过度优化。

6. 从权重文件到落地:导出 ONNX 与单图推理验证

训练完拿到的best.pt还是 PyTorch 权重,生产环境里一般要导出成 ONNX 再转成各家推理引擎的格式。导出的命令很短:

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640

导出后建议做一步推理验证,确认导出的 ONNX 和原 PyTorch 权重的检测结果一致:

from ultralytics import YOLO # 分别加载 pt 和 onnx,对同一张图做推理,对比检测结果 pt_model = YOLO("runs/detect/train/weights/best.pt") onnx_model = YOLO("runs/detect/train/weights/best.onnx") img = "val_sample.jpg" results_pt = pt_model.predict(img, conf=0.25, imgsz=640) results_onnx = onnx_model.predict(img, conf=0.25, imgsz=640) # results 里的 boxes.xyxy 和 cls 可以直接比较 print("pt 检测框数:", len(results_pt[0].boxes)) print("onnx 检测框数:", len(results_onnx[0].boxes))

重点验证的是 ONNX 的输出和原模型一致,排查导出时有没有把后处理丢掉。两个模型检测框数量不一致时,多半是导出时设置了simplify=True或 imgsz 和训练时不统一,回看导出命令修一下参数。运行时性能上,ONNX 在 CPU 上比 PyTorch 快不少,但真正的速度提升要靠 TensorRT——T4 这类边缘卡上跑 640 分辨率,单卡支持的路数取决于吞吐瓶颈在推理还是预处理,这个按实际硬件压测拍板,公式永远只作参考。

我自己的血泪经验是:数据集验证脚本永远别删。每个新数据集到手先跑一遍,再谈训练配置。学会这份番茄数据集的全套处理手法之后,换牛油果、青椒、柑橘数据集,只是改改 yaml 里 names 的事。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询