仓库托盘检测数据集全解析:YOLO/VOC标注校验到训练避坑

发布时间:2026/10/1 10:34:34
仓库托盘检测数据集全解析:YOLO/VOC标注校验到训练避坑 简介一套面向仓库内托盘检测任务的目标检测数据集专为需要训练YOLO、Faster R-CNN等算法的研究者与开发者准备。压缩包共2000个文件包含1182个XML标注文件和818个TXT标签文件整体约192.54MB。数据采用VOC与YOLO双格式存储标注信息完整标签仅“tuopan”一类共38971个矩形框框体覆盖托盘在不同角度与光照下的形态图片清晰度较高且未做增强可直接用于模型训练、验证与精度评估。资源内按图片、XML、TXT分文件夹整理目录结构清晰便于数据加载与二次开发适用于仓库管理、物流跟踪、库存盘点等场景也可作为目标检测算法的对比测试数据。已有130人学习下载能有效节省自行采集与标注的工作量帮助开发者在实际项目中快速起步。1. 仓库托盘检测的工程起点1182张图、38971个框能做什么在自动化仓库场景里做移动小目标检测托盘是被低估最多的目标。托盘外形看着规整但仓库里一出现堆叠、遮挡和光照变化常规检测模型的漏检率就会明显上升。这次拆解的仓库内托盘检测数据集以yolovoc双格式交付1182张实拍图对应的38971个矩形标注框平均每张图约33个框正好是密集场景下目标检测模型最需要的训练形态。它的实用性集中在三处VOC与YOLO两套标注同时给全标签tuopan单类别专一图片未增强保留了实拍特征。适合仓库监控、AGV视觉定位和装卸口计数这类落地项目也适合用来成体系地检验密集小目标场景的召回率优化。2. 数据内部结构拆解三个文件夹如何构成一条完整标注链路拆开压缩包之后你会看到一套在公开数据集中非常常见的目录约定JPEGImages存放图片原图Annotations存放对应的xml标注labels存放YOLO训练直接读取的txt标注。三个目录里文件数量一致都用同名主干关联。这种结构的好处是对齐简单换工具链时迁移成本低也方便脚本做批量校验。这套结构不是什么新发明但恰恰因为它太通用反而容易被忽略。很多人拿到数据第一件事就是解压开训练等到训练报错才回头查文件对应关系。所以先把目录逻辑理清楚后面所有脚本都建立在“同名即同一张图”这个前提上。2.1 JPEGImages、Annotations、labels三个目录如何对齐同一条规则贯穿整个数据集一张jpg图片在三个目录下必须有完全同名的文件与之对应。这是VOC时代传下来的惯例YOLO系工具也沿用。标注流程中图片是唯一主键xml和txt都从属于它所以对齐方向始终是“由图片出发找标注”。import os jpg_dir JPEGImages xml_dir Annotations txt_dir labels def get_stem_set(folder, ext): return {os.path.splitext(f)[0] for f in os.listdir(folder) if f.endswith(ext)} jpg_names get_stem_set(jpg_dir, .jpg) xml_names get_stem_set(xml_dir, .xml) txt_names get_stem_set(txt_dir, .txt) print(图片数量:, len(jpg_names)) print(xml数量:, len(xml_names)) print(txt数量:, len(txt_names)) only_jpg jpg_names - xml_names - txt_names only_xml xml_names - jpg_names - txt_names only_txt txt_names - jpg_names - xml_names print(只在jpg侧存在的文件:, only_jpg) print(只在xml侧存在的文件:, only_xml) print(只在txt侧存在的文件:, only_txt)逻辑说明先把三个目录各自读一遍生成不含扩展名的文件主干的集合然后对比差异。这一步解决的是“数量对不对得上”的问题。只有三个数量都是1182且两侧的“独有文件”集合都为空才说明压缩包内部没有缺文件或多余文件。参数说明os.path.splitext负责把文件名和扩展名拆开注意索引是[splitext(f)[0]]取主干。判断文件后缀时用endswith过滤避免把隐藏文件和临时文件读进来。比如macOS解压后常出现的.DS_StoreWindows下解压产生的__MACOSX目录残留都可能混入集合导致统计失真。这类检查跑一次只要几秒钟但能挡掉后面训练中一大半的“label数量不匹配”报错。2.2 同一张图的VOC与YOLO标注坐标怎么换算VOC格式用xml记录信息每一张图对应一个annotation根节点里面包含图片尺寸、文件名和多个object子节点每个object描述一个目标框。本数据集里object的name字段只有一种取值tuopan。这是Pascal VOC最标准的写法方便人工阅读也方便在labelImg这类工具里二次修改。annotation folderJPEGImages/folder filename000042.jpg/filename size width1920/width height1080/height depth3/depth /size object nametuopan/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin302/xmin ymin451/ymin xmax688/xmax ymax742/ymax /bndbox /object /annotation对应的YOLO格式txt文件则是一行一个框每行五个数类别id、归一化中心x、归一化中心y、归一化宽度w、归一化高度h。这个数据集只有tuopan一个类所以类别id固定为0。实际内容类似0 0.257813 0.552314 0.201042 0.269574 0 0.781510 0.208102 0.154688 0.145370参数说明归一化的含义是相对图片宽高的比例所以每个分量都应该落在0到1之间。VOC到YOLO的换算公式是x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height。反过来从txt还原像素坐标时要用cx - w/2得到左上角x1。维度VOC(xml)YOLO(txt)坐标单位绝对像素归一化相对值坐标顺序xmin, ymin, xmax, ymaxx_center, y_center, width, height每张图对应文件1个xml1个txt适合场景人工复核、迁移到MMDetection/Detectron2YOLO系训练、自动增强管线VOC坐标全是整数看框的位置直观YOLO坐标是浮点便于模型直接回归。两套格式表达的是同一批ground truth所以它们之间必须严格一致。这也是后面校验脚本的核心依据如果两侧统计出的总框数不一致说明这套数据的生成链路存在问题训练前必须排查。3. 标注质量校验脚本把38971个框从文本里揪出来我拿到新数据集的习惯是固定的先算数量再查坐标范围然后抽样目检最后才敢把数据送进训练脚本。原因很简单模型训练报错大多能看懂但标注文件里藏着的脏数据不会直接报错只会让loss曲线图变得玄学mAP忽高忽低却说不清原因。这个数据集的摘要里明确写了两项指标标签种类数是1总框数是38971。我做的第一步就是写脚本让机器把这38971个框从txt和xml两个文件系统里分别数出来结果对不上就停下来检查对上了再继续。3.1 遍历labels目录统计每个txt的框数并筛查格式错误YOLO的txt结构简单但正因为简单出问题时反而隐蔽。常见的情况是某一行多了一个空格或者坐标写成了整数或者某张图片的标注文件为空。用脚本批量扫描一遍这些问题都能在几分钟内全部暴露。import os txt_dir labels total_boxes 0 empty_files 0 out_of_range 0 bad_lines 0 boxes_per_file {} for fname in os.listdir(txt_dir): if not fname.endswith(.txt): continue path os.path.join(txt_dir, fname) with open(path, r, encodingutf-8) as f: lines [line.strip() for line in f.readlines() if line.strip()] n len(lines) total_boxes n boxes_per_file[fname] n if n 0: empty_files 1 continue for line in lines: parts line.split() if len(parts) ! 5: bad_lines 1 print(f[字段数错误] {fname}: {line}) continue cls, cx, cy, w, h parts try: vals [float(cx), float(cy), float(w), float(h)] except ValueError: bad_lines 1 print(f[数值异常] {fname}: {line}) continue if any(v 0.0 or v 1.0 for v in vals): out_of_range 1 print(f[越界] {fname}: {line}) print(总框数:, total_boxes) print(空标注文件数:, empty_files) print(越界记录数:, out_of_range) print(异常行数:, bad_lines) print(单图最多框数:, max(boxes_per_file.values()))逻辑说明这里做了四层检查。第一层数行数累加得到总框数理想值就是38971第二层检查是否有空文件空txt意味着某张图完全没有标注这在目标检测里不算致命但需要单独记录第三层检查每行拆出来必须是5个字段多了少了都说明文件在转换或复制时被污染第四层检查坐标分量是否都在0到1之间越界坐标会让模型训练时产生NaN或极端loss。参数说明我习惯把empty_files和out_of_range、bad_lines三个变量的目标值对齐为0。如果其中任何一个非零先定位到具体文件再处理不要直接进训练。out_of_range的常见原因是格式转换脚本把整数除法结果截断成了整数再乘回图片宽高就出现了边界溢出。3.2 用VOC的xml再数一遍双端对齐验证上下限光检查txt还不够本数据集同时提供了VOC格式那么两个格式的统计结果必须一致。用xml解析虽然慢一点但能验证两套标注是不是真来自同一份ground truth。这个验证对后面尤其重要因为很多人会把txt送去YOLO训练又把xml送去MMDetection如果两套标注有偏差换框架训练时的基准效果就对不上。import os import xml.etree.ElementTree as ET xml_dir Annotations total_obj 0 fail_files [] for fname in sorted(os.listdir(xml_dir)): if not fname.endswith(.xml): continue path os.path.join(xml_dir, fname) try: tree ET.parse(path) root tree.getroot() total_obj len(root.findall(object)) except Exception as e: fail_files.append((fname, str(e))) print(xml中object总数:, total_obj) print(解析失败文件:, fail_files)逻辑说明用Python自带的xml.etree.ElementTree遍历每个xml取根节点下所有object子节点的数量累加后与txt统计总数对比。两边的预期值都是38971。如果xml侧多出几个object很可能是有人手工标注后没同步到txt如果xml侧少了则是txt生成时漏了目标这两种情况都需要回到原图确认。参数说明Exception处理要保留原始错误信息fail_files为空才是正常状态。解析失败多数发生在xml文件被某个编辑器保存成非UTF-8编码或者标签未闭合这时候用文本编辑器打开检查一下就好不用重新标注。3.3 把边界框画回原图给标注做一次最直观的目检统计数字只能说明“数量对”不能说明“位置准”。YOLO坐标是归一化的浮点光看数字很难判断框在不在目标上。我的做法是随机抽取几张图片把txt里的框还原成像素坐标画上去保存成可视化图片再用看图软件快速扫一遍。import cv2 img_path JPEGImages/000042.jpg txt_path labels/000042.txt save_path check_000042.jpg img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r, encodingutf-8) as f: for line in f: parts line.split() if len(parts) ! 5: continue _, cx, cy, bw, bh parts cx, cy, bw, bh float(cx), float(cy), float(bw), float(bh) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(save_path, img)逻辑说明在读取txt后用float()转换每个数值再通过换算公式得到像素坐标框。这里最容易出错的就是中心坐标还原成左上角坐标时忘记减掉一半的宽高结果所有框都会偏移到右下方看起来完全不贴合目标。参数说明cv2.rectangle的thickness设为2是为了在1920x1080这类大分辨率的图片上还能看清轮廓。如果单张图框数很多我建议勾选框后立即缩放到50%再检查。这个数据集平均每张33个框从1182张图里挑二十张做这样的目检时间成本在十分钟以内性价比很高。4. 训练集和验证集的拆分目录对齐和随机种子同样不能省训练前最后一步是数据拆分。很多人直接用train_test_split处理图片文件列表再同步复制标注文件时忘了把xml和txt一起带上结果训练集有图没标注验证集有标注没图。这类问题在YOLO系框架里报错还比较明显在自定义训练脚本里就变成了莫名其妙的索引越界。我用的拆分方案不算复杂但每一步都能相互验证以图片文件名为唯一索引随机打乱后按比例切分然后在分割结果内同步复制三套后缀文件。4.1 先按图片名建索引再同步给三个目录做切分拆分时我坚持以“图片名为核心”的set逻辑不用文件名后缀作为唯一键。这样拆分的结果就是一组集合后续复制任何文件都从这个集合里查询从根本上杜绝图片和标注被拆散。import os import random random.seed(42) jpg_dir JPEGImages ratio 0.85 jpg_names [os.path.splitext(f)[0] for f in os.listdir(jpg_dir) if f.endswith(.jpg)] random.shuffle(jpg_names) split_idx int(len(jpg_names) * ratio) train_names set(jpg_names[:split_idx]) val_names set(jpg_names[split_idx:]) print(f训练集图片数: {len(train_names)}) print(f验证集图片数: {len(val_names)})逻辑说明先读取JPEGImages下所有jpg文件名去掉扩展名得到图片名列表用random.shuffle打乱顺序后按比例切分。得到的train_names和val_names都是set后续把所有操作都建立在这两个集合之上。图片名本身是唯一关联键这样无论怎么移动文件同一张图的jpg、xml、txt都保持同步。参数说明random.seed(42)固定随机种子这个习惯能保证实验可复现。如果你中途调整过数据集换一个seed重新拆分所有历史实验指标就失去可比性了。ratio取0.85是经验值1182张图不算多85%训练15%验证大约得到177张验证图对这个单类别且框密度高的场景足够评估。如果你后续要专门测试夜间或者逆光场景可以再把对应图片手动划分进验证集。4.2 生成YOLO训练需要的目录结构并保留xml备查YOLO官方仓库常见的训练目录约定是datasets/project/train/images和datasets/project/train/labels同级验证集也是同样的两层结构。为了保留原始标注这套脚本里我额外保留了annotations目录单独存放xml方便之后人工查阅和错误分析。import os import shutil split_data {train: train_names, val: val_names} src_dirs { jpg: JPEGImages, xml: Annotations, txt: labels } for split_name, names in split_data.items(): for sub in [images, labels, annotations]: os.makedirs(fdatasets/tuopan/{split_name}/{sub}, exist_okTrue) for n in names: shutil.copy( os.path.join(src_dirs[jpg], n .jpg), fdatasets/tuopan/{split_name}/images/{n}.jpg ) shutil.copy( os.path.join(src_dirs[txt], n .txt), fdatasets/tuopan/{split_name}/labels/{n}.txt ) shutil.copy( os.path.join(src_dirs[xml], n .xml), fdatasets/tuopan/{split_name}/annotations/{n}.xml )逻辑说明脚本遍历train_names和val_names两个集合使用shutil.copy复制而不是move这样原始三条目录保持完整万一拆分逻辑有问题还能后悔。images目录放jpglabels目录放txt这是YOLO训练的标准位置。xml单独放到annotations目录给后续做误检分析和mAP曲线诊断用。参数说明shutil.copy每张图要复制三次1182张图全部跑完大概几秒钟速度可以接受。如果你有强迫症想把目录压缩得整齐一些可以把xml去掉只保留images和labels但我不建议删后面做模型误差分析时xml是很有用的参考标注层。4.3 密集场景的训练参数感知imgsz、anchor和batch怎么定这个数据集最有辨识度的特点是每张图平均约33个框。这意味着它不是那种“一张图一两个目标”的常规检测数据集而是接近密集小目标场景。因此训练命令里最值得调整的不是epoch而是输入分辨率imgsz与模型的基础anchor策略。yolo detect train datatuopan.yaml modelyolov8s.pt epoch100 batch32 imgsz640 device0逻辑说明这是ultralytics仓库的标准训练命令。参数全部用名字直传data指定数据集配置model指定预训练模型imgsz是训练输入边长。如果直接在默认的640分辨率下训练jpg原图的宽高比例会被强制缩放密集堆叠的托盘框可能被压缩到很小的尺寸小目标召回率会偏低。参数说明imgsz从640提到896或1280可以明显缓解密集小目标的漏检代价是显存占用大幅上升24GB显卡在batch 32时比较吃力通常要降到16甚至8。batch大小跟着显存走显存不够时优先降batch而不是降imgsz。关于anchor我一般会在训练初轮开启自动聚类让框架根据tuopan数据重新计算anchor尺寸而不是沿用COCO预训练模型的默认值。5. 避坑/常见问题五个让工程翻车的细节数据集本身质量不错但使用过程中的翻车点主要出现在解压环境、坐标转换和训练拆分这几个环节。下面五条记录是我在类似数据上反复遇到过的每条都按现象、原因、解决三步说明。5.1 解压后出现__MACOSX和._开头文件统计脚本怎么都对不齐现象在macOS或部分Linux桌面环境下解压这个zip目录里除了JPEGImages、Annotations、labels之外多出一个__MACOSX文件夹图片目录里还有不少._开头的隐藏文件。跑前面2.1节的核对脚本时得到的结果比预期多出一堆莫名其妙的“假文件名”。原因压缩包是在macOS上制作的Finder写入的AppleDouble文件被一并打包._文件就是存储文件扩展属性的残留物。Windows下默认隐藏这类文件但os.listdir会原样读出来。解决解压后先清理再进入后续流程。find . -name ._* -delete rm -rf __MACOSX逻辑说明find命令递归删除所有以._开头的文件rm删除__MACOSX目录。执行完后再做一次目录列表检查确保只剩下JPEGImages、Annotations、labels三个文件夹顺手把顶层目录重命名为纯英文比如tuopan_root。参数说明这两条命令是幂等的就算没有匹配文件也不会报错。处理任何来源不明的压缩数据建议解压后都先执行一遍省得后面被统计脚本的“数量不一致”误导。5.2 训练到一半loss变成nan验证集mAP全程为0现象训练跑了十几个epoch损失曲线突然跳到nan验证集mAP一直停在0换什么模型都是这样。原因YOLO格式的txt里有个别行的宽度或高度超出了1.0在Mosaic增强时坐标被裁切到图像外部计算loss时出现极端梯度。这种越界数据在单个文件里很难用肉眼发现因为每一行看起来都像是合法数字。解决在训练前把第3.1节的越界扫描跑一遍发现out_of_range非零就逐行定位。修正方式有两种要么删除越界行重新保存txt要么回到对应的xml文件用bndbox里的绝对坐标重算一遍归一化数值。优先用xml反算因为xml保存的是原始像素坐标重新计算出来的txt更准确。5.3 VOC画框和YOLO画框位置不重叠目标框整体偏移现象用xml的bndbox坐标画标注框位置看起来完全贴合目标但用txt的归一化坐标还原画框框的位置整体偏离两个框的中心对不上。原因坐标转换过程中出现了“先取整、再换算”的精度丢失。比如用(xmin xmax) / 2计算中心点时会得到浮点数但有些脚本先把结果转成int再乘回图片宽度这个截断误差在1920宽度的图上会被放大成明显偏移。解决全程用浮点数运算只在最后cv2.rectangle画框那一步才转int。之前3.3节的画框脚本就是标准写法txt读取后立刻转float换算像素坐标时都用float参与计算直到画框前才用int()取整。这个问题从根源上避免比事后逐条修正要省事得多。5.4 每张图框数太多loss下降很快但mAP始终上不去现象训练时loss曲线下降得很漂亮但验证集mAP50一直徘徊在0.4附近拖到真实仓库视频里几乎不能直接使用。原因每张图约33个框的场景里前景与背景的比例严重失衡默认的anchor和分类损失权重其实是按COCO那种“每张图几个目标”的分布设计出来的直接套用会漏掉大量小尺寸托盘。另一个容易被忽略的原因是数据拆分时没有正确处理顺序问题比如文件名字典序刚好把白天和夜间图片切分到了两个集合模型只在白天学到模式验证集全是夜间照片。解决训练时允许框架自动聚类anchor尺寸让预定义框适配托盘的真实长宽分布同时把imgsz从640提到896小目标召回率通常会有明显提升。数据拆分时一定要用random.shuffle并固定seed记录在实验笔记里。5.5 直接把整个文件夹喂给训练脚本导致数据泄漏现象用ultralytics命令行时把data参数指定成datasets/tuopan根目录训练集和验证集指向同一个位置最后mAP虚高到0.99部署到新场景立刻崩盘。原因没有做真正的train/val分离。验证集里出现了训练过的图片模型实际上是“背题”而不是“解题”评估结果完全失真。解决严格按4.2节生成split后的子目录yaml配置里仅为train和val指明各自独立路径。如果团队里多人协作复现一定要把拆分后目录结构整个发给对方避免对方在本地重新拆出不同分布。6. 让数据集跑起来从解压到一次短训练的完整验证链数据合格只是第一步真正让这个数据集产生价值的是“从解压到训练出第一版模型”的完整链路是否顺畅。我现在的标准流程是五步解压到英文路径、跑格式校验、拆分目录、短训练、可视化预测。每一步都通过再进入下一步任何异常都不跳过。切分完毕后YOLO训练需要的tuopan.yaml配置我一般写成下面这样path: ./datasets/tuopan train: train/images val: val/images nc: 1 names: [tuopan]参数说明path是数据集根目录train和val分别指向拆分后的图片路径nc为1表示只有一个类names必须和标注里的类别id一一对应这里就是[tuopan]。yaml里最容易出错的是nc写错比如漏改默认的80训练启动时框架会在类别索引检查时报错。短训练我一般先跑20个epoch验证链路而不是一上来就训100轮。因为20轮足够看出数据格式有没有问题、梯度是否正常、loss是否会下降。确认链路畅通后再按4.3节的参数正式开跑。训练结束后先不看mAP而是拿验证集里几张最有代表性的图做一次可视化预测看预测框与真实托盘的贴合程度。预测框整体偏大多半是anchor尺寸没调好预测框整体偏小多半是输入分辨率不足框的位置对但置信度低说明目标特征不够明确需要进一步考虑增强策略。这个数据集标注了“图片未增强”我猜正是希望保留仓库实拍的原生特征。所以初始阶段我倾向于不开大幅度的颜色抖动和随机擦除等跑出一个baseline再看有哪些场景预测不理想再针对性地补增强。顺序反了的话颜色增强反而会污染托盘本身的特征让模型在光照变化剧烈的仓库里更不稳定。从那以后我每次拿到新的检测数据集都会强制走一遍“解压到英文路径、格式校验、拆分目录、20轮短训练、可视化预测”的完整流程。这套习惯给我省下了大量“训练一晚上才发现数据有0到1个越界框”的重复时间也让我能放心地说这份数据真的能用于训练。希望这个流程对你也一样管用。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询