托盘检测数据集VOC转YOLO实战:从格式解析到训练避坑全流程
发布时间:2026/10/1 19:17:40来源:尧图网络
简介仓库内托盘检测数据集采用VOC与YOLO双格式标注面向目标检测研究者和仓库智能化开发者可直接用于训练YOLO等常见检测模型也适用于仓库托盘定位、物流库存管理等场景。压缩包整体约192.54MB共2000个文件主要包含1182个xml标注文件与818个txt标签文件并配有对应jpg图片目录按JPEGImages、Annotations、labels三个文件夹清晰分类图片与标注一一对应便于直接读取和划分训练集。数据集标签仅‘tuopan’一类1182张清晰图片共标注38971个矩形框单图平均约33框高密度标注有助于模型充分学习托盘形态与多视角特征非常适合训练高精度目标检测模型。数据集未做图像增强保留原始特征适合严谨的算法评估。已有130人学习该数据集适合需要高质量单类目标检测训练样本的开发者快速上手。1. 仓库内托盘检测数据集yolovoc格式1182张.zip先弄清楚你拿到的是什么仓储场景里托盘检测是叉车避障、AGV定位、货物入库校验都绕不开的基础能力。标题里的这个压缩包目标只有一个类别——托盘同时提供了VOC和YOLO两套标注格式共1182张图。这意味着你省掉了从零标注和格式转换的最脏阶段解压后可以很快喂给yolov8这类检测框架。但1182张这个量级对托盘检测来说什么时候够用、什么时候远远不够需要先拆清楚。这篇笔记围绕这个数据集格式从目录结构、VOC转YOLO、训练配置到避坑逐一走通适合第一次接仓储视觉项目的人照着复现也适合想评估这批数据值不值得花精力清洗的熟手。2. VOC和YOLO两套标注先看懂数据集再动手2.1 VOC与YOLO格式到底差在哪里很多刚拿到仓库内托盘检测数据集的人第一反应是直接解压丢进训练脚本结果报错或者指标虚高回头才发现两套标注的形式和语义都没对齐。VOC和YOLO虽然描述的是同一个目标框但存储方式完全不同落地时踩的坑也不一样。VOC格式用XML文件描述一张图里所有的目标常见目录结构是把原图放在JPEGImages把XML放在AnnotationsImageSets/Main里放训练验证划分的txt。每个XML里有size节点记录图像宽高object节点里是类名和bndbox的四个角点坐标单位是像素顺序是xmin、ymin、xmax、ymax。对人来说非常直观但训练框架不能直接用要先去解析XML。YOLO格式则是每张图对应一个同名txt文件每行五个数类别id、归一化后的中心点x和y、归一化后的宽w和高h。所有数值除以图像宽高范围在0到1之间。这种归一化的好处是跟图像分辨率解耦训练时不关心图是多少像素只关心相对位置和相对尺度。代价是坐标一旦计算出错比如越界或宽高为负肉眼很难发现训练时模型也会莫名地不收敛。仓库内托盘检测数据集yolovoc格式1182张.zip之所以把两套都提供常见做法是方便不同框架直接消费——用YOLO系框架直接读txt用mmdetection或老版Faster R-CNN读VOC的XML。但拿到手的第一步千万别默认两套标注完全一致。实际打包时VOC往往是原始标注YOLO是后转出来的如果转换脚本有Bug两套数据就存在偏差。所以开工前要做一次校验而不是跳过。校验的思路是对同一张图把VOC的bndbox和YOLO的txt分别画到图上视觉对比有没有位置偏差再按公式反推验证YOLO的坐标系是否和VOC吻合。这个校验必须在训练前做真等模型训完再发现数据错了后悔药是没有的。2.2 解压后的目录检查和标注可视化先解压看清楚压缩包内部的组织方式。很多数据集不是标准的VOC三层结构而是把图片和标注混在一堆子文件夹里甚至嵌套了两层zip。我习惯解压后先跑一段检查脚本把目录结构打出来同时确认文件数量对得上。unzip 仓库内托盘检测数据集yolovoc格式1182张.zip -d pallet_dataset cd pallet_dataset # 统计图片和XML数量是否一致 find . -name *.jpg | wc -l find . -name *.xml | wc -l # 看看有没有独立存放的YOLO版标注 find . -name *.txt | wc -l逻辑说明第一条命令解压到pallet_dataset目录加-d参数可以避免直接在当前目录散落一堆文件算是解压zip的常规保底操作。之后统计三种文件数量目的是先确认是不是三套完整数据——图片、VOC的XML、YOLO的txt各1182份才算齐全。如果jpg和xml数量不一致说明有图没标或者有标没图如果txt文件数量是零说明YOLO标注可能放在压缩包内层的另一个目录里需要再找一层。参数说明unzip的-d指定输出目录通配符*.jpg只匹配当前目录如果你的数据集是JPG大写后缀或png格式记得改成对应后缀。更保险的办法是把find的路径写到子目录名上例如find JPEGImages -name *.jpg。确认数量无误后进入可视化环节。工具方面目标检测常用标注工具如labelImg、labelme都能直接打开VOC格式查看但逐张点开1182张不现实。一般做法是写一个脚本随机抽二十张把标注框叠加在原图上输出成新图快速确认类别标签和框位置是否合理。import cv2 import random import glob import xml.etree.ElementTree as ET xml_list glob.glob(pallet_dataset/Annotations/*.xml) random.shuffle(xml_list) for xml_path in xml_list[:20]: tree ET.parse(xml_path) root tree.getroot() img_path xml_path.replace(Annotations, JPEGImages).replace(.xml, .jpg) img cv2.imread(img_path) for obj in root.iter(object): name obj.find(name).text box obj.find(bndbox) x1 int(float(box.find(xmin).text)) y1 int(float(box.find(ymin).text)) x2 int(float(box.find(xmax).text)) y2 int(float(box.find(ymax).text)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, name, (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 2) out check/ xml_path.split(/)[-1].replace(.xml, .jpg) cv2.imwrite(out, img)逻辑说明脚本做的事很简单——随机抽xml解析出每个目标的类名和坐标用OpenCV画框和标签另存到check目录。关键不是画图而是让你在一个小时内把所有类名的写法、框的松紧度、是否存在把货物或叉车叉臂也算进去的脏标注全部过一遍。参数说明img_path的replace依赖目录命名为Annotations和JPEGImages如果你的数据集目录名不是这两个直接手动拼接路径。cv2.rectangle的线宽2在1920分辨率图上看还可以如果是小图建议改成3。putText字号1.2也看图片尺寸决定太小看不清类名。这一步最有价值的产出是确认一件事xml里的类名到底统一不统一。有的数据集里一部分标成pallet一部分标成托盘甚至混着tray、wooden_pallet。这些不一致如果不在训练前解决后面class mapping一定出问题。统计所有xml的唯一类名比人眼抽查更稳import glob import xml.etree.ElementTree as ET names set() for xml_path in glob.glob(pallet_dataset/Annotations/*.xml): tree ET.parse(xml_path) for obj in tree.getroot().iter(object): names.add(obj.find(name).text) print(names)逻辑说明用set收集所有object的name文本最后打印出这个数据集里到底出现了多少种类名写法。这一步必须和上面画框可视化一起做只统计不画图发现不了坐标本身的问题。参数说明如果你的xml里标签不是object而是其他节点名需要先用文本编辑器打开一个xml看结构再改不要照抄。这一步算是用最小代价给整个数据集做体检比直接丢进训练省几十小时。3. 把VOC转成YOLO转换脚本与四个边界坑3.1 VOC转YOLO的转换脚本仓库内托盘检测数据集yolovoc格式1182张.zip里如果VOC和YOLO都已经齐全这一步可以跳过。但在实际项目中这个压缩包大概率只给你VOC的XMLYOLO的txt需要自己从XML转出来或者你打算换一个类别顺序、过滤某些脏标注都必须重新转一遍。我一般会用一个脚本完成转换同时把输出目录也一并创建好。import os import glob import xml.etree.ElementTree as ET from pathlib import Path # 类名顺序顺序一旦确定不要改 class_names [pallet] # 按数据集的实际情况调整 xml_dir pallet_dataset/Annotations out_dir pallet_dataset/labels os.makedirs(out_dir, exist_okTrue) def voc2yolo(xml_path, out_txt): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) # 修正坐标反了或越界的情况 x1, x2 min(x1, x2), max(x1, x2) y1, y2 min(y1, y2), max(y1, y2) x1 max(0, min(x1, img_w - 1)) x2 max(0, min(x2, img_w - 1)) y1 max(0, min(y1, img_h - 1)) y2 max(0, min(y2, img_h - 1)) if x2 - x1 2 or y2 - y1 2: continue # 过滤掉宽或高小于2像素的无效框 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines) if lines else ) for xml_path in glob.glob(os.path.join(xml_dir, *.xml)): stem Path(xml_path).stem voc2yolo(xml_path, os.path.join(out_dir, stem .txt))逻辑说明脚本先从xml的size节点读出图像宽高用于归一化。然后遍历object把bndbox的角点坐标转成中心点和宽高除以图像宽高写成YOLO格式的txt。脚本里做了两个防护坐标反了会先swap回来越界的坐标会clip到图像边界内。这个处理不是多此一举仓库场景的托盘往往紧贴图像边缘标注时手滑把xmax标到图外很常见不clip会让归一化坐标大于1训练脚本要么报错要么默默丢框。参数说明class_names里的顺序很重要因为txt里写的是类别的index不是名字。很多转换脚本用集合或者dict去存类名虽然能跑但顺序是乱序换台机器结果就不一样。所以这里写死成一个list既稳定又直观。宽或高小于2像素的框直接跳过这种框在缩放到640分辨率时几乎只剩一个点留着只会给损失函数送噪声。输出的txt和原xml同名放在labels目录正好对应YOLO系列框架的目录约定。3.2 类别映射和训练验证集划分转换之后还有一个容易被忽略的步骤确认整个数据集的类别分布以及训练验证划分是否合理。两个问题关联在一起——如果验证集里恰好没有托盘贴边的难例指标会虚高如果同场景的连拍图被随机切到训练和验证两边模型相当于开卷考试。常见做法是先把所有图按场景分组再按组划分不按单张随机切。仓库内托盘的图片通常是一段视频抽帧得到的同一个托盘位置会有连续好几帧随机切分会让训练集和验证集里出现几乎相同的画面验证时mAP50虚高到0.99换个场景直接崩。下面这个脚本按文件名前缀把数据划开后缀相同前缀视为同一场景组。import os import random from collections import defaultdict image_dir pallet_dataset/JPEGImages train_ratio 0.85 groups defaultdict(list) for img_name in os.listdir(image_dir): if not img_name.lower().endswith((.jpg, .jpeg, .png)): continue prefix img_name.split(_frame)[0] # 比如20230712_1501 groups[prefix].append(img_name) group_names list(groups.keys()) random.shuffle(group_names) split_idx int(len(group_names) * train_ratio) train_groups set(group_names[:split_idx]) val_groups set(group_names[split_idx:]) train_files, val_files [], [] for g in groups: for img in groups[g]: if g in train_groups: train_files.append(img) else: val_files.append(img) print(f训练组数量 {len(train_groups)}验证组数量 {len(val_groups)}) print(f训练图数量 {len(train_files)}验证图数量 {len(val_files)})逻辑说明img_name.split(_frame)[0]是按文件名前缀取场景ID。如果你的压缩包里文件名本身就是编号比如000001.jpg到001182.jpg那就不能按前缀分组得看是不是按采集批次命名。这一步要人工确认命名规则不要盲目抽前缀。参数说明train_ratio设成0.85对1182张来说验证集有170多张足够算mAP。如果样本总框数太少留0.8也行但不要低于0.75否则验证集方差太大。划分结果建议写成两个txt文件train.txt和val.txt内容是图片的绝对路径供训练脚本直接读取。YOLO系列框架一般也支持txt方式指定数据集比目录扫描更可控。这个分组划分逻辑同样适用于mmdetection和Detectron2那两套框架读VOC或COCO格式时也是先做数据集划分再转格式分组粒度保持一致。常见的错误做法是先转COCO再切分切分时用图像ID随机数同一个场景的图又被拆到两边数据泄漏又回来了。4. 用YOLO训练托盘检测最小配置与参数选型4.1 数据集配置文件和训练命令格式和划分都准备好后就能进入训练阶段。目标是跑出一个可用于叉车视觉检测的托盘检测模型而不是在学术指标上刷分。所以yolov8目标检测数据集处理这步要做得干净配置文件写清楚训练命令别堆一堆看着炫但不知道在干什么的参数。我一般会按YOLO系列框架约定的目录结构把图片和标签分别放好pallet_dataset/ images/ train/ val/ labels/ train/ val/然后用一个data.yaml指向这个目录。这里注意labels目录名是框架约定不要自己想当然改成txt或者annotations。path: D:/pallet_dataset train: images/train val: images/val names: 0: pallet逻辑说明path写的是数据集根目录的绝对路径train和val相对路径拼在path后面。names的0对应上面转换脚本里class_names的顺序如果顺序变了这里要跟着改。这一步看似简单却是大多数人翻车的地方——names顺序和txt里的index对不上训练时loss正常下降但预测出的类别全错。数据集配置好之后用下面的命令启动训练yolo detect train \ datapallet.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ device0逻辑说明model用yolov8n.pt而不是从零初始化是在COCO预训练权重基础上继续训练收敛速度明显更快。虽然COCO里没有托盘这个类但底层的边缘、纹理、形状特征仍然有用。epochs设120对1182张单类数据来说足够一般到第50个epoch损失就平了。参数说明imgsz640是检测速度和精度的中间值。仓库托盘目标多处于中近景640足够如果画面里存在大量小托盘可以尝试896显存不够就把batch降到8。device0指定第一块GPU没有GPU的机器换成cpu但训练时间会非常长1182张虽然不大纯CPU跑yolov8m也得按天算。如果中途发现类别不均衡最简单的办法不是调损失而是先把数据配平。托盘检测的本质是单类检测正负样本比由锚框匹配决定图片层面的不平衡影响不大。真正要注意的是验证集里别出现某一类难例全部集中在验证集的情况那会导致单个epoch的mAP波动剧烈。4.2 yolo损失函数在托盘任务上的取舍yolo损失函数这部分很多教程讲得玄乎落地时其实只需要理解三个组件分类损失、回归损失、DFL分布损失。yolov8默认用的是BCE分类损失加CIoU回归损失加DFL。托盘检测里分类损失压力极小因为只有一个类且托盘和背景差异明显回归损失才是决定框准不准的关键。单类检测有个容易被忽略的复杂度类内差异全靠形状和长宽比来扛。标准托盘是1.2米乘1米的长方形但相机视角从叉车上往下俯拍时同一块托盘在画面里可能是正方形、窄条形、甚至被货架挡成L形。CIoU对长宽比的敏感性在俯拍场景里会放大如果验证集里全是正俯视角mAP50会很高一旦换成侧俯视角框就开始飘。实操上我建议先把epochs从100提到150看看回归损失是否还在下降。如果损失曲线在80个epoch附近已经走平说明模型没吃透难例这时候加epoch没有意义需要提高输入分辨率或者调整数据增强。不要一上来就去动三个损失的权重系数调了反而让训练波动。另一个常见甜点是数据增强参数。yolov8默认的mosaic和fliplr在托盘数据上基本可用但要注意两点一是flipud在叉车俯视画面里会改变托盘的上下语义吗不会托盘是中性物体翻转不影响类别开着问题不大二是mosaic在托盘这种大目标场景下如果把四张图拼在一起原始托盘会被缩得很小学习到的特征偏小目标验证时反而掉点。我一般在托盘数据集上把mosaic的开启概率从默认1.0降到0.5代价是训练速度变慢但框的稳定性好很多。训练结束后看两样东西weights/best.pt和weights/last.pt。best.pt是验证集mAP最高的权重last.pt是最后一个epoch的权重。工程上用best.pt没错但如果best.pt出现在全场最靠前的epoch比如第30轮那大概率是验证集划分出了问题样本泄漏的典型信号。遇到这种情况不要拿best.pt直接部署回去检查场景分组。5. 托盘数据集避坑解压、标注、划分和训练中的常见问题5.1 解压后目录不完整缺少划分文件现象按标题解压仓库内托盘检测数据集yolovoc格式1182张.zip后发现只有images和annotations没有train.txt、val.txt也没有按框架要求的目录结构。原因压缩包在打包时可能只保留了原始VOC格式的图片和xmlyolo版的txt和划分文件被遗漏或者放在了另一个嵌套目录里。越是网上流传的数据集越容易出现这种打包不完整的情况。解决先用2.2里的脚本统计jpg、xml、txt三类数量明确缺哪块。缺xml就完全不能用VOC转YOLO的路线只能先把jpg拷出来找人补标缺txt只是缺转换产物重新走一遍第3章的转换脚本即可。划分文件没有就自己生成不要硬等数据集作者补。zip解压时还有一个隐藏坑——某些在线解压工具会把文件名里的中文目录转成乱码导致框架找不到路径建议本地用7-Zip或Python内置的zipfile解压比网页工具可控得多。5.2 类名不一致训练时类别数量对不上现象运行训练命令时报错或者在画框可视化时看到同一张图里有的框叫pallet有的叫托盘还有的xml里根本没有name节点。原因数据集标注不是一个人完成的标注工具导出的类名命名不规范或者中途改过类名历史数据没同步更新。这类问题在北京上海那边的标注团队里尤其常见全流程质量管理再好也挡不住换人重标。解决写脚本统计所有xml里的唯一类名把同一种物体但不同名字的类全部映射到同一个标准名。不要试图在class_names里写两个名字当两个类那样模型会把同一个托盘学成两个互相竞争的输出损失降不下去框也乱飘。命名规约用英文小写加下划线避免中文类名在部分框架编码转换时报错。5.3 归一化坐标越界训练时报错或框被过滤掉现象转换生成的txt里出现大于1的数字比如0.023 1.024 0.81 0.45训练时yolo框架提示坐标不在合法范围或者训练能跑但召回率奇低。原因VOC的bndbox是原始像素坐标标注平台允许框超出图片边界转YOLO时直接除以图像宽高就产生了越界值。尤其在托盘贴近画面边缘、标注时框到图外的情况下这个现象很频繁。解决转换脚本里先对角点坐标做一次max和min的clip再算归一化。另外在转换后加上一个检查步骤把txt里所有超过0和1范围的坐标打印出来。磅出来的文件通常就是贴边的极难样本人工抽查一遍确认框是不是真的在图内如果是真目标就手工修一下xml再重新转换。5.4 训练集和验证集数据泄漏验证指标虚高现象训练后验证集mAP50高达0.98导出模型部署到实际的仓库视频里漏检一串串出现与验证时的精度完全对不上。原因图片来自同一段视频的连续帧托盘在相邻帧里位置几乎不变。随机划分时这些几乎相同的画面被同时分到训练集和验证集框架等于考了它背过的答案。解决按场景分组划分。对视频抽帧数据优先按文件名里体现的时间戳或镜头编号分组而不是使用train_test_split的纯随机模式。如果文件名是纯序号没有时间维度的信息就看连续帧的前后差异把位移很小的帧归为同组。这个坑在托盘数据集里格外致命因为堆垛场景下相机静止几十帧画面几乎一模一样随机划分出的泄漏比例可能超过三分之一。5.5 标注框含货物模型学到的是货物而不是托盘现象模型的框收敛得很大经常把托盘上面堆的纸箱、塑料筐一起框进去或者在托盘被压住只剩一个边时完全检测不到。原因标注人员把托盘连同上面堆的货物整体框成一个矩形。VOC标注里拖出来的矩形如果上下边界压到货物上训练出来的框高度就会偏大因为回归目标本来就是错误的框。解决翻看可视化输出的二十张图重点看框的上边界是不是贴合托盘面板而不是贴合货物顶部。如果脏样本比例高把这类xml找出来重新调整bndbox的y2坐标只卡到托盘本体。被迫在没有干净标注的情况下直接训练也不是不行但损失函数里回归这部分的梯度会被错误目标带偏框的高宽比会学成一个混合平均值这会在俯拍视频里表现得特别明显。6. 让1182张发挥出更大价值增广策略与工程化验证如果只靠原始1182张图去覆盖各种仓库现场大概率是不够的。托盘这个目标看起来简单实际因为光照、地面反光、叉车遮挡、不同颜色托盘并存模型的域偏移非常明显。我通常会在训练阶段做两件额外的事一是手工控制数据增强的强度和类型二是把验证从mAP数字拉回到工程指标上。数据增强方面mosaic概率从默认降到0.5左右保留fliplr增加HSV扰动到0.02左右的色相偏移和0.5的饱和度偏移。托盘的木色在黄色和棕色之间波动轻微的色相扰动可以让模型对木质的颜色不再那么敏感反而更依赖纹理和边角结构。高斯噪声和运动模糊不建议加到训练图里这两个增强会让边界变得模糊拖累回归损失收敛。真正值得试的是albumentations里的RandomBrightnessContrast仓库头顶灯光闪烁和白天黑夜切换都体现在亮度变化上。验证方面不要只盯val集上的mAP50输出。我会把从现场录的十分钟视频抽成200帧不参与训练单独跑一次模型推理统计两个指标漏检率和误检率。漏检率定义为托盘实际可见但没有框的帧数除以有托盘出现的帧数误检率定义为框落在非托盘区域的比例。这两个数字比mAP更能反映能不能上线。托盘检测在工程上宁可漏检也不宜误检误检会导致叉车误判障碍物急停漏检还可以靠多帧检测补偿。开放词汇目标检测是另一个值得一提的方向它能把托盘这类物体从固定类别抽象成语义描述遇到没见过的蓝色塑料托盘、黑色橡胶托盘时泛化性更强但推理开销和工程复杂度比专用检测模型高一个量级。对仓储项目来说先用固定类别把业务跑通再用开放词汇做补充验证是更稳妥的路径。我自己的习惯是每次训练完都把best.pt保存好同时把data.yaml和训练命令写进一个README文件放回数据集目录。因为三个月后再回来调模型时你大概率不记得当时的类别顺序和划分逻辑。有一次我拿着别人给的数据集没做类名统计直接训练训到第三天才发现names列表和txt里的index错位所有框都是空的。从那以后我养成了解压后先跑统计脚本再动手的习惯。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网