news 2026/9/14 3:33:09

小番茄目标检测数据集:从VOC XML到YOLO训练全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小番茄目标检测数据集:从VOC XML到YOLO训练全流程解析

简介:这是一份面向计算机视觉与智慧农业领域的小番茄目标检测数据集,包含不同光照、拍摄角度下的果实图像及对应XML标注,能够支撑YOLO等主流目标检测算法的训练、验证与优化,常用于果实成熟度判别、自动化采摘等任务。资源包总计1790个文件,内含895张PNG格式实景图片与895个XML标注文件,标注记录了边界框坐标与类别标签,整体压缩包大小约180.33MB,目录结构规整,便于直接划分训练集、测试集进行模型评估。目前已有79人浏览学习,对于农业检测方向的研究者与学生来说,是一份轻量、可直接上手的实战数据。借助该数据集,可以系统练习YOLO系列模型的训练和参数调优,也可引入迁移学习与数据增强策略提高模型泛化能力,为小番茄的精准识别与农业智能化落地提供扎实的数据基础,甚至可在此基础上进一步探索改进检测算法的思路。

1. 小番茄检测先解决数据问题

做农业视觉落地的朋友应该都有同感:模型结构不是瓶颈,数据才是。一个在COCO上表现很好的YOLO模型,换到温室大棚里识别小番茄,mAP能掉到没法看——因为果实密集、互相遮挡、光照不均,还有成熟度带来的颜色差异。要在这类场景下把检测效果做起来,第一步不是调参,而是拿到一份标注质量合格、分布合理的数据集。这份小番茄目标检测数据集就是干这个用的:图片来自真实拍摄角度,标注采用XML格式,按PASCAL VOC的标准记录每个果实的边框位置和类别信息,可以直接用来训练YOLOv5、YOLOv8或者R-CNN系列模型。适合做农业检测算法验证、迁移学习基线测试,也适合当作学习目标检测流程的入门数据。下面从数据格式、转换脚本到训练配置,把整个链路拆开讲。

2. 理解数据集结构与XML标签格式

2.1 图片与标注文件的组织方式

拿到压缩包后,先看目录结构。常见的组织方式有两种:一种是图片和XML文件放在同一个文件夹,另一种是分imagesannotations两个子目录。这套小番茄数据集里图片文件名为tomato784.pngtomato394.png这种序号格式,对应的XML文件名与图片名完全一致,后缀不同。同名的XML就是这张图的标注文件,训练脚本读取时通过文件名前缀来关联。

├── images/ │ ├── tomato784.png │ ├── tomato394.png │ ├── tomato259.png │ └── ... └── annotations/ ├── tomato784.xml ├── tomato394.xml ├── tomato259.xml └── ...

如果压缩包内图片和XML混在一起,先用下面命令把标注文件筛出来,按项目结构重新整理。-name参数指定匹配后缀,exec对筛选结果执行移动操作,{}是find找到的每个文件的占位符。

mkdir -p images annotations for f in *.xml; do mv "$f" annotations/ done for f in *.png; do mv "$f" images/ done

2.2 XML里到底存了什么信息

打开任意一个XML文件,核心结构是annotation根节点下的几个子节点。size记录图片宽高和通道数,object节点每出现一次,就代表图里的一个检测目标。VOC格式里,bndboxxminyminxmaxymax四个值表示左上角和右下角坐标,单位是像素。

一个object节点大概长这样:

<annotation> <folder>images</folder> <filename>tomato394.png</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>tomato</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>126</xmin> <ymin>98</ymin> <xmax>210</xmax> <ymax>173</ymax> </bndbox> </object> </annotation>

name字段是类别标签,本数据集里就是tomatotruncated为1表示目标被图片边界截断,difficult为1表示目标辨识难度大,这两个字段训练时一般直接忽略。补充一点:小番茄是圆形果实,实际标注中有些框会包住相邻果实的部分区域,两个框之间的IoU可能偏高,用YOLO训练时nms阈值要留出余量。

2.3 批量检查XML标注的合法性

标注文件多了之后,肉眼检查不现实。用脚本批量读XML,重点检查三类问题:一是坐标值是否越界,比如xmax大于图片宽度;二是是否有空标注,一个object都没有的图片在训练中会被当成背景图,数量过多会影响正负样本平衡;三是不同图片间的尺寸是否一致,混用分辨率会导致letterbox变形。

import os import xml.etree.ElementTree as ET from PIL import Image def check_annotation(img_dir, xml_dir): bad_files = [] empty_files = [] for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue xml_path = os.path.join(xml_dir, xml_name) img_name = xml_name.replace('.xml', '.png') img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): bad_files.append(xml_name + ': image missing') continue img_w, img_h = Image.open(img_path).size tree = ET.parse(xml_path) objs = tree.findall('object') if len(objs) == 0: empty_files.append(xml_name) continue for obj in objs: 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) if xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: bad_files.append(xml_name + ': bbox out of range') print('bad files:', bad_files) print('empty annotations:', empty_files) check_annotation('images', 'annotations')

过滤条件里加了图片存在性检查,避免XML与图片缺对。发现坐标越界,处理方式通常是裁剪或者直接删掉问题标注,而不是整个丢弃图片——小番茄果实密集,一张图里往往有七八个有效标注,为了一个坏框丢掉全部标注不划算。

3. 从XML到YOLO训练格式的转换与数据划分

3.1 为什么YOLO不直接读XML

YOLOv5和YOLOv8的训练接口不接受PASCAL VOC的XML格式,它需要的是TXT标注文件,每一行表示一个目标。YOLO的坐标定义和VOC完全不同,它存储的是归一化后的中心点坐标和宽高,取值范围0到1,不依赖图片尺寸。

class_id x_center y_center width height

举个例子,XML里一个框的坐标是(126, 98, 210, 173),图片宽640高480,换算规则是:

x_center = (126 + 210) / 2 / 640 = 0.2625 y_center = (98 + 173) / 2 / 480 = 0.2823 width = (210 - 126) / 640 = 0.1313 height = (173 - 98) / 480 = 0.1563

这么设计的好处是:同一张图无论缩放到什么尺寸,标注坐标都无需改动,省去了逐尺度调整的麻烦。但代价是坐标信息变得不直观,调试时如果发现检测框位置偏了,很难直接从txt里看出问题,通常要写个可视化脚本来核验转换是否正确。

3.2 转换脚本与数据集划分

写转换脚本时,我一般同时完成两件事:XML转TXT,以及按比例切分训练集和验证集。下面是一个完整的转换脚本,直接放在数据集根目录下运行:

import os import random import xml.etree.ElementTree as ET from PIL import Image classes = ['tomato'] def convert_annotation(xml_path, img_path, out_txt_path): img_w, img_h = Image.open(img_path).size tree = ET.parse(xml_path) lines = [] for obj in tree.findall('object'): name = obj.find('name').text if name not in classes: continue class_id = classes.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) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f'{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') with open(out_txt_path, 'w') as f: f.write('\n'.join(lines)) img_dir = 'images' xml_dir = 'annotations' label_dir = 'labels' os.makedirs(label_dir, exist_ok=True) items = [] for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue stem = xml_name.replace('.xml', '') xml_path = os.path.join(xml_dir, xml_name) img_path = os.path.join(img_dir, stem + '.png') if not os.path.exists(img_path): continue out_txt = os.path.join(label_dir, stem + '.txt') convert_annotation(xml_path, img_path, out_txt) items.append(stem) random.seed(42) random.shuffle(items) val_count = max(1, int(len(items) * 0.2)) with open('train.txt', 'w') as f: for stem in items[val_count:]: f.write(stem + '\n') with open('val.txt', 'w') as f: for stem in items[:val_count]: f.write(stem + '\n') print(f'total: {len(items)}, train: {len(items) - val_count}, val: {val_count}')

脚本里random.seed(42)固定了随机种子,保证每次运行划分结果一致,复现实验结果时这点很关键。classes列表顺序决定了类别ID,tomato在列表第一位,所以TXT里所有标注的class_id都是0。YOLO训练时的类别顺序必须和这个列表保持一致,否则会出现类别标签错位,模型能训练但推理结果无法对应到正确的类别名称。

划分比例按19:18:2都行,小番茄这种目标尺寸偏小的数据集,验证集不建议低于10%,否则mAP评估的置信区间会很大,调参时看不出真实效果差异。

3.3 转换后做一次可视化验证

转换完不要急着训练,先抽几张图,把TXT标注重新画回去,对照原图确认坐标没有偏移。这一步能拦住大多数低级错误,比如坐标归一化时除错了边、对象名过滤条件写错导致全图标注丢失。

import cv2 def draw_yolo_boxes(img_path, txt_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f: parts = line.strip().split() class_id, x_center, y_center, bw, bh = parts x_center = float(x_center) * w y_center = float(y_center) * h bw = float(bw) * w bh = float(bh) * h xmin = int(x_center - bw / 2) ymin = int(y_center - bh / 2) xmax = int(x_center + bw / 2) ymax = int(y_center + bh / 2) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) return img img = draw_yolo_boxes('images/tomato394.png', 'labels/tomato394.txt') cv2.imwrite('check_tomato394.png', img)

注意YOLO框角点换算回去时要用x_center - bw/2x_center + bw/2,不是直接加宽高。另外cv2读图默认是BGR通道,直接imwrite输出颜色和原图一致,但如果用matplotlib显示,需要先转换成RGB再imshow

4. 小番茄检测的YOLO训练配置与参数调整

4.1 从零训练还是用预训练权重

小番茄数据集规模不大,从零训练YOLOv8s效果通常不太理想。常见做法是加载COCO预训练权重,把最后一层类别数改成1继续训练。预训练权重已经学会了通用的边缘、纹理、颜色特征,小番茄果实和茎秆的视觉特征相对明显,迁移学习的起点比随机初始化高很多,收敛也快。

以YOLOv8为例,数据配置文件按下面格式写:

path: /path/to/tomato_dataset train: train.txt val: val.txt nc: 1 names: 0: tomato

path是数据集绝对路径,trainval指向我们之前生成的TXT文件列表。YOLOv8读取这个文件后,会自动把列表中的每个stem拼接成图片路径和标签路径。注意train.txt里存的是不带后缀的文件名,YOLOv8的假设是图片在images/目录、标签在labels/目录,文件名前缀一致。首次创建数据集时,在images同级的labels文件夹里新建一个train子目录,把转换好的txt标签按划分放进去,路径层级不能错。

4.2 训练命令与关键超参数

训练命令很直接:

yolo detect train \ data=tomato.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20

训练超参数对结果影响很大,下面逐个解释我在小番茄数据上的设置经验:

参数设置值说明
modelyolov8s.pt用yolov8s的COCO预训练权重作为起点,参数700万级,显存占用低。精度要求高换yolov8m或yolov8l,显存8G以下不建议l
imgsz640图片输入尺寸。如果你的原图分辨率远大于640,可以先缩放。小番茄果实本身很小,强制缩得过小会让果实的像素区域只剩几个像素,检测头无法提取有效特征
batch16受显存限制,设置成显存大小 / (模型参数占用量 * imgsz缩放系数)的粗略估算值。批量太小batch norm统计不稳定,批量太大训练时间翻倍
lr00.01初始学习率。COCO预训练权重已经处于一个较优的局部区域,学习率过大会破坏预训练特征,过小收敛太慢。从0.01开始配合余弦退火
patience20验证集mAP连续20个epoch不提升就提前停止训练。小数据集容易过拟合,这个参数能帮你省下大量时间

这里额外说明一个容易踩的坑:yolov8s.pt会把最后一个检测头去掉,换成随机初始化的新检测头,nc=1。但如果你在同一个数据集上想继续训练,比如先训了100轮再训100轮,不要再用预训练权重,要加载上一次的last.pt,否则检测头被重新初始化,之前的训练完全被浪费。

4.3 小目标改进方向

小番茄果实相对整张图而言通常属于中、小目标,YOLOv8的检测头里有P3特征层专门处理小目标,但训练时是否生效取决于标注框的像素面积分布。先跑一段脚本统计一下:

import os import numpy as np sizes = [] for txt_name in os.listdir('labels'): with open(os.path.join('labels', txt_name)) as f: for line in f: parts = line.strip().split() w = float(parts[3]) * 640 h = float(parts[4]) * 640 sizes.append(w * h) arr = np.array(sizes) print('box pixel area percentiles:') for p in [10, 25, 50, 75, 90]: print(f' {p}%: {np.percentile(arr, p):.1f} px^2')

如果大量标注框的面积低于32x32像素(也就是1024px^2),P3特征层是检测的主力,原来的imgsz=640可以让P3层对应的特征图分辨率达到80x80,基本能满足小番茄的检测需求。但如果你用的是YOLOv5,建议把imgsz调大到960,同时增大P3层anchor的尺度范围。

5. 训练后的效果验证与实用调优技巧

5.1 用val指令评估模型并看诊断图

训练结束后,先跑一次标准的验证流程,看mAP50和mAP50-95这两个指标。mAP50是IoU阈值0.5下的平均精度,mAP50-95是0.5到0.95每间隔0.05取一个阈值算出的平均值。对密集果实场景,mAP50-95比mAP50更能反映定位精度。

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=tomato.yaml

命令输出的results.csv里包含每个类别的Precision、Recall和mAP。对番茄这种成熟度会影响外观的目标,Precision和Recall差距过大说明某个成熟阶段的果实没有被正确检测。这时先看confusion_matrix.png,如果漏检集中在某个颜色区域,建议补充对应光照条件下的图片数据。

5.2 推理脚本里的预热与阈值策略

嵌入式设备或机器人上做实时检测时,推理脚本建议加一次warmup,让CUDA kernel先加载完成再开始计时。检测置信度阈值设0.25通常够用,但在果实密集场景,NMS的IoU阈值建议从默认0.45调低到0.35,减少相邻果实的框被合并的概率。

from ultralytics import YOLO model = YOLO('best.pt') model.predict(source='images/tomato394.png', conf=0.3, iou=0.35, save=True)

iou参数在NMS后处理阶段生效,数值越小,两个重叠框被合并的条件越严苛;过高会保留大量重复框,在密集区域让结果看起来一片红绿。特别提醒:不要在预测阶段把conf设太低,小番茄的误检框往往出现在叶片和背景纹理上,一味降低置信度阈值收获的更多是噪音。

5.3 数据增强参数的适度策略

YOLOv8默认开启了hsv_hhsv_shsv_vtranslatescalefliplr等增强项。小番茄检测中不同成熟度的果实颜色差异很大,适当增强HSV变换可以让模型忽略颜色、关注形状和纹理特征。但注意不要加强mosaic=2.0的强度,mosaic会把四张图片拼在一起,导致小番茄被切碎,反而增加训练难度。建议通过augment=true开启在线增强,并在验证时用augment=false保证推理性能不被打折。

真正到现场部署时,还会遇到相机安装角度和数据集不完全一样的情况。一个务实的做法是:用这个数据集训练出基线模型,从验证集里挑出10张最难检测的图片,人工分析错误类型——是小番茄被叶子遮挡、果实重叠导致目标合并,还是因为背景反光产生了误检。这些错误模式清楚之后,再决定是加数据还是调结构。这样一轮轮下来,模型在特定农场环境里的表现才会逐步逼近实用水平。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 3:32:08

威尔伯福斯摆:耦合振动建模、实时参数辨识与教学可视化系统

简介&#xff1a;本资源为2021年全国大学生物理实验竞赛一等奖获奖项目——威尔伯福斯摆&#xff08;Wilberforce Pendulum&#xff09;的完整开源实现&#xff0c;面向物理类本科生、实验课程教师及对振动与耦合动力学感兴趣的科研初学者。项目聚焦共振耦合现象&#xff0c;系…

作者头像 李华
网站建设 2026/9/14 3:31:53

PHP双端适配软件分发系统:PC+移动端分离架构与会话安全实践

简介&#xff1a;这是一套开箱即用的软件下载系统网站源码&#xff0c;面向Web开发初学者、中小型项目开发者及个人站长&#xff0c;解决软件分发平台快速搭建需求。系统原生支持PC端与移动端双适配&#xff0c;具备分类浏览、关键词搜索、下载管理及基础用户交互能力&#xff…

作者头像 李华
网站建设 2026/9/14 3:30:23

C++常用数据结构与STL函数实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 3:29:18

turbostat详解:用Linux命令行工具监控CPU频率与功耗

简介&#xff1a;面向Linux/Unix系统管理员与开发者的Intel处理器性能监控资源&#xff0c;提供turbostat工具的核心C语言源代码。turbostat是一款命令行实用程序&#xff0c;能够实时显示CPU在Turbo Boost动态加速下的工作频率变化&#xff0c;并统计各C-state&#xff08;C0、…

作者头像 李华
网站建设 2026/9/14 3:28:45

Python步态识别实战:基于GEI与CNN的行人身份识别

简介&#xff1a;这份步态识别行人项目资料包围绕 Python 实现展开&#xff0c;适合希望入门或进阶学习步态识别、传感器行为分析的学习者&#xff0c;也可直接用于毕业设计、课程设计、大作业、工程实训或初期项目立项。包内提供两组实验数据&#xff0c;data 与 data2 各含 1…

作者头像 李华
网站建设 2026/9/14 3:28:28

Python+OpenCV轻量级车道线检测系统开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华