ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

1301张西红柿YOLO+VOC数据集:转换、训练与验证全流程

1301张西红柿YOLO+VOC数据集:转换、训练与验证全流程 简介西红柿目标检测专用数据集面向计算机视觉初学者及算法工程师可满足YOLO系列、Faster R-CNN等主流目标检测框架的训练需求用于果蔬识别、农业自动化等场景。压缩包共2000个文件主要包含jpg原图、VOC格式xml标注以及YOLO格式txt标签三类文件整体包体约64MB目录按照JPEGImages、Annotations、labels三个文件夹分别存放图像、xml和txt结构清晰便于直接划分训练集与验证集。所有标注均为矩形框标签类别统一为tomato共1646个标注框图片清晰度较高且未经过增强处理适合用于模型基线和精度对比实验训练时可直接读取无需额外格式转换。目前已有23人学习下载作为一份格式规范、标注完整的农业视觉数据集可帮助快速上手VOC与YOLO标注互转并完成从数据读取到模型训练的完整流程。1. 西红柿数据集 1301 张 YOLOVOC 压缩包先看懂它再动手做目标检测的人拿到一个 1301 张的西红柿数据集压缩包心里第一反应通常是YOLO 和 VOC 两套标注是不是重复冗余张数这么多够不够训练值不值得为它搭一套流程答案是这类双格式数据集恰恰是最省事的一类资源。1301 张对单类检测来说不算小按 8:2 划分后验证集超过 250 张足以把模型选型、阈值调优和漏检分析跑出统计意义。你不需要自己爬图、打标省下的时间足够你把 YOLO 的路径配置、锚框尺寸和类别权重都过一遍。这份笔记就围绕这个压缩包展开拆结构、双格式互转、跑通训练、排坑最后落到怎么验证模型真的学到了西红柿。2. 拆包看结构YOLO 的 txt 与 VOC 的 xml 到底存了什么2.1 压缩包内的目录组织影像文件、标签文件与划分清单拿到手先别急着解压拖进训练脚本。常见的双格式数据集包内部会按images、labels、Annotations、ImageSets这类目录分开存放。其中images放 JPG 或 PNG 原图labels放 YOLO 格式的 txtAnnotations放 VOC 格式的 xmlImageSets/Main放train.txt、val.txt这些划分清单。有的包会把 YOLO 的 train/val 拆分直接放在labels/train和labels/val子目录里而不是用清单文件统一指定。先unzip -l看一眼清单比直接解压后翻目录更高效unzip -l 西红柿数据集1301张YOLOVOC.zip | head -50这条命令只列压缩包内容不解压。输出第一列是文件大小最后一列是路径。重点看三个方面图片扩展名是否统一xml 和 txt 是否都存在有没有明显缺失的编号段。我习惯顺手统计一下每种目录的文件数unzip -l 西红柿数据集1301张YOLOVOC.zip | awk {print $4} | grep -E \.(jpg|jpeg|png)$ | wc -l unzip -l 西红柿数据集1301张YOLOVOC.zip | awk {print $4} | grep -E \.(xml)$ | wc -l unzip -l 西红柿数据集1301张YOLOVOC.zip | awk {print $4} | grep -E \.(txt)$ | wc -l如果图片数量和 xml 数量一致说明 VOC 侧标全了txt 数量可能多于 xml因为 txt 里还可能包含划分文件或类别名单。如果 txt 数量明显少于图片就要怀疑 YOLO 侧有漏标或者负样本图片故意不带标签。这个信息直接影响后续训练时drop_last和负样本策略的选择。2.2 YOLO 标签格式归一化坐标和类别 ID 的读法YOLO 的 txt 每一行对应一个目标框格式是class_id x_center y_center width height。注意点在于后四个值全部是相对图片尺寸归一化后的比例范围 0 到 1不是像素坐标。比如一行0 0.6822 0.4355 0.2110 0.1880意思是这张图里有一个类别 ID 为 0 的西红柿框的中心在图片横向 68% 处纵向 43.55% 处框宽占图片宽 21.1%高占图片高 18.8%。拿到别人的数据集第一件事不是直接丢进训练而是先写个几十行的脚本把所有 txt 的类别分布、框数量、异常值扫一遍。我一直用下面这段做快速体检import os import glob from collections import Counter label_dirs [labels/train, labels/val] cls_counter Counter() box_counter Counter() bad_lines [] for ld in label_dirs: for txt_path in glob.glob(os.path.join(ld, *.txt)): with open(txt_path) as f: lines [l.strip() for l in f if l.strip()] box_counter[len(lines)] 1 for line in lines: parts line.split() if len(parts) ! 5: bad_lines.append((txt_path, line)) continue cls_id parts[0] vals list(map(float, parts[1:])) if any(v 0 for v in vals) or any(v 1 for v in vals): bad_lines.append((txt_path, line)) cls_counter[cls_id] 1 print(类别分布:, cls_counter) print(每图框数分布:, box_counter.most_common()) print(异常行数:, len(bad_lines))cls_counter告诉你这个数据集是不是只有0一类还是有多个类别 ID。如果只用了0但包内 xml 里的 object name 不止一个说明 YOLO 侧做了合并。box_counter用来判断每张图的平均框数如果大量图片都是 0 框那些图片在训练时会作为背景参与 Loss 计算这会影响召回。异常行数不为 0 就说明坐标有越界或非规范格式这类 txt 一定要处理掉再训练。2.3 VOC 标注格式xml 里的 object、bndbox 与 difficultVOC 格式的 xml 包含两层关键信息图片尺寸和每个目标的类别与坐标。典型的object结构是nametomato/name加bndbox下的xmin、ymin、xmax、ymax这四个是绝对像素坐标左上角原点。还有可选的difficult标记值为 1 表示该目标很难辨认通常是遮挡严重或边界模糊。很多转换脚本默认跳过 difficult1 的框这是个隐患。建议解压后先随机抽三个 xml 看完整结构确认size里的宽高是否和实际 jpg 一致。有些数据集压缩时改过图片尺寸但 xml 没同步更新训练时 YOLO 读图尺寸和 xml 尺寸不一致坐标系就直接错位了。检查代码可以用 Python 的ElementTree快速比对import xml.etree.ElementTree as ET from PIL import Image import glob for xml_path in glob.glob(Annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) img_path images/ root.find(filename).text with Image.open(img_path) as im: real_w, real_h im.size if img_w ! real_w or img_h ! real_h: print(尺寸不一致:, xml_path, (img_w, img_h), 实际, (real_w, real_h))如果大量 xml 尺寸和图片不一致就别在 YOLO 里硬转 txt 了直接重写一个以图片实际尺寸为基准的转换脚本更省事。另一个坑是filename里可能带子目录前缀或文件名后缀不匹配在 batch 训练时表现为图片加载报错。这种数据包常见问题是标注侧的命名规范和图片侧不完全一致批量重命名或统一映射表是唯一解法。3. 把 VOC 转成 YOLO或反向一个可复用的转换脚本与边界坑3.1 转换前的物理检查确认坐标范围、类别列表和文件一一对应做双格式转换前先确认三个事实类别 ID 映射表、坐标范围和文件对应关系。VOC 的类别是字符串YOLO 的类别是整数必须有一个固定的class_names列表否则转了之后类别就错位。一个常见的翻车场景是xml 里用了tomato、Tomato、Tomato_ripe多种写法直接转 YOLO 后出现了三个类别 ID但真实标签只有一类。这时先把所有 object name 收集出来做归一化统一成小写或按数据集说明合并。坐标范围检查指的是确认xmin xmax、ymin ymax并且四个值都在图片尺寸范围内。很多标注工具导出时会把边界点设为 xmin0、xmaxwidth 这种贴边框YOLO 归一化后没问题但反向从 YOLO txt 转 VOC 时要小心宽度用w * img_w算出来带上浮点误差导致 xmax 超过图片宽度 0.5 像素。后续评估时这种超边界框会让 mAP 计算对不齐 GT。建议转换前统一做一次 clamp。文件一一对应更直接转换按 stem 文件名关联也就是00001.xml对应00001.jpg对应00001.txt。如果原包内出现某个图片没有标注转换脚本必须选择跳过还是生成空 txt。训练检测模型时空 txt 是合法的表示该图为背景但如果后续做增强时背景图会被复制多份空 txt 会让增强后的标签目录缺文件这一步要提前想清楚。3.2 全套转换脚本xml 转 txt 与 txt 转 xml两边都写一遍才算完整。xml 转 txt 时我按图片实际尺寸计算归一化坐标而不是信 xml 里的 size 字段因为前面 2.3 已经踩过尺寸不一致的坑。核心函数如下import os import xml.etree.ElementTree as ET from pathlib import Path def voc_xml_to_yolo_txt(xml_path, out_dir, class_names, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(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 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w min(max(w, 0), 1) h min(max(h, 0), 1) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(out_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines) (\n if lines else )) class_names [tomato, ripe_tomato, unripe_tomato] # 按实际类别调整 # 使用时循环所有 xml并传入对应图片的原始宽高参数说明里最重要的其实是img_w和img_h。我用它们替换root.find(size).find(width).text因为实际项目中 xml 的 size 字段被改错过。min(max(...))那三行是 clamp 操作防止贴边框在浮点计算后变成 1.000001这种值一旦和原始标注拼在一起做 mAP 评测会出现无法容忍的微小偏移。类别过滤放在转换前如果某个 object name 不在class_names里直接跳过而不是自动分配新 ID。反向转换我一般只在大模型或检测框架必须消费 VOC 时才做。txt 转 xml 的关键是把归一化坐标还原成绝对值并注意四舍五入到整数def yolo_txt_to_voc_xml(txt_path, img_w, img_h, class_names): root ET.Element(annotation) size ET.SubElement(root, size) ET.SubElement(size, width).text str(img_w) ET.SubElement(size, height).text str(img_h) ET.SubElement(size, depth).text 3 with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, x_c, y_c, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) xmin int((x_c - w / 2) * img_w) ymin int((y_c - h / 2) * img_h) xmax int((x_c w / 2) * img_w) ymax int((y_c h / 2) * img_h) xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(0, min(xmax, img_w - 1)) ymax max(0, min(ymax, img_h - 1)) obj ET.SubElement(root, object) ET.SubElement(obj, name).text class_names[cls_id] bndbox ET.SubElement(obj, bndbox) ET.SubElement(bndbox, xmin).text str(xmin) ET.SubElement(bndbox, ymin).text str(ymin) ET.SubElement(bndbox, xmax).text str(xmax) ET.SubElement(bndbox, ymax).text str(ymax) return ET.tostring(root, encodingunicode, xml_declarationTrue)反向转换里没有写difficult原因是 YOLO txt 没有这个信息补 0 或补 1 都算编造。若下游框架强行要求该字段统一补 0 最稳妥。另外注意 xmin 用(x_c - w/2) * img_w时如果原标注就是由 YOLO 正变换来的这里是可逆的如果原标注来自分辨率不匹配的 xml这一步会把错误放大。3.3 转换后的自检用五点验证法防止标签错位转换完不是直接开训练先用五个指标快速验证。第一类别分布守恒转换前后各类别的目标数完全相等写个 Counter 对比即可。第二坐标范围守恒所有 YOLO txt 的值都在 [0,1] 区间反向转换后所有 xml 的 xmax 不大于图片宽度、ymax 不大于图片高度。第三文件数守恒得到的 txt 数量等于输入 xml 数量不允许有落单。第四随机目检抽 5 张图把转换出的 bbox 画到图上和人眼理解对比。第五尺寸字段一致性随机挑 20 个 xml 重复 2.3 里的尺寸比对。这五点全过再进训练基本能挡住 90% 的脏数据事故。4. 用这份西红柿数据集跑通 YOLO 训练数据集组织、命令与关键参数4.1 按 YOLO 约定组织目录images、labels、train 与 val 的划分无论压缩包内部结构多乱训练前我统一整理成 YOLO 标准布局。目录树长这样datasets/tomato/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamlimages/train放训练图片labels/train放对应同名 txt。注意 YOLO 训练程序不会自动帮你匹配它只读 data.yaml 里的路径然后按images/train下每个图片找labels/train下同名 txt。如果原包的 txt 全部在labels平铺目录而没有 train/val 区分就得先划分。划分时不能简单随机因为同一个场景多角度拍摄的图片通常连号随机划分会让相近图片分到两边验证集指标虚高。我一般先按文件名前缀或拍摄时间聚类再把整个簇划到一边。mkdir -p datasets/tomato/images/train datasets/tomato/images/val mkdir -p datasets/tomato/labels/train datasets/tomato/labels/val # 如果原包里有 train.txt / val.txt 清单则按下述方式移动 while read img_path; do name$(basename $img_path .jpg) cp $img_path datasets/tomato/images/train/ cp labels/${name}.txt datasets/tomato/labels/train/ done train.txt用cp而不是mv保留一份原始数据方便后续对照。如果原包已经有划分好的labels/train和labels/val直接用ln -s软链能省一半磁盘空间但跨平台搬运或 zip 重新压缩时软链会断所以数据包场景下我老老实实用cp。4.2 写一个 data.yaml类别、路径和验证集比例data.yaml 是训练入口的配置文件内容是路径和类别的直接映射。对单类西红柿检测最小配置如下path: /absolute/path/to/datasets/tomato train: images/train val: images/val names: 0: tomatopath字段建议写绝对路径避免训练脚本从不同工作目录启动时报找不到文件。train和val都是相对path的目录名。一个常见问题是有人把train直接写成images/train/*.jpg或给names少一个键YOLO 训练会立刻报类别索引越界。如果原包用多类别表示不同成熟度比如unripe、half-ripe、ripe就按那段列表把 names 扩充成三行。类别顺序一定要和转换脚本里的class_names列表完全一致否则 ID 错位就是集体翻车现场。如果原包只有一个tomato类就不要为了均衡而强行拆分成熟度除非你打算手动给图片重新分类。4.3 训练启动命令与三个关键参数imgsz、batch、epochs以及运行期监控组织好目录、写好 yaml 后用 YOLOv8 风格命令启动训练是当下常见做法。不需要从零初始化权重直接拉预训练权重做迁移学习收敛更快且指标更稳。启动命令yolo detect train \ datadatasets/tomato/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ save_dirruns/tomato_baselineimgsz640是输入分辨率。西红柿目标通常中等大小640 足够如果原图都是 1080p 以上且小目标多改 720 或 832 会提升召回但显存占用和训练时间同步上涨。batch16依据显存调整8GB 卡上跑 YOLOv8n 时 batch 8 更稳24GB 卡可以上 32。epochs150对 1301 张图略偏多我会开着patience30验证集指标连续 30 个 epoch 不提升就自动停节省时间也防止过拟合。训练期间不要只盯 Loss 曲线。YOLO 命令行输出的指标表里box_loss和cls_loss会持续下降但要同时关注mAP50和mAP50-95在验证集上的变化。如果 mAP50 在第 50 个 epoch 后仍然急剧波动说明验证集划分不当或标签噪声大先停下来调数据而不是继续加 epoch。另外可以定期看runs/tomato_baseline下的val_batch0_pred.jpg这张图把模型预测结果叠加在原图上能直观看到漏检和误检长什么样。5. 数据集使用的常见坑与排查解压异常、标签错位、训练崩 BN5.1 解压报错或者损坏zip 伪加密和文件头不对现象解压到一半提示unsupported compression method或需要密码但你确认原始文件不需要加密。原因这是 zip 伪加密的典型表现。ZIP 格式里有个通用位标记第 0 位被置 1 就表示这个条目密码加密。部分打包工具或分享工具只是把这个标记改了文件内容并未真正加密于是解压工具要求密码且不解压。也可能是 zip 文件头被 HTTP 下载过程截断导致 CRC 错误。解决先用zipinfo -v查看条目详情重点看 General purpose bit flag 的 bit 0。如果是伪加密用 7-Zip 打开时选择不输入密码直接提取或者用 Python 的zipfile在读取时手动把flag_bits里的加密位清掉再解压。若是下载截断重新获取原始包不要在损坏包上拿工具强修强修出来的图像可能整张变花。5.2 图片和标签不对应顺序配对了名字没对上现象训练时 Loss 正常下降但目检发现预测框经常出现在错误位置比如把叶子当西红柿或者同一个框在不同 epoch 里忽左忽右。原因转换过程里文件名匹配用错了规则。比如原包图片是0001.jpgxml 里的 filename 写成C:\fakepath\0001.jpg或多了_resized后缀或者你把train.txt清单里的路径和第二列图片名配对时按行号顺序硬拼恰好两侧顺序不一样标签和图片错位装进了同一个文件名下。解决先按文件名 stem 做排序后逐一 diff。写一段脚本比较images/train下每个图的 stem 是否都能在labels/train下找到同名 txt找不到就输出缺失列表。再随机抽 10 张图用训练前的标注可视化脚本把 bbox 画上去人眼确认每个框是否确实压在果实上。这个步骤叫显式校验永远不要跳过。5.3 一个越界的 bbox 把整批 Loss 带崩现象训练前几十个 epoch 正常某次迭代 Loss 突然跳到 20 以上后续再也降不下来或者直接 NaN。原因数据集中混入了某个xmax img_w的框或者 YOLO txt 里出现了负的宽高。归一化坐标用户在 [0,1] 内但训练时的 mosaic 和随机仿射变换会把多个框叠加后再做裁剪越界框在扭曲后可能产生无意义的梯度。更常见的是某个 txt 里 w 或 h 为 0模型对该框的预测后验概率变成 0Loss 对数值罚项直接爆炸。解决训练前用 2.2 的体检脚本把所有w 0或h 0的行剔除并把越界坐标裁剪到 [0,1]。如果是 xml 转 txt 时整数除法导致的精度问题加上float()强制转换并在 clamp 后再四舍五入到 6 位小数。另外可以把所有标注框的宽高比打印成直方图若出现极端细长条宽高比大于 5大概率是标注错误而非真实目标。5.4 训练中 BN 崩溃学习率过大与标签噪声的关系现象训练跑到一半cls_loss突然飙升查 GPU 温度、显存占用都正常但验证集 mAP 直接归零日志里出现nan。原因BatchNorm 统计量在训练中遇到方差爆炸。常见诱因有三个初始学习率太大、batch size 太小导致 BN 统计不稳定、标签里存在大量错误框让小批量梯度方向紊乱。西红柿 Dataset 如果是从网络爬虫聚合而来个别图片可能是背景或半成品YOLO 把该图负样本过少某些 batch 里全是难例BN 的 running_var 会被近似为 0。解决把初始学习率降到默认的一半或四分之一YOLOv8 的lr0默认值约 0.01对 1301 张的规模改 0.005 更稳。batch size 不要小于 8能用 16 就不要用 4。如果怀疑标签噪声加入dropout或对 loss 权重做平滑不现实最直接的方案是人工清洗一批高难样本把模糊、遮挡超过 50% 的框从训练集拿掉。记住 BN 崩溃是结果不是原因清理异常标签才是后悔药。5.5 仅一个类别时的另一个坑混淆矩阵总合不唯一现象跑完训练出混淆矩阵发现每一行的和都不是该类的真实样本数类别只有tomato和背景background但矩阵总数对不上。原因YOLO 的混淆矩阵在目标检测里不是按像素或按实例硬计数的。它统计的是预测框和真实框的匹配结果一个真实框可以被多个预测框命中一个预测框也可以同时匹配多个 GT置信度阈值不同导致矩阵行列总和天然不唯一。这个现象在单类数据集上尤其明显因为背景类占比大很多低置信度预测框被归为背景计数规则跟着阈值走。解决不要用混淆矩阵的总和去做数据回收决策只看两个相对值召回率漏检率和精确率误检率。如果番茄的 FN 偏高说明小目标或遮挡目标没学到优先增加 imgsz 和多尺度训练如果 FP 偏高说明叶子、藤蔓等背景区域被当成了果实优先降低置信度阈值或增加负样本。混淆矩阵本身是诊断工具不是验收指标。6. 进阶用验证集与混淆矩阵确认模型真的学到了西红柿6.1 验证集的划分建议按拍摄场景而不是随机抽样如果压缩包里自带划分清单先看看 train 和 val 的图片文件名是否出现同场景连号。如果是随机切分我会重新划分把同一拍摄批次、同一光照条件下的图片归到同一侧。西红柿表面反光、遮挡和颜色变化受光照影响极大随机划分会让模型在验证集上偷看邻近帧的纹理测试指标虚高。划分时保留至少 200 张独立场景的图片做验证这样 mAP 的置信区间才有参考价值。6.2 混淆矩阵怎么读FN 高说明漏检FP 高说明误检训练完成后用yolo detect val生成混淆矩阵。单类模型主要看tomato行和background行的关系。横轴是真实类别纵轴是预测类别对角线是真阳性。tomato行的非对角线格反映漏检background行里被误判为tomato的格反映误检。如果漏检集中在单个验证图片簇回到 6.1 检查是不是验证集里混入了过多夜间或强逆光图片。如果误检集中在背景格说明模型学到了叶片边缘的圆角特征此时需要额外收集一些纯背景的负样本图片补充到训练集。6.3 一个简单的最终验证把模型结果可视化并输出置信度最后别只看指标把预测结果可视化到图上。用yolo predict对验证集出结果并保存带框图片yolo detect predict \ modelruns/tomato_baseline/weights/best.pt \ sourcedatasets/tomato/images/val \ conf0.25 \ save_txtTrue \ save_confTrue \ projectruns/tomato_visconf0.25是置信度阈值设太低会看到大量假阳性设太高又看不见低置信度真框。我会跑两遍一遍 0.25一遍 0.5对比哪些西红柿在低阈值下能被召回但在高阈值下丢了把那个差集拿出来人工看一遍。如果差集里大多是遮挡 30% 以上的小目标那说明模型边界能力就是你需要的下限如果差集里全是清晰的正常果实那就不是阈值问题是训练集里这类形态的样本太少。这么多年跑数据集我的习惯永远是先怀疑数据再怀疑模型。1301 张听起来足够训练但成像角度、光照变化、果实叠压程度才是决定模型上限的因素。拿到这个压缩包后花一个下午做清洗和划分比盲目堆迭代次数值得多。希望这份流程能帮你在西红柿检测上少走几段弯路。本文还有配套的精品资源点击获取
返回列表