
简介这是一套YOLO番茄目标检测数据集专为计算机视觉学习与实战设计收录1000张真实场景高质量图片覆盖多种环境下的番茄果实形态适合初学者至进阶开发者用于模型训练与算法验证。压缩包共2000个文件包含VOC格式xml标签、YOLO格式txt标注、COCO格式json注释及yaml配置文件另附YOLO环境搭建与训练教程html、数据集划分Python脚本整体大小约954MB。目前已有1762人学习或下载。标签均经LabelImg人工标注质量较高且三种格式分目录存放可直接接入YOLO系列框架配套教程覆盖Windows与Linux环境并支持按需求划分训练集、验证集和测试集能显著降低数据准备门槛适合课程设计、毕业设计及目标检测入门项目复用。1. 为什么拿来就用的番茄目标检测数据集这么少1000张图补齐训练链路做目标检测这行真正劝退新手的往往不是YOLO算法本身而是数据集。我拆过不少网上下载的“数据集”要么图片格式乱要么标签只给一种格式换个框架就得重新标注运气差的连标注框和图片对不上。这份番茄数据集1000张图片不算多但VOC、COCO、YOLO三种格式的标签全部备齐另外还附带划分脚本和一份训练教程算是把“原始图片→标准标签→训练验证集→权重文件”的完整链路补齐了。它适合两类人一是刚入门目标检测、想用一套现成数据把YOLO流程完整跑通的小白二是做农业视觉项目、想拿番茄检测做底模的从业者。我下面按拆包后的实际顺序讲三种标签格式怎么互转、划分脚本怎么改、训练时有哪些坑最后落到验证方法。2. VOC、COCO、YOLO三种标签互转格式差异与转换脚本要点2.1 三种格式的本质区别一个框在三种语言里的样子同一个番茄框在三种格式里长得完全不一样。VOC格式是XML文件每张图片对应一个同名XML框坐标存在bndbox节点里xmin、ymin、xmax、ymax都是绝对像素坐标COCO格式是一个大JSON所有图片的标注集中在annotations数组里每个标注带bbox字段写法是[x, y, width, height]同样用绝对值YOLO格式最简单每张图片对应一个txt文件一行表示一个目标写法是“类别id x_center y_center width height”关键点是全部做了归一化除以图片宽高得到0到1之间的小数。拿资源里随便一张图片举例VOC的XML长这样annotation filenametomato_001.jpg/filename size width640/width height480/height /size object nametomato/name bndbox xmin120/xmin ymin80/ymin xmax200/xmax ymax160/ymax /bndbox /object /annotation对应的YOLO txt就一行0 0.25 0.25 0.125 0.166667第一个0是类别索引后面四个分别是中心点x、中心点y、宽度、高度的归一化值。COCO的JSON则把这张图的id、file_name、标注框全部塞进一个大字典读取时按image_id关联。这三种格式的差异不只是语法问题背后是设计意图的不同。VOC和COCO做评测用追求人类可读、可校验保存绝对坐标YOLO的txt是给训练管线直接吃的归一化后不受输入分辨率影响。理解这一点你再去看转换脚本就知道每一行在做什么。2.2 转换脚本核心逻辑与参数说明资源里已经附带三份标签不需要你全部手动重转但只要你打算往数据集里加自己的番茄图片就得自己写转换脚本。我按这套数据集最常见的整理方式整理过一套核心函数长这样import os import xml.etree.ElementTree as ET def xml_to_yolo(xml_path: str, out_txt_path: str, class_names: list): tree ET.parse(xml_path) root tree.getroot() # 图片宽高从size节点取不能自己猜 img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) 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) # 部分标注框会略微越界clip一次更稳 xmin max(0.0, min(xmin, img_w)) xmax max(0.0, min(xmax, img_w)) ymin max(0.0, min(ymin, img_h)) ymax max(0.0, min(ymax, img_h)) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这段代码有两个最容易写错的地方。第一class_names的顺序必须和图片里实际出现的类别名一一对应后续训练用的data.yaml里的names要从同一个列表复制过来顺序一变类别id全错位模型会把番茄学成另一个类。第二归一化除的是图片宽高不是标注时显示的缩放尺寸。番茄数据集里的图片都是原图但如果你后面加了带exif旋转的手机照片最好先统一转成标准方向否则框的坐标和像素对不上。COCO转YOLO的做法类似但多一步先读JSON里的categories把COCO的category id重映射成从0开始的连续索引因为COCO允许类别id不连续YOLO只认0到nc-1的整数。import json import os def coco_to_yolo(json_path: str, out_dir: str): with open(json_path, r, encodingutf-8) as f: data json.load(f) # COCO的id可能从1开始也可能跳号必须重映射 cat_mapping {c[id]: idx for idx, c in enumerate(data[categories])} for img in data[images]: img_id img[id] img_w img[width] img_h img[height] lines [] for ann in data[annotations]: if ann[image_id] ! img_id: continue cls_id cat_mapping[ann[category_id]] x, y, w, h ann[bbox] # bbox给的是左上角坐标加一半换成中心 x_center (x w / 2) / img_w y_center (y h / 2) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, img[file_name].rsplit(., 1)[0] .txt) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这里的参数细节bbox里的x、y是框左上角坐标要加w/2和h/2换成中心点才能喂给YOLOCOCO的width/height是标注用的整数如果你在导出前resize过图片必须用resize后的尺寸做归一化否则所有框都会偏。2.3 转换完怎么自检从XML到txt最容易出错的三个点转换完成后不要直接开训先做三个自检。第一抽查txt内容看归一化坐标是否都在0到1之间出现大于1的值说明分母用错或者xmax/xmin顺序反了。第二用OpenCV把框画回图片上逐张目测这一步能发现坐标错位、宽高为0、类别反了等一堆问题。第三对比原XML里的object数量和生成的txt行数少了说明有的object节点没被解析比如类别名里带空格、标签层级不同。第三个自检的具体工具可以这样写import cv2 def draw_boxes(img_path, txt_path, class_names, out_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f.readlines(): cls, xc, yc, bw, bh line.split() x1 int((float(xc) - float(bw) / 2) * w) y1 int((float(yc) - float(bh) / 2) * h) x2 int((float(xc) float(bw) / 2) * w) y2 int((float(yc) float(bh) / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(cls)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img)画框之后肉眼扫一遍比看数字直观得多。我一般还会对数量做一次粗校验统计所有txt的总行数再和原标注总目标数对一下不一致的单独列出。这一轮自检十分钟跑完但能省下后面训练失败排查的几个小时。提醒一句这个子目录里的标签文件如果和图片不同名画框脚本会直接读不到所以自检前先确认命名统一。3. 数据集划分脚本随机种子、样本均衡与验证集的边界3.1 划分脚本的实现思路与关键参数拿到数据集之后第一步不是改模型而是划分train/val/test三份。资源里带了划分脚本但它解决的是“能划分”的问题你要自己决定的是“怎么划才合理”。我按这类场景最常见的做法把脚本的核心逻辑拆开讲。import os import random import shutil random.seed(42) # 固定种子保证每次划分结果可复现 img_dir images label_dir labels split_cfg {train: 0.8, val: 0.1, test: 0.1} imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg, .jpeg, .png))] random.shuffle(imgs) start 0 for split, ratio in split_cfg.items(): end start int(len(imgs) * ratio) split_imgs imgs[start:end] start end # images和labels下都要建同名子目录结构要镜像 os.makedirs(os.path.join(img_dir, split), exist_okTrue) os.makedirs(os.path.join(label_dir, split), exist_okTrue) for img_name in split_imgs: base img_name.rsplit(., 1)[0] shutil.copy2(os.path.join(img_dir, img_name), os.path.join(img_dir, split, img_name)) shutil.copy2(os.path.join(label_dir, base .txt), os.path.join(label_dir, split, base .txt))这段逻辑里最值得注意的参数是random.seed(42)。固定随机种子是为了让划分结果可复现你跑十次train和val的组成完全一样。很多新手忽略这点第一次划分运气差某种场景的番茄全进了val第二次重新划又全进了train前后对比实验就没法做了。我习惯把seed单独提出来放到配置项里每次实验记录用哪个seed出问题能还原。shutil.copy2保留文件修改时间等元数据对标注任务来说无所谓但它不会自动创建目标目录前面那几行os.makedirs必须保留。另一个细节是标签文件名要和图片名保持严格一致只是后缀不同。有些标图工具输出的txt名带后缀全名比如tomato_001.jpg.txt目录里全部要统一否则训练时Ultralytics会报图片找不到对应标签。3.2 按场景调整划分比例的实测建议1000张图的数据集train/val/test怎么分很多网上教程直接写7:2:1或8:1:1但这两个比例在这个规模下差别不小。8:1:1意味着train只有800张val 100张test 100张。100张验证集在番茄这种单类检测里每张图平均3到5个目标验证目标数约400个够统计出比较稳定的mAP了。如果你的目标是复现论文指标按8:1:1走没问题如果要做模型上线前的严格评估我建议7:2:1把val放大到200张。要是你的场景里番茄大小差异很大比如既有无人机拍的远景小目标又有手机近拍的大番茄那最好先按图片里的目标尺寸分层再在每一层里做随机划分保证train和val里都有大小目标而不是某种尺寸全部被分到val。test集在这个资源里没有单独强调但我的习惯是test至少留50张只用于最终验证不参与训练也不参与调参。数据量再小也不要为了多喂几张训练图把test并进train那样你后面的模型评估就只剩自欺欺人的分数。3.3 分层抽样改进与完整性检查如果图片之间的目标数差异很大纯随机划分可能让val集全是单目标图结果模型在大遮挡场景里的表现完全没被验证到。我一般会在这个基础上加一层按目标数的分层抽样from collections import defaultdict def stratify_by_count(images, label_dir): buckets defaultdict(list) for img in images: txt os.path.join(label_dir, img.rsplit(., 1)[0] .txt) with open(txt, r) as f: n len(f.readlines()) bucket 0 if n 0 else (1-3 if n 3 else 4) buckets[bucket].append(img) return buckets拿到bucket之后在每个bucket内部做random.shuffle再按比例取train/val/test这样目标稀疏和密集的图片都均匀分布在三个集合里。番茄藤蔓这种密集结实的场景一个bucket里的图就是你的主要训练来源分层能明显减少val指标忽高忽低的情况。划分完不等于结束。要检查三类问题文件缺失、标签空文件、图片损坏。最常见的是copy时漏掉某个子目录的标签或者有的图片本身打不开训练时会在某个epoch突然卡住。写个几十行的检查脚本遍历三个split逐个确认图片能打开、同名txt存在且非空。这一步放在训练前做比训练中途崩了再回头找原因省心得多。注意验证集的标签缺失同样影响val指标计算不能只查train。4. YOLO训练避坑指南那些不报错但结果全废的细节4.1 五条血泪教训现象、原因、解决这章是实打实的坑。我按资源里的训练教程从零跑了一遍番茄检测遇到的五个问题每一条都是不报错但结果全废的类型。先说第一个坑。训练时loss曲线一路下降mAP却纹丝不动。现象是训练过程一切正常日志里没有任何error但val的mAP始终卡在0.1以下。原因一般是标签里有无效框坐标全为0、宽高为0、类别id超出nc范围。YOLO的标签解析器对这类框会直接忽略但不会在控制台里提示。解决方法是训练前把每个txt都过一遍校验筛出宽高为0或坐标越界的行并清理一个都不能留。第二个坑更隐蔽COCO标签里类别id从1开始YOLO的nc从0开始少做一步减1所有框的类别都会偏移。现象是训练完成后识别番茄的模型把一个个框框在背景上或者把番茄全部识别成另一个类。原因就是重映射时没对COCO的id做连续化。解决方法我在2.2节已经写过训练前打印一次data.yaml里的names再随机找一张验证集图片预测一下用眼睛确认类名和框的位置对不对。第三个坑出在划分环节随机划分没有按类别分层结果某个角度、某种光照条件下的番茄图片全部进了val。现象是train loss正常val的loss波动很大mAP方差也大。原因是划分脚本只用random.shuffle没有对标签里的类别分布做约束。番茄虽然只有一类但不同成熟度、不同遮挡程度的图片分布会直接影响泛化。解决方法是按第3章的分层逻辑先分组再在每组内随机划分。第四个坑是数据增强参数过猛。Ultralytics默认开启HSV扰动、翻转等增强对番茄这种红色占比高的物体过度的饱和度扰动会让模型学到错误的颜色特征。现象是训练时mAP高一到真实场景就掉点。解决方法是在番茄场景里把hsv_s和hsv_v适当调低翻转增强保留旋转角度不要给太大尤其是接近圆形的番茄旋转增强本身意义不大还容易把框转歪。第五个坑是漏看了标签目录结构。现象是训练能启动但日志里大量出现“WARNING: ignoring corrupt/lossy image”或者训练完val的recall为0。原因是images和labels下的目录层级不一致脚本只建了images/train忘了建labels/trainUltralytics按文件名找标签时全部落空。解决方法是在划分完立刻检查三个split下images和labels的文件数是否一一对应脚本里os.makedirs两行缺一不可。4.2 番茄场景的标注边界与重叠目标番茄检测和通用目标检测有个明显差异果实之间大量重叠成熟度不同颜色又接近背景。标注时如果每个番茄都画一个独立紧框会出现大量互相交叠的框如果画得松两个框之间的IoU可能超过0.5训练时NMS又把它们当成同一个目标压掉。我通常建议标注时遵循“框住可见部分”的原则不脑补被遮挡的完整轮廓。被压在后面的番茄只标露出来的区域这样框之间IoU可控模型学到的也是真实可见的特征。对遮挡严重的密集场景可以适当用mosaic增强提高泛化同时把预测时的NMS IoU阈值从默认的0.45调到0.5以上减少相邻果实的合并误检。如果这份资源里大量框明显超出果实边缘那是标注风格偏“完整轮廓”建议在预处理阶段统一把框向内缩5%再训练反过来如果框比果实小一圈就说明标注只覆盖了可见核心区域不用额外处理按紧框训练效果更好。我用一个简单规则判断随机抽20张图肉眼对比框和番茄轮廓的贴合度偏差超过一个像素级别的就统一处理。5. 训练教程怎么用环境配置、命令拆解与超参数调优5.1 从环境配置到训练启动的完整流程资源里的训练教程是针对0基础入门写的文档核心目标就是教你把YOLO训练跑起来。第一步环境配置教程直接建议装Ultralytics这也符合当前主流做法。番茄数据集只有一类用yolov8n这种轻量模型起步就够先跑通再换大模型。conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics装完后先确认GPU可用再准备好数据目录。Ultralytics的YOLO训练命令不复杂教程里给的主干命令一般是下面这种yolo detect train datatomato.yaml modelyolov8n.pt epochs120 imgsz640 batch16data.yaml是关键文件内容要和你前面划分好的目录一一对应path: /absolute/path/to/dataset train: images/train val: images/val test: images/test nc: 1 names: 0: tomato注意path要写绝对路径Ultralytics会在当前工作目录下拼接train和val的相对路径路径写错时会提示数据集找不到但报错信息有时候不够明确。我遇到过一次相对路径被拼到了某个不存在的上级目录排查了很久。如果你用Windows路径分隔符也经常出问题统一用正斜杠最省事。5.2 超参数怎么调batch_size、imgsz、epochs的取舍教程里给的是通用参数真正落到番茄场景三个参数要重新考虑。第一个是imgsz。1000张图的番茄数据集如果原图分辨率只有640x640imgsz设640合理如果原图是1080p可以设到800或960小目标框更不容易丢。但设得越高显存占用越大batch要相应减小。第二个是batch_size。显存够的情况下batch越大训练越稳定但不要超过合理范围。我习惯先用batch16试跑几个epoch如果显存占用超过70%就减半。对新手来说等显存溢出再降代价很大因为训练中途崩了重来很浪费时间。第三个是epochs。1000张图的数据规模120个epoch偏保守实际跑到60到80个epoch时mAP就基本收敛了。建议设150但打开早停Ultralytics的patience参数设20意思是连续20个epoch没提升就自动停。yolo detect train datatomato.yaml modelyolov8n.pt epochs150 imgsz640 batch16 patience20这行命令多出来的patience20就是后悔药防止你半夜跑训练时模型已经收敛还在空转。模型选型方面先跑通n再按需升级下面这张表是Ultralytics各规格的大致性价比模型规格速度参考精度潜力适用阶段yolov8n最快中等先跑通流程yolov8s快中上日常迭代yolov8m中高精度优先yolov8l/x慢更高显存充裕番茄单类目标n到m之间基本够用再往上提升有限但训练和推理时间成倍增加。加训自己的数据时从yolov8n.pt这样的小模型继续训练比从零随机初始化收敛快得多。5.3 训练日志怎么看loss下降不代表模型能上手训练结束后打开results.png很多人只看total loss那条曲线觉得降了就好。实际上要看三条box_loss、cls_loss、dfl_loss三条全要稳步下降且val曲线不明显反弹。如果train的loss在降val的loss在后期反弹说明过拟合来了早该停。另外要看val的mAP50和mAP50-95两条曲线两者差距大说明模型对IoU阈值敏感框的位置精度不够这时要从标注框是否贴紧入手排查而不是急着换模型。很多人一看mAP50不错就封板结果部署时框歪得没法看就是没关注mAP50-95。教程里通常还会提一句“把best.pt和last.pt都留着”这句话很容易被忽略。best.pt是val指标最优的权重last.pt是最后一个epoch的权重两者差别明显时说明训练后期出现了轻微过拟合。我一般优先用best.pt做推理验证但如果last.pt在真实场景里效果更好说明val集和真实分布有偏差要回到第3章检查划分。6. 模型验证与结果可视化从指标分数到落地可用性判断6.1 混淆矩阵和PR曲线怎么读训练完了别急着看那串数字就完事还要看混淆矩阵和PR曲线这两个是判断模型能不能上手的关键。混淆矩阵在Ultralytics的val结果里自动生成对单类检测主要看番茄这一类被漏检的占比以及背景被误检成番茄的占比。漏检高说明可见目标没被框出来误检高说明背景噪声被当成了果实两者的调整方向完全不同。查漏检时把置信度阈值降下来重新跑val把漏检的图片单独拎出来逐个看是遮挡、暗光还是小目标。查误检时则把阈值调高再检查训练数据里背景的多样性因为背景容易被误检成番茄说明训练集里番茄和背景的对比度不够、颜色接近场景太多。PR曲线看的是不同置信度下精确率和召回率的权衡。曲线下面积大说明模型本身质量高如果曲线尾部掉得很快说明高置信度下精确率也不稳生产环境里宁可用保守阈值。6.2 推理验证与阈值调整最后做一次真实推理验证不要用val集合里见过的图片用test集里的照片跑一遍预测yolo detect predict modelruns/detect/train/weights/best.pt sourcetest_images conf0.25跑完把输出目录里带框的图片翻一遍重点观察三件事框有没有整体偏移、大小是否和番茄真实轮廓一致、遮挡严重的番茄有没有漏检。框整体偏移通常是标签坐标转换时分子分母用反了框太大太小多半是归一化时用了错误的分辨率漏检多则先检查遮挡占比再考虑降低阈值。我自己的习惯是conf先设0.25如果误检明显就提到0.4以上如果漏检多就降到0.15。这一个阈值是真正影响部署体验的地方模型分数差了0.01不一定有感知阈值差0.1体验天差地别。把conf和NMS的iou阈值记录在训练配置里每次换模型都用同一套阈值做对比这样不同版本之间的效果比较才是公平的。从那以后我每次拿到新数据集都会在动训练之前强制走一遍“格式自检分层划分标签校验”三步哪怕资源里已经给好了标签和脚本这一步也绝不跳过。一套干净的输入数据比换任何模型都管用。希望帮到你。本文还有配套的精品资源点击获取