多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

YOLO番茄目标检测实战:数据集格式转换、划分与迁移学习指南

YOLO番茄目标检测实战:数据集格式转换、划分与迁移学习指南 简介面向目标检测学习者的YOLO番茄数据集包含1000张真实场景高质量图片使用LabelImg标注且框体质量可靠提供VOC、COCO、YOLO三种格式标签可直接用于不同框架下的YOLO系列模型训练。压缩包共2000个文件包含1000个xml、990个txt、6个html教程、3个Python划分脚本和1个yaml配置整体约954MB。资源包在CSDN上已有1762人浏览学习。此外还附赠Windows/Linux双版本的YOLO环境搭建与训练教程以及训练集/验证集/测试集划分脚本方便按需拆分数据并快速跑通训练流程。这些内容让初学者不必额外查找资料即可从环境配置、数据准备到模型训练形成完整闭环适合课程设计、毕业设计和算法练手。1. 拿到YOLO番茄目标检测数据集压缩包你其实只差三步做番茄目标检测的人要么是被买标注数据的价格劝退要么是被自己标注三天三夜的体力活劝退。这份压缩包里给你备好了1000张番茄图片以及VOC、COCO、YOLO三种格式的标签还有划分脚本和训练教程意味着你省掉了数据采集、格式转换、训练集划分这三步最琐碎的前置工作。不要以为标签格式齐了就万事大吉格式怎么读、划分脚本参数怎么调、训练时损失函数为什么乱跳这些才是真正的坎。这篇文章就从拆开压缩包开始把每一步的原理和踩坑点讲清楚最终让你用这份数据跑出一个能用的YOLO番茄检测模型。2. VOC、COCO、YOLO三种标签格式看似相同坐标系完全不同一份图片配三种标签很多人以为只是文件后缀不同直接拿XML或者JSON就往YOLO训练代码里塞结果要么报错要么训练出来的框全在图片角落里。实际上这三种格式从存储结构到坐标定义都不同互相转换也不是简单的字符串替换。2.1 三种格式的存储结构与坐标定义VOC格式是XML文件一张图片对应一个XML。核心部分是object节点里面用bndbox存xmin, ymin, xmax, ymax这四个值是绝对像素坐标比如一张1920x1080的图框左上角坐标可能是(320, 240)。COCO格式是单个JSON文件标注数据集中所有图片每个标注记录bbox字段存的是[x, y, width, height]同样是绝对像素坐标但这里x, y是左上角坐标不是中心点。YOLO格式是txt文件每一行对应一个目标存class_id, x_center, y_center, width, height所有坐标都除以图片宽高做了归一化取值在0到1之间。这里最容易搞混的就是COCO的bbox和YOLO的cx, cy, w, h。COCO的四元组是左上角加宽高YOLO是中心点加宽高转换时多加两个减法运算。格式文件形态坐标元组坐标值域典型场景VOC一张图一个XMLxmin, ymin, xmax, ymax绝对像素早期Pascal VOC比赛、LabelImg导出COCO一个数据集一个JSONx, y, width, height绝对像素COCO数据集、Detectron2、MMDetectionYOLO一张图一个txtclass_id, cx, cy, w, h归一化0~1Ultralytics YOLO、Darknet2.2 从VOC到YOLO的转换归一化坐标是最大的坑如果你用的工具默认导出了VOC格式而你又想用现在主流的Ultralytics YOLO训练必须先把XML转成txt。转换代码看起来只有几行但80%的人翻车在忘记归一化或者把xmin, ymin当成了中心点。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path, class_names): # 解析XML文件 tree ET.parse(xml_path) root tree.getroot() # 读取图片尺寸YOLO归一化坐标必须依赖这组数值 img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text # 类别必须从0开始编号和训练时的names列表顺序一致 class_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) # 绝对像素 - 归一化除以宽高中心点用两个边相加除2 cx ((xmin xmax) / 2) / img_width cy ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) class_names [tomato] # 番茄数据集一般单类多类时保持顺序 voc_to_yolo(images/0001.xml, labels/0001.txt, class_names)这段代码的逻辑是先解析宽高再遍历每个object取出真实框坐标然后算中心点和宽高最后除以图片宽高。参数里最容易忽略的是class_names的顺序训练时data.yaml里names怎么写这里就必须怎么排否则类别索引错位训练出来检测结果张冠李戴。COCO转YOLO的思路类似只是JSON里直接给了bbox的[x, y, w, h]中心点就是x w/2和y h/2。注意JSON里的category_id可能有偏移COCO数据集从1开始而你训练的类别索引从0开始需要建立映射表。这份数据集自带三种格式按理说不需要你手动转换但保不齐你以后要换数据集、给别人的数据集补标签这个转换逻辑迟早得写一次。3. 划分脚本怎么用训练集/验证集/测试集的比例与随机种子数据准备好之后不能把所有图片都丢进去训练一定要划分出验证集和测试集。验证集用于调参和早停测试集用于最终评估模型泛化能力。很多人偷懒不划分训练完直接在训练集上评估mAP虚高一到真实场景就露馅。3.1 脚本核心逻辑按文件名单划分压缩包里最常见的划分脚本是一个Python文件它做的事情可以概括成读取全部图片文件名按比例随机切分成train/val/test三份然后把对应的图片和标签复制到images/train、labels/train这类目录下。核心代码如下import os import random import shutil def split_dataset(img_dir, label_dir, output_dir, train_ratio0.7, val_ratio0.2, test_ratio0.1, seed42): random.seed(seed) # 固定随机种子保证每次划分结果可复现 imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(imgs) # 根据比例计算各集合的数量注意四舍五入和总数量校验 train_n int(len(imgs) * train_ratio) val_n int(len(imgs) * val_ratio) train_imgs imgs[:train_n] val_imgs imgs[train_n:train_n val_n] test_imgs imgs[train_n val_n:] for subset, names in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: os.makedirs(os.path.join(output_dir, images, subset), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, subset), exist_okTrue) for img_name in names: stem os.path.splitext(img_name)[0] # 图片和标签必须成对复制标签名主体部分和图片相同 shutil.copy(os.path.join(img_dir, img_name), os.path.join(output_dir, images, subset, img_name)) label_path os.path.join(label_dir, stem .txt) if os.path.exists(label_path): shutil.copy(label_path, os.path.join(output_dir, labels, subset, stem .txt)) else: print(f[警告] 缺少标签文件: {label_path})3.2 参数设置train/val/test比例与随机种子这个脚本有三个参数值得细究比例、随机种子、标签缺失处理。比例为0.7/0.2/0.1是目标检测任务比较稳妥的起点1000张图片里用700张训练、200张验证、100张测试验证集和测试集都足够反映真实分布。如果图片只有300张建议改成0.8/0.1/0.1甚至用交叉验证的方式跑但那样训练成本高。如果你的番茄图片包含多种光照、成熟度、遮挡程度比例最好保持默认因为验证集太少会导致早停判断失真。随机种子固定为42或2024都行它的意义在于可复现。你跑第一次划分训练出A结果改了个小参数再跑如果种子不固定划分出的图片不一样你就说不清涨点到底来自参数还是数据。标签缺失的处理很关键。有些划分脚本只复制图片不管标签结果训练时一半图片没有对应的txtYOLO会直接跳过或报错。上面这段代码在缺失标签时会打印警告属于最基础的兜底。我一般还会加一段统计划分后在每个子集的labels目录里数一数txt数量和图片数量比对不一致就停止。4. 基于Ultralytics YOLO的训练教程从环境配置到跑通标签和划分都就绪后进入训练阶段。现在最主流的选择是Ultralytics YOLO它的环境配置对零基础用户很友好一条pip命令装完就能用。网上搜YOLOv11(Ultralytics)环境配置会看到很多教程但真正动手时翻车的点往往不在安装而在数据集路径和yaml文件。4.1 环境配置与数据集yaml安装Ultralytics只需要一个命令但建议在训练前用pip安装最新版因为旧版本对某些数据增强参数支持不全。pip install ultralytics装完后验证一下CUDA是否可用否则训练会龟速跑在CPU上。python -c import torch; print(torch.cuda.is_available())输出True说明GPU可用False则要检查PyTorch版本和NVIDIA驱动。接着准备游戏规则文件tomato.yamlYOLO会从这里读取数据路径和类别名。这个文件必须放在项目目录下路径建议写绝对路径或者写相对于当前工作目录的路径否则容易找不到数据。# tomato.yaml path: /home/user/tomato_dataset # 数据集根目录 train: images/train # 相对path的路径 val: images/val test: images/test nc: 1 # 类别数这里只有番茄一类 names: [tomato] # 类别名顺序必须和标签txt里的class_id对应如果你的数据集结构是images/train_imgs放在一个目录、labels/train_imgs放在另一个目录Ultralytics要求的是images/train和labels/train在同一个根目录下且图片和标签子目录名完全一致。如果压缩包里的划分脚本已经生成了标准结构那直接填路径就行。4.2 训练命令与关键超参数模型选择上1000张图片的规模不适合直接上YOLO11x这种大模型过拟合风险高。常见做法是从yolov8n.pt或yolov11n.pt这种轻量级预训练权重开始或者从m模型开始。用官方预训练权重做迁移学习比从随机初始化训练收敛快得多。yolo detect train datatomato.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0这句命令做的事情是用COCO预训练的yolov8n权重在我们的番茄数据上微调100个epoch。这里的epochs100对于1000张图不算多因为配置好的Ultralytics会在验证集上做早停一般到第50轮左右就会触发。几个核心参数如果你想手动调可以在命令里覆盖imgsz640输入分辨率番茄是小目标多还是大目标多如果图片里番茄占画面比例小建议调成imgsz1280但显存占用会翻倍。batch16显存不足就降到8显存吃紧时观察nvidia-smi的显存占用率不要盲目开大。lr00.01默认即可数据量少时学习率太大容易震荡。patience30早停耐心值验证集连续30轮没提升就停止训练。device0指定第一块GPU多卡时用device0,1。训练过程中你会看到四个loss值逐步下降box_loss边框回归损失、cls_loss分类损失、dfl_loss分布焦点损失以及综合的loss。我见过不少新手看到loss在0.02附近波动就以为收敛了其实要看验证集上的mAP50-95是不还在涨涨就继续训不涨就停。训练结束后权重保存在runs/detect/train/weights/best.pt注意best.pt是按验证集mAP选出来的不是最后一个epoch的last.pt。以后部署或继续训练都用best.pt。5. 番茄检测训练中的5个常见坑与排查用这份数据集训练的人不少翻车现象也高度集中。我把实践中遇到最多的5个问题按下述格式写出来按现象、原因、解决的顺序帮你排查。5.1 现象loss不降卡在初始值附近徘徊原因划分后的图片里有很多空白背景图片或者标签文件和图片名对不上YOLO读到的是空txt模型在纯背景上反复学习梯度更新缓慢。解决打印每个子集labels里的txt文件行数如果大量为0说明划分脚本复制标签时漏掉了空标签文件。用脚本把没有目标的图片从训练集里剔除或者退回去重新执行划分检查警告输出。5.2 现象训练能跑通但预测画出来的框全部偏左上角原因标签坐标用了绝对像素值没有除以图片宽高归一化。比如图片1920x1080中心点写成了(960, 540)这种数值模型理解成框占满了图片预测自然诡异。解决先用2.2节的转换脚本把所有txt检查一遍看坐标值是否都在0~1之间。如果出现了大于1的数值重新做VOC转YOLO或从COCO转换时用bbox[0]/width而不是直接填。5.3 现象一个番茄被识别成两个框NMS去不掉原因多个标注框重叠严重可能是VOC转换时框坐标用错同一个目标被重复标注或者标注本身有大量遮挡重叠。解决查看labels里的txt同一行目标之间如果IOU过高用脚本去重。常见做法是按置信度或面积保留一个框删除另一个。这不是训练参数问题是标注质量问题。5.4 现象用yolov8n训练100轮总显存溢出原因batch16在imgsz640时对8GB显存的卡稍高而且数据增强中mosaic1.0会临时拼接4张图显存峰值比单图高很多。解决把batch降到4或者8或者设mosaic0.5在后期关掉。还有一种做法是ampTrue开启自动混合精度官方默认开的如果你手动关了就重新打开。5.5 现象混淆矩阵里背景类占比极高番茄漏检严重原因类别不均衡不是主要问题更常见的是图片里番茄和叶子颜色相近模型学到的特征不够。也可能是cls_loss权重太低分类分支对正样本的敏感度不足。解决先检查标签框是否太小大量宽高小于0.05的小目标被下采样后特征消失这时用imgsz1280重训。或者用hsv_h0.015这类增强参数提高颜色扰动强迫模型关注纹理和轮廓而非纯颜色。6. 用迁移学习把1000张番茄图用出1万张的效果数据集只有1000张如果从零初始化训练yolov8smAP50能到0.9但泛化到真实大棚场景会掉到0.5以下。我有两个习惯能显著缓解这个问题。第一个习惯是分阶段解冻训练。先用权重文件里的全部参数在datatomato.yaml上训练50轮此时特征提取层的参数已经变化。然后冻结backbone的前10层只训练检测头和颈部网络再把学习率调到lr00.001继续训30轮。冻结层数可以这样控制训练时设freeze10Ultralytics会把前10层参数锁死。这种做法适合数据量小的场景防止backbone被少量数据带偏。第二个习惯是用数据增强撑过前期过拟合。除了Ultralytics默认的mosaic、flip、hsv扰动我强烈建议把degrees10和translate0.2打开让模型看到稍微旋转和平移的番茄。如果部署场景是固定摄像头角度扰动可以小一点避免引入训练分布外的样本。验证阶段别只盯着mAP曲线。跑完评估后我用yolo predict在测试集上随机抽几张图结合runs/detect/val/confusion_matrix.png和PR_curve.png看。番茄目标小且密集时PR曲线的召回率在0.8之前就会掉这时候调整conf阈值到0.25而不是0.5。代码里加个conf0.25即可改善小目标漏检。我之前接过一个温室番茄检测的项目一开始也是拿别人的数据集上来就跑结果训练完在实拍视频里一串青番茄全漏掉。后来发现是标注框里混了不少未成熟的番茄样本标签文件里类别名写成了tomato_green和tomato_red两种而训练yaml里只写了tomato。这种事只有仔细核对标签文件内容才会暴露。从那以后我拿到任何数据集的第一件事就是打印每个txt的行数和类别索引分布确认和yaml对得上再动手训练。希望你也能少走这些弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表