简介:这份资源是面向计算机、电子信息工程、数学等专业学生及算法初学者的番茄目标检测数据集,可直接用于课程设计、期末大作业与毕业设计。数据由作者自行打标,与站点现有番茄数据集均不重复,图片清晰度高、标注准确,适合训练和验证YOLO系列检测模型。压缩包共303个文件,包含150张高清jpg图像、150个同名txt标注文件,以及2个cache缓存和1个xml文件,整体约387MB,jpg与txt一一对应,便于直接接入YOLO训练流程,cache文件可加速数据读取。目前已有325人学习下载。资源还附带参数化编程思路与清晰注释,方便读者理解数据组织方式、调整训练参数并复现检测实验,为番茄目标检测任务提供开箱即用的数据基础与排错参考。
1. 番茄检测数据集到手之后:先别急着 train,把这几件事捋清楚
拿到一个标注好的番茄高清数据集,很多人的第一反应是直接丢进 YOLO 里跑train.py,然后盯着 loss 曲线发呆。但真实情况往往是:跑起来了,mAP 却上不去,或者验证集表现和训练集差一大截。问题通常不在模型,而在数据本身——图片命名混乱、标注格式不统一、类别 id 对不上、缓存文件残留。这份番茄数据集的价值在于「已标注、可直接用」,但「可直接用」的前提是你知道它长什么样、该怎么接进 YOLO 的训练管线。它适合做课程设计、期末大作业、毕业设计里目标检测方向的同学,也适合想快速验证一个检测想法、不想从零标注的工程师。图片是高清的,标注是人工逐张打的,和网上那些批量爬取、粗标的数据集不是一回事。接下来我会按「先看清数据 → 再配环境 → 再跑训练 → 再避坑 → 最后进阶」的顺序,把这份资源从解压到出结果的全过程拆一遍。
2. 拆开压缩包先看什么:文件结构、命名规律与标注格式核对
2.1 从文件清单反推数据集的组织方式
解压之后你会看到类似这样的文件列表:train.cache、val.cache、IMG_1335.JPG、IMG_1399.JPG、IMG_1394.JPG、IMG_1381.JPG、IMG_1386.JPG、IMG_1375.JPG、IMG_1387.JPG、IMG_1264.JPG。这里有几个信号值得注意。
第一,出现了train.cache和val.cache。这是 YOLOv5/v8 系列在首次训练时自动生成的缓存文件,里面存的是图片路径、尺寸、标签索引等元信息。它的存在说明这份数据至少被 YOLO 跑过一轮,或者作者在打包时把缓存一起放进来了。缓存文件的好处是第二次训练不用重新扫描,坏处是如果你改了图片或标签,缓存不会自动更新,会导致「改了标注但训练没变化」的玄学现象。
第二,图片命名是IMG_xxxx.JPG这种相机默认命名。这种命名方式本身没问题,但要注意两点:一是编号不连续(1335、1399、1394、1381……),说明是从一批照片里筛选过的;二是全部大写.JPG扩展名,在 Linux 下大小写敏感,如果你用脚本按.jpg去匹配会漏文件。
第三,正文里只列了图片和 cache,没有显式列出labels文件夹或.txt标注文件。但标题明确写了「已标注」,所以标注文件一定在压缩包里,只是正文没展开。常见做法是images/和labels/两个平行目录,或者图片和同名.txt放在一起。
2.2 核对标注格式:YOLO txt 的五个字段
YOLO 系列用的是归一化坐标的 txt 标注,每行五个值:
class_id x_center y_center width height其中class_id从 0 开始,后四个都是相对于图片宽高的归一化值(0~1 之间)。番茄检测如果只检测「番茄」一类,那所有行的第一个值都应该是0。如果你打开某个.txt发现第一个值是1甚至更大,说明类别不止一类,或者标注时类别 id 写错了。
我一般会先跑一段脚本做体检,确认图片和标签一一对应、坐标没有越界、类别 id 没有异常:
import os from pathlib import Path img_dir = Path("images") lbl_dir = Path("labels") img_files = {p.stem for p in img_dir.glob("*.JPG")} | {p.stem for p in img_dir.glob("*.jpg")} lbl_files = {p.stem for p in lbl_dir.glob("*.txt")} # 检查是否有图片没有对应标签,或标签没有对应图片 missing_lbl = img_files - lbl_files missing_img = lbl_files - img_files print("无标签的图片:", missing_lbl) print("无图片的标签:", missing_img) # 检查标注内容合法性 for lbl in lbl_dir.glob("*.txt"): with open(lbl) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: print(f"{lbl.name} 第{i+1}行字段数不对: {parts}") continue cls, x, y, w, h = parts vals = list(map(float, [x, y, w, h])) if any(v < 0 or v > 1 for v in vals): print(f"{lbl.name} 第{i+1}行坐标越界: {vals}")这段脚本做三件事:集合运算找出图片和标签的差集、逐行检查字段数是否为 5、检查归一化坐标是否在 0~1 之间。参数上唯一需要改的是img_dir和lbl_dir的路径,以及扩展名匹配规则。如果你拿到的数据集图片和标签混在同一个目录,把两个路径都指向同一目录即可,但要注意glob("*.txt")不会把图片误算进去。
提示:如果发现大量「无标签的图片」,先别急着删。有些数据集会把负样本(没有目标的背景图)也放进来,这种情况标签文件为空是正常的。但空标签文件在 YOLO 训练里会被当作纯背景,数量太多会拉低召回。
2.3 缓存文件该不该留:train.cache 的处理策略
train.cache和val.cache是 YOLO 的加速缓存,本质是一个 pickle 文件,存了每张图的形状、路径和标签。它的失效条件是:图片增删、标签修改、图片尺寸变化。只要你动了数据集里任何一个文件,缓存就和实际数据不一致了。
我的习惯是:拿到新数据集,先把.cache文件删掉,让 YOLO 重新生成。原因很简单——你无法确认这份缓存是在什么版本的 YOLO、什么路径结构下生成的。如果作者打包时的目录结构和你的解压路径不同,缓存里的绝对路径会全部失效,训练时直接报FileNotFoundError。
# 删除所有缓存文件,强制重新生成 find . -name "*.cache" -delete这条命令在当前目录及子目录下查找所有.cache文件并删除。执行后第一次训练会慢一些(要重新扫描所有图片),但能避免路径不一致导致的翻车。删完之后,YOLO 会在训练启动时自动在数据集根目录生成新的train.cache和val.cache。
3. 把数据集接进 YOLO 训练管线:目录改造、yaml 配置与首轮跑通
3.1 目录结构改造:从散落到标准布局
YOLOv5/v8 官方推荐的结构是:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml但你拿到的压缩包大概率是扁平的:所有图片在一个目录,所有标签在另一个目录,或者图片和标签混在一起。这时候需要做一次划分和搬运。我一般用脚本按 8:2 切分训练集和验证集:
import random import shutil from pathlib import Path random.seed(42) # 固定随机种子,保证可复现 src_img = Path("images") src_lbl = Path("labels") dst = Path("dataset") for split in ["train", "val"]: (dst / "images" / split).mkdir(parents=True, exist_ok=True) (dst / "labels" / split).mkdir(parents=True, exist_ok=True) # 收集所有有标签的图片 stem stems = [p.stem for p in src_lbl.glob("*.txt")] random.shuffle(stems) split_idx = int(len(stems) * 0.8) train_stems = stems[:split_idx] val_stems = stems[split_idx:] def move_files(stem_list, split): for stem in stem_list: # 图片可能是 .JPG 或 .jpg,两个都试 for ext in [".JPG", ".jpg", ".png"]: img_path = src_img / (stem + ext) if img_path.exists(): shutil.copy(img_path, dst / "images" / split / img_path.name) break lbl_path = src_lbl / (stem + ".txt") if lbl_path.exists(): shutil.copy(lbl_path, dst / "labels" / split / lbl_path.name) move_files(train_stems, "train") move_files(val_stems, "val") print(f"训练集 {len(train_stems)} 张,验证集 {len(val_stems)} 张")这里用random.seed(42)固定划分结果,避免每次跑出来的训练集不一样导致指标无法对比。shutil.copy而不是move,是为了保留原始文件,万一划分有问题还能重来。扩展名按.JPG、.jpg、.png依次尝试,兼容大小写和不同格式。
3.2 data.yaml 的五个关键字段
YOLO 训练靠一个 yaml 文件告诉它数据在哪、有几类、类名是什么。番茄检测通常是单类,配置如下:
path: ./dataset train: images/train val: images/val nc: 1 names: ['tomato']逐字段说明:path是数据集根目录,可以用相对路径或绝对路径;train和val是相对于path的图片目录;nc是类别数量,番茄只有一类就写 1;names是类别名列表,顺序必须和标注文件里的class_id对应。如果你打开标注发现 class_id 有 0 和 1 两种,那nc要改成 2,names也要加一项。
注意:
names的顺序不能乱。如果标注里 0 代表「成熟番茄」、1 代表「未成熟番茄」,你写成['unripe', 'ripe']就把两类搞反了,训练出来的模型会把成熟番茄识别成未成熟。核对方法很简单:随便打开一个标注文件,看第一个值,再去图片里找对应位置的目标,确认类别语义。
3.3 首轮训练命令与参数含义
环境配好、目录改好之后,用 YOLOv8 跑首轮训练:
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ project=runs/tomato \ name=exp1参数逐个说:data指向刚才写的 yaml;model用yolov8n.pt这种预训练权重做迁移学习,比从零训练收敛快得多;epochs=100是训练轮数,番茄这种单类任务通常 100 轮够用,但要看 loss 是否还在降;imgsz=640是输入分辨率,高清图片会被缩放到 640,如果你显存够可以调到 1280,小目标检测效果会更好;batch=16是批大小,显存不够就降到 8 或 4;project和name控制输出目录,方便多次实验对比。
首轮跑通的标准不是 mAP 多高,而是:训练能启动、loss 在下降、验证集能跑完、runs/tomato/exp1/weights/best.pt能生成。如果卡在数据加载阶段,八成是路径或缓存问题;如果 loss 一直是nan,检查标注里有没有坐标越界或空文件。
3.4 训练过程中的三个观察点
跑起来之后别只盯着进度条。我一般会看三个东西:train/box_loss、val/box_loss和metrics/mAP50。box_loss下降说明模型在学定位;如果train/box_loss降但val/box_loss不降甚至上升,就是过拟合,需要加数据增强或减模型复杂度;mAP50是 IoU 阈值 0.5 时的平均精度,番茄单类任务跑到 0.9 以上算正常,低于 0.7 就要回头查标注质量。
YOLOv8 默认会在runs/tomato/exp1/下生成results.csv,里面记录了每个 epoch 的这些指标。用 pandas 读出来画个曲线,比在终端里翻日志直观得多:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/tomato/exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能带空格 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df["epoch"], df["train/box_loss"], label="train") axes[0].plot(df["epoch"], df["val/box_loss"], label="val") axes[0].set_title("box_loss") axes[0].legend() axes[1].plot(df["epoch"], df["metrics/mAP50"], label="mAP50") axes[1].set_title("mAP50") axes[1].legend() plt.tight_layout() plt.savefig("training_curve.png")这段代码读results.csv,把列名两端的空格去掉(YOLO 输出的列名有时带空格,直接索引会 KeyError),然后画 loss 和 mAP 两条曲线。保存成图片后可以直观判断是否过拟合、是否还需要继续训练。
4. 标注质量与数据划分的避坑清单:五条血泪经验
4.1 现象:训练 loss 正常下降,但验证集 mAP 始终在 0.5 以下
原因通常不是模型不行,而是标注框和实际目标偏差太大。人工标注时如果框得太松(把背景也框进去)或太紧(只框了番茄的一部分),模型学到的定位边界就是错的。番茄这种圆形目标尤其容易框成正方形,导致 IoU 天然偏低。
解决办法:抽 20 张图用labelImg或labelme可视化标注框,逐张核对。如果发现系统性偏移(比如所有框都偏右下),可能是标注工具导出时坐标系搞错了。修正后删掉.cache重新训练。
4.2 现象:训练时提示No labels found in ...
原因有两个:一是标签目录路径写错了,YOLO 去找labels/train/但实际文件在labels/;二是标签文件扩展名不是.txt,或者文件名和图片名不完全一致(比如图片是IMG_1335.JPG,标签是IMG_1335.txt,这没问题;但如果标签是img_1335.txt,大小写不一致就会漏)。
解决办法:用前面 2.2 节的体检脚本先跑一遍,确认missing_lbl为空。如果路径结构特殊,在data.yaml里把train和val直接指向图片目录的绝对路径,YOLO 会自动在同级找labels目录。
4.3 现象:改了标注文件,重新训练结果完全没变
原因:train.cache和val.cache还在。YOLO 检测到缓存文件存在就直接读缓存,不会重新扫描标签。这是最容易被忽略的坑,因为训练能正常启动,没有任何报错,只是结果不对。
解决办法:每次修改数据集后,执行find . -name "*.cache" -delete,或者在训练命令里加--cache ram强制重建。我现在的习惯是训练脚本第一行就删缓存,宁可多等几分钟扫描,也不要被缓存骗。
4.4 现象:验证集图片和训练集图片高度相似,mAP 虚高
原因:随机划分时没有考虑图片来源。如果数据集里的图片是连续拍摄的(比如同一颗番茄的不同角度),随机划分会把相似图片分到训练和验证两边,导致验证集「见过」类似样本,指标虚高但实际部署效果差。
解决办法:按拍摄批次或时间划分,而不是完全随机。如果图片命名有规律(比如IMG_13xx是一批、IMG_12xx是另一批),可以按编号段划分。更严格的做法是做交叉验证,但课程设计级别按批次划分就够了。
4.5 现象:训练到一半显存溢出(CUDA out of memory)
原因:batch或imgsz设太大,或者workers开太多导致内存泄漏。高清图片在数据加载时占用内存比模型本身还大。
解决办法:先把batch降到 8 或 4,再把imgsz从 640 降到 416 试一轮。如果还溢出,把workers设为 0(主进程加载数据,慢但不爆内存)。另外可以在训练命令里加amp=False关闭混合精度,虽然慢一点但显存占用更稳定。
5. 从训练到推理:验证模型是否真的学到了番茄
5.1 用验证集跑一次批量推理
训练完成后,best.pt只是权重文件,它到底能不能检测番茄,要用没参与训练的图片验证。YOLO 提供了val模式:
yolo detect val \ model=runs/tomato/exp1/weights/best.pt \ data=dataset/data.yaml \ imgsz=640 \ batch=8这条命令会输出每类的 precision、recall、mAP50、mAP50-95。重点看mAP50-95,它比mAP50更严格,能反映定位精度。番茄单类任务如果mAP50在 0.9 以上但mAP50-95只有 0.5,说明框的位置还不够准,可能需要调整标注或增加训练轮数。
5.2 单张图片推理与结果可视化
验证指标是数字,但工程师更信自己的眼睛。用predict模式跑几张图,把检测框画出来:
yolo detect predict \ model=runs/tomato/exp1/weights/best.pt \ source=dataset/images/val \ conf=0.25 \ save=True \ project=runs/predict \ name=tomato_testconf=0.25是置信度阈值,低于这个值的检测框会被过滤掉。番茄检测如果漏检多,把conf降到 0.1 试试;如果误检多,提到 0.5。save=True会把画了框的图片存到runs/predict/tomato_test/,直接打开就能看效果。
5.3 导出 ONNX 做部署前的最后一步
如果这个模型最终要部署到其他平台(比如 C++ 推理、边缘设备),导出 ONNX 是常见做法:
yolo export \ model=runs/tomato/exp1/weights/best.pt \ format=onnx \ imgsz=640 \ simplify=Truesimplify=True会调用 onnx-simplifier 做图优化,去掉冗余算子。导出后的.onnx文件可以用 Netron 打开看结构,确认输入输出节点名称和维度。注意导出时的imgsz必须和训练时一致,否则推理结果会偏移。
5.4 一个容易被忽略的验证技巧:负样本测试
拿几张完全没有番茄的图片(比如叶子、土壤、其他水果)跑推理,看模型会不会误检。如果误检率高,说明训练集里负样本太少,模型没见过「没有番茄」的场景。解决办法是在训练集里加入 10%~20% 的纯背景图,标签文件留空即可。这个技巧在课程设计里很少被提到,但实际部署时能省很多麻烦。
从那以后我每次拿到新数据集,都强制走一遍「删缓存 → 体检标注 → 按批次划分 → 负样本测试」的流程,宁可前期多花半小时,也不要训练到一半才发现数据有问题。希望帮到你。
本文还有配套的精品资源,点击获取