
简介这份资源是面向计算机、电子信息工程、数学等专业学生及目标检测初学者的番茄高清标注数据集可直接用于YOLO系列模型的训练与课程设计、期末大作业、毕业设计等场景。数据由作者自行打标与站点现有番茄数据集均不重复图片清晰、label标注准确并配套参数化编程代码参数修改方便、注释详细、思路清晰便于读者理解检测流程并快速复现实验。压缩包共303个文件以150张jpg高清图像和150个txt标注文件为主另含2个cache缓存与1个xml文件整体约387.26MB目录结构清晰适合直接投入训练与验证。目前已有325人浏览学习可为番茄目标检测任务提供从数据到代码的完整参考帮助读者省去采集与标注成本把精力集中在模型调参与效果优化上。1. 番茄检测数据集拿到手先别急着训练把这三件事想清楚你从群里或者某网盘拖下来一个压缩包名字叫「YOLO目标检测番茄高清数据集已标注可以直接使用数据集对应已标注文件.rar」解压完看到一堆 jpg 和 txt第一反应大概率是直接丢进 YOLOv8 跑yolo train。我见过太多人这么干然后 mAP 卡在 0.3 上不去回头怀疑模型、怀疑显卡、怀疑人生。问题往往不在 YOLO而在你根本没搞清楚这个数据集里装的是什么。番茄目标检测在农业视觉里是个典型场景果实遮挡、光照剧烈变化、成熟度不同导致颜色差异大、枝叶背景杂乱。这个数据集的价值在于它已经帮你省掉了最痛苦的一步——标注。但「已标注」不等于「标注格式对」「类别定义合理」「划分没泄漏」。这篇笔记就是把我拿到这类番茄数据集后实际会走的流程讲清楚从目录结构核对、标注格式转换、训练参数设置到训练崩了怎么排查。适合刚接触 YOLO 目标检测、手里有标注数据但不知道怎么把它变成可用模型的同学也适合做过检测但没碰过农业场景的熟手看看边界在哪。2. 解压之后先做体检目录结构、标注格式与类别分布2.1 一个「已标注」数据集到底该长什么样番茄数据集常见的组织方式有两种。一种是已经按 YOLO 要求分好了images/train、images/val、labels/train、labels/val另一种是全部图片堆在一个文件夹标注文件跟图片同名但扩展名不同需要你自己划分。先别管哪种解压后第一件事是看文件数量和配对情况。# 统计图片和标注文件数量检查是否一一对应 find . -name *.jpg -o -name *.png | wc -l find . -name *.txt | wc -l # 列出前几个文件名确认命名规则 ls images/ | head -20 ls labels/ | head -20如果图片数和 txt 数对不上说明有图没标或者有标没图。常见情况是背景图没有目标只有图片没有 txt这在 YOLO 里是合法的负样本但你要心里有数。另一种情况是标注文件用了 XMLVOC 格式或者 JSONCOCO 格式那就需要转换不能直接喂给 YOLO。提示番茄数据集里如果混了不同分辨率的图先统计一下尺寸分布。YOLO 训练时会统一 resize但极端长宽比的图会带来大量 padding影响小目标召回。2.2 标注格式核对YOLO txt 里每一行代表什么YOLO 格式的标注文件每行是class_id x_center y_center width height全部归一化到 0~1。很多人拿到数据集发现训练时 loss 不降最后查出来是标注没归一化坐标还是像素值。用下面这段脚本快速抽检。import os import glob def check_yolo_labels(label_dir, num_samples5): 抽检 YOLO 标注文件检查坐标是否归一化、类别 ID 是否合理 files glob.glob(os.path.join(label_dir, *.txt))[:num_samples] for f in files: with open(f, r) as fh: lines fh.readlines() print(f文件: {os.path.basename(f)}, 目标数: {len(lines)}) for line in lines: parts line.strip().split() if len(parts) ! 5: print(f 异常行: {line.strip()}) continue cls_id, x, y, w, h parts x, y, w, h float(x), float(y), float(w), float(h) # 归一化坐标应该在 0~1 之间 if not all(0 v 1 for v in [x, y, w, h]): print(f 坐标未归一化: {line.strip()}) # 类别 ID 应该是整数 if not cls_id.isdigit(): print(f 类别 ID 异常: {cls_id}) check_yolo_labels(labels/train)这段脚本做三件事检查每行是否有 5 个字段、坐标是否在 0~1 范围内、类别 ID 是否为整数。如果发现坐标超过 1基本可以确定是像素坐标没归一化需要写转换脚本除以图片宽高。如果类别 ID 出现字母或者超出预期类别数说明标注工具导出时出了问题。2.3 类别分布统计别让某一类把模型带偏番茄检测通常至少分「成熟」「未成熟」两类有的还分「半熟」「病害果」。类别不平衡是农业数据集的通病——成熟果实往往比未成熟的多或者反过来。统计一下每个类别的框数量心里有数。import os import glob from collections import Counter def count_classes(label_dir): 统计所有标注文件中的类别分布 counter Counter() for f in glob.glob(os.path.join(label_dir, *.txt)): with open(f, r) as fh: for line in fh: parts line.strip().split() if parts: counter[parts[0]] 1 return counter train_dist count_classes(labels/train) val_dist count_classes(labels/val) print(训练集类别分布:, dict(train_dist)) print(验证集类别分布:, dict(val_dist))如果某一类占比超过 80%训练时可以考虑用cls损失加权或者对少样本类做过采样。YOLOv8 本身没有直接提供类别权重参数但可以在数据集层面复制少样本图片来平衡。另一个常见问题是验证集里某个类别完全没有导致该类 mAP 为 0这不是模型的问题是划分的问题。3. 从原始标注到 YOLO 可训练格式转换、划分与配置文件3.1 VOC/COCO 转 YOLO两种最常见格式的转换脚本如果你拿到的番茄数据集标注是 XMLVOC或 JSONCOCO需要先转成 YOLO txt。VOC 转 YOLO 的核心是用xml.etree.ElementTree解析出bndbox的xmin/ymin/xmax/ymax再除以图片宽高得到归一化中心点和宽高。import xml.etree.ElementTree as ET import os from PIL import Image def voc_to_yolo(xml_path, img_path, class_map, output_dir): 将单个 VOC XML 转为 YOLO txt class_map: {tomato_ripe: 0, tomato_unripe: 1} tree ET.parse(xml_path) root tree.getroot() img Image.open(img_path) img_w, img_h img.size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_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) # 计算归一化中心点和宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 写入同名 txt base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(output_dir, base .txt), w) as f: f.write(\n.join(lines))class_map是你自己定义的类别名到 ID 的映射必须和后面 YAML 里的names顺序一致。x_center和y_center是框中心点坐标不是左上角。转换完记得抽几张图用可视化脚本画框确认别直接开训。3.2 训练集/验证集划分别让同一颗番茄出现在两边如果数据集没有预先划分你需要自己分。常见做法是按 8:2 或 7:3 随机划分。但番茄数据有个坑如果同一颗番茄被拍了多张不同角度、不同光照这些图必须分到同一侧否则验证集里出现训练集见过的番茄mAP 虚高。import os import shutil import random from pathlib import Path def split_dataset(img_dir, label_dir, output_dir, val_ratio0.2, seed42): 按比例划分训练集和验证集图片和标注同步移动 random.seed(seed) img_dir, label_dir Path(img_dir), Path(label_dir) images sorted([f for f in img_dir.iterdir() if f.suffix.lower() in (.jpg, .png, .jpeg)]) random.shuffle(images) split_idx int(len(images) * (1 - val_ratio)) train_imgs, val_imgs images[:split_idx], images[split_idx:] for subset, imgs in [(train, train_imgs), (val, val_imgs)]: (output_dir / images / subset).mkdir(parentsTrue, exist_okTrue) (output_dir / labels / subset).mkdir(parentsTrue, exist_okTrue) for img in imgs: shutil.copy(img, output_dir / images / subset / img.name) label label_dir / (img.stem .txt) if label.exists(): shutil.copy(label, output_dir / labels / subset / label.name) split_dataset(raw/images, raw/labels, Path(dataset), val_ratio0.2)seed固定后每次划分结果一致方便复现。如果你的番茄图有分组信息比如按拍摄批次把random.shuffle换成按组划分更稳妥。3.3 data.yaml 怎么写路径、类别数和 names 的顺序YOLOv8 训练需要一个 YAML 配置文件指定训练/验证路径和类别信息。# tomato.yaml path: /home/user/dataset # 数据集根目录 train: images/train val: images/val nc: 2 # 类别数 names: 0: tomato_ripe 1: tomato_unripepath是根目录train和val是相对路径。nc必须和names的条目数一致names的 ID 必须和标注文件里的 class_id 对应。我见过有人把nc写成 3 但names只列了 2 个训练直接报索引越界。改完 YAML 用yolo checks验证一下环境再开训。4. 用 YOLOv8 跑通番茄检测训练命令、参数含义与首轮验证4.1 最小训练命令与每个参数的实际作用假设你已经装好 ultralytics数据集按上面的结构放好最小训练命令如下。yolo detect train \ datatomato.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/tomato \ nameexp1modelyolov8n.pt用的是 COCO 预训练权重番茄检测属于迁移学习用预训练比从零训收敛快得多。imgsz640是输入分辨率番茄如果普遍较小可以提到 1280但显存占用翻倍。batch16在 8G 显存上跑 640 分辨率基本够用不够就降到 8。patience20表示 20 轮验证指标不提升就早停避免过拟合。lr00.01是初始学习率YOLOv8 默认用 SGD 时会自动调但显式指定更可控。注意如果你用的是 YOLOv8 的yolov8n.pt第一次运行会自动下载权重。如果网络环境不稳定提前手动下载放到当前目录然后model./yolov8n.pt。4.2 训练过程中该盯哪些指标训练日志里重点看三个box_loss、cls_loss、mAP50。box_loss下降说明框回归在收敛cls_loss下降说明分类在学。如果box_loss震荡不降检查标注是否有大量框重叠或者宽高为 0。mAP50是 IoU0.5 时的平均精度番茄检测第一轮能到 0.5 以上算正常如果一直在 0.1 附近大概率是标注格式或类别映射有问题。# 训练结束后用验证集跑一次评估 yolo detect val \ modelruns/tomato/exp1/weights/best.pt \ datatomato.yaml \ imgsz640验证输出会给出每个类别的 precision、recall、mAP50、mAP50-95。番茄检测里 recall 通常比 precision 更重要——漏检一颗成熟番茄的代价比误检高。如果 recall 明显低于 precision可以降低置信度阈值或者增加训练轮数。4.3 用训练好的模型跑单张图推理训练完先别急着部署拿几张验证集里的图跑推理肉眼看看框得准不准。from ultralytics import YOLO model YOLO(runs/tomato/exp1/weights/best.pt) results model.predict( sourcedataset/images/val, conf0.25, # 置信度阈值 iou0.45, # NMS IoU 阈值 saveTrue, # 保存带框图片 projectruns/tomato, namepredict )conf0.25是只保留置信度高于 0.25 的框番茄场景可以调到 0.3 减少误检。iou0.45是 NMS 的 IoU 阈值如果同一颗番茄出现多个重叠框把这个值调低。推理结果保存在runs/tomato/predict下打开图片看有没有漏检、误检、框偏移。5. 番茄数据集训练避坑从 loss 不降到 mAP 虚高的排查清单5.1 现象训练 loss 一直不降mAP 接近 0原因最常见的是标注坐标没归一化或者data.yaml里的names和标注 class_id 对不上。另一种可能是图片路径写错YOLO 实际读到的是空图。解决用 2.2 的脚本抽检标注确认坐标在 0~1。检查data.yaml的path和train/val拼接后能否找到图片。如果路径含中文或空格改成纯英文路径。5.2 现象训练到一半 loss 突然变成 NaN原因学习率太大导致梯度爆炸或者某张图的标注框宽高为 0除零。番茄数据集里如果标注工具导出时框到了图片边界外归一化后宽高可能为负。解决把lr0从 0.01 降到 0.001加warmup_epochs3。同时用脚本过滤掉宽高小于 0.001 的标注行。5.3 现象验证集 mAP 很高但实际推理漏检严重原因训练集和验证集划分时同一颗番茄的多个角度图被分到了两边验证集相当于「见过」。或者验证集图片和训练集来自同一拍摄批次光照背景高度相似。解决按拍摄批次或番茄个体分组划分确保验证集里的番茄在训练集里没出现过。如果做不到至少按时间顺序划分——用早期批次训练后期批次验证。5.4 现象某些类别 mAP 始终为 0原因该类在验证集里没有样本或者样本数极少少于 10 个框。YOLO 的 mAP 计算对少样本类不稳定。解决检查验证集类别分布如果某类缺失从训练集里匀几张过去。如果某类本身样本就少考虑合并类别比如把「半熟」并入「未成熟」或者用过采样复制该类的图片。5.5 现象推理时同一颗番茄出现多个框原因NMS 的 IoU 阈值设得太高或者模型对重叠目标学得不好。番茄成串生长时相邻果实框重叠度高NMS 容易误删或保留过多。解决把推理时的iou从默认 0.7 降到 0.45~0.5。如果仍然过多检查训练时是否有大量重叠框标注——标注时尽量让框贴合单个果实不要一个大框套多个。6. 让番茄检测真正能用提升小目标召回与部署前的量化检查训练出一个 mAP 0.8 的模型只是起点真正放到大棚或者分拣线上你还会遇到小番茄漏检、逆光过曝、模型体积太大跑不动的问题。这一章讲两个我实际用过的技巧一个针对小目标召回一个针对部署前的模型体检。先说小目标。番茄数据集里如果包含大量远距离拍摄的小果实640 分辨率下这些目标可能只有十几个像素YOLOv8 的 P3 特征图感受野不够。我一般会把imgsz提到 1024 或 1280同时把model换成yolov8s.pt或yolov8m.pt参数量大一些对小目标更友好。如果显存不够用yolov8n.pt但开启multi_scaleTrue让模型在训练时随机缩放输入尺寸增强对不同尺度目标的适应。另一个技巧是在data.yaml里加augment: TrueYOLOv8 默认开启 mosaic 和 mixup但番茄场景我建议关掉 mixup——把两张图叠在一起会让果实颜色失真反而干扰成熟度分类。# 小目标召回优化训练配置 yolo detect train \ datatomato.yaml \ modelyolov8s.pt \ epochs150 \ imgsz1024 \ batch8 \ lr00.005 \ mosaic1.0 \ mixup0.0 \ scale0.5 \ patience30mosaic1.0保持默认的四图拼接mixup0.0关掉混合scale0.5让图片随机缩放幅度加大。imgsz1024下batch8在 8G 显存上勉强能跑再大就 OOM。再说部署前的量化检查。模型导出 ONNX 或 TensorRT 之前先确认三件事输入尺寸是否固定、输出节点是否包含 NMS、类别顺序是否和训练一致。YOLOv8 导出命令很简单但导出后一定要用同一张图对比 PyTorch 和 ONNX 的输出。from ultralytics import YOLO import numpy as np # 导出 ONNX model YOLO(runs/tomato/exp1/weights/best.pt) model.export(formatonnx, imgsz640, simplifyTrue) # 对比 PyTorch 和 ONNX 推理结果 img dataset/images/val/sample.jpg pt_result model.predict(img, conf0.25)[0] onnx_model YOLO(runs/tomato/exp1/weights/best.onnx) onnx_result onnx_model.predict(img, conf0.25)[0] print(PyTorch 框数:, len(pt_result.boxes)) print(ONNX 框数:, len(onnx_result.boxes)) # 如果框数差异超过 1~2 个检查导出时的 opset 和 simplify 设置如果 ONNX 和 PyTorch 的框数差异大优先检查opset版本YOLOv8 建议 12 以上和simplifyTrue是否生效。部署到边缘设备时TensorRT 的 FP16 量化通常能提速 2 倍左右但番茄检测里 FP16 对置信度的影响很小可以放心用。INT8 量化则需要校准集我一般从验证集里抽 200 张图做校准量化后 mAP 掉超过 3 个点就回退到 FP16。最后说一个我踩过的坑番茄数据集里的图片如果带 EXIF 旋转信息用 PIL 读取时不会自动旋转但标注是按旋转后的图标的。训练时 YOLO 用 OpenCV 读图OpenCV 会自动应用 EXIF 旋转导致图片和标注错位。解决办法是在转换标注前统一用 PIL 读图并ImageOps.exif_transpose校正或者直接用 OpenCV 重新读一遍所有图并覆盖保存。这个坑很隐蔽因为大部分图没有旋转信息只有手机拍的少数几张会触发但一旦触发就是整张图的框全错。我现在的习惯是拿到任何标注数据集先跑一遍体检脚本再抽 10 张图可视化标注确认没问题才开训。这个流程帮我省下了大量「训了半天发现数据有问题」的时间。希望帮到你。本文还有配套的精品资源点击获取