☰
仓库托盘检测数据集全解析:YOLO/VOC标注校验到训练避坑
2026/10/1 10:34:30 网站建设 项目流程

简介:一套面向仓库内托盘检测任务的目标检测数据集,专为需要训练YOLO、Faster R-CNN等算法的研究者与开发者准备。压缩包共2000个文件,包含1182个XML标注文件和818个TXT标签文件,整体约192.54MB。数据采用VOC与YOLO双格式存储,标注信息完整,标签仅“tuopan”一类,共38971个矩形框,框体覆盖托盘在不同角度与光照下的形态,图片清晰度较高且未做增强,可直接用于模型训练、验证与精度评估。资源内按图片、XML、TXT分文件夹整理,目录结构清晰,便于数据加载与二次开发,适用于仓库管理、物流跟踪、库存盘点等场景,也可作为目标检测算法的对比测试数据。已有130人学习下载,能有效节省自行采集与标注的工作量,帮助开发者在实际项目中快速起步。

1. 仓库托盘检测的工程起点:1182张图、38971个框能做什么

在自动化仓库场景里做移动小目标检测,托盘是被低估最多的目标。托盘外形看着规整,但仓库里一出现堆叠、遮挡和光照变化,常规检测模型的漏检率就会明显上升。这次拆解的仓库内托盘检测数据集以yolo+voc双格式交付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_Store,Windows下解压产生的__MACOSX目录残留,都可能混入集合导致统计失真。这类检查跑一次只要几秒钟,但能挡掉后面训练中一大半的“label数量不匹配”报错。

2.2 同一张图的VOC与YOLO标注,坐标怎么换算

VOC格式用xml记录信息,每一张图对应一个annotation根节点,里面包含图片尺寸、文件名和多个object子节点,每个object描述一个目标框。本数据集里object的name字段只有一种取值:tuopan。这是Pascal VOC最标准的写法,方便人工阅读,也方便在labelImg这类工具里二次修改。

<annotation> <folder>JPEGImages</folder> <filename>000042.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>tuopan</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>302</xmin> <ymin>451</ymin> <xmax>688</xmax> <ymax>742</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 / width,y_center = (ymin + ymax) / 2 / height,w = (xmax - xmin) / width,h = (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", encoding="utf-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", encoding="utf-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(f"datasets/tuopan/{split_name}/{sub}", exist_ok=True) for n in names: shutil.copy( os.path.join(src_dirs["jpg"], n + ".jpg"), f"datasets/tuopan/{split_name}/images/{n}.jpg" ) shutil.copy( os.path.join(src_dirs["txt"], n + ".txt"), f"datasets/tuopan/{split_name}/labels/{n}.txt" ) shutil.copy( os.path.join(src_dirs["xml"], n + ".xml"), f"datasets/tuopan/{split_name}/annotations/{n}.xml" )

逻辑说明:脚本遍历train_names和val_names两个集合,使用shutil.copy复制而不是move,这样原始三条目录保持完整,万一拆分逻辑有问题还能后悔。images目录放jpg,labels目录放txt,这是YOLO训练的标准位置。xml单独放到annotations目录,给后续做误检分析和mAP曲线诊断用。

参数说明:shutil.copy每张图要复制三次,1182张图全部跑完大概几秒钟,速度可以接受。如果你有强迫症想把目录压缩得整齐一些,可以把xml去掉只保留images和labels,但我不建议删,后面做模型误差分析时xml是很有用的参考标注层。

4.3 密集场景的训练参数感知:imgsz、anchor和batch怎么定

这个数据集最有辨识度的特点是每张图平均约33个框。这意味着它不是那种“一张图一两个目标”的常规检测数据集,而是接近密集小目标场景。因此训练命令里最值得调整的不是epoch,而是输入分辨率imgsz与模型的基础anchor策略。

yolo detect train data=tuopan.yaml model=yolov8s.pt epoch=100 batch=32 imgsz=640 device=0

逻辑说明:这是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个越界框”的重复时间,也让我能放心地说这份数据真的能用于训练。希望这个流程对你也一样管用。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询