简介:面向深度学习目标检测任务的仓储机器人识别数据集,涵盖比亚迪托盘式、G1RB5000、AGV-P三类常见仓储设备,适用于YOLO系列、Faster RCNN、SSD等主流目标检测算法的训练与验证。包内文件总数达两千个,其中包含一千五百一十个文本格式的标签文件、四百八十九个可扩展标记语言格式的标签文件,以及一个用于定义类别名称与编号的配置文档;图片与对应标签已经预先划分为训练集、验证集和测试集,并提供VOC与YOLO两种主流标注格式,研究者拿到数据后无需额外整理,即可直接输入到检测框架中开展训练。压缩包整体约八十二兆,体积适中,便于本地复制与云端存储。目前已有三百八十三人学习使用,覆盖高校实验室、企业算法团队以及刚接触仓储机器人识别的开发者。借助这一数据集,用户能快速熟悉仓储机器人的标注规范,理解不同类别的对应关系,省去数据清洗与格式转换的步骤,将更多精力投入模型改进与场景部署。
1. 为什么 AGV 仓储场景的目标检测,不能拿自动驾驶数据集硬顶
做仓储机器人视觉的人,第一步往往卡在数据上:手里没有一套像样的 agv仓储机器人识别数据集,就急着拿 COCO 或自动驾驶数据集先跑通流程。我见过不少项目这样起步,真机一测就翻车——AGV 顶升托盘时看不到托盘腿、两车交汇时把另一台 AGV 认成货架、夜间补光下把地面反光当成障碍物。这不是模型不行,是通用数据集里根本没有这些视角和场景。本文会从数据采集、标注规范、训练配置到难例挖掘讲完整套落地路径,覆盖目标检测数据集处理的常见坑,适合做仓储机器人视觉、对接 AGV 调度系统或者打算自建数据集的工程师参考。数据集选对方向,YOLO 系列模型在这类固定机位俯拍场景里收敛很快;数据集选错,调参全是玄学。
2. AGV 仓储机器人识别数据集的构成与采集:机位、场景分层与目录组织
2.1 采集机位与视角:AGV 视觉为什么和自动驾驶差这么多
AGV 的感知方案一般分两种:一种是 AGV 自带相机朝前看,识别货架、托盘和人;另一种是场端固定相机从高处俯拍,识别 AGV 本身的位置和姿态,把结果喂给调度系统。不管哪种,视角都和自动驾驶的水平前视完全不一样,采集时必须按实际安装位置来定机位。
我见过最典型的错误,是拿路测的行人数据集来训仓储场景,模型在真机上对弯腰搬货的工人完全无感。原因很简单:行人数据集是水平视角,人体是竖着的;AGV 俯拍视角下,人是一个「头顶 + 肩膀」的椭圆,特征分布完全不同。所以自建数据集第一步不是选模型,而是先固定相机安装高度和角度。常见做法是:AGV 顶部相机离地 1.2~1.5m,略向下俯视 10~15 度;场端相机装在通道上方 2.5~3m,垂直向下或接近垂直。这样采集到的图像,模型上线后看到的和训练时看到的基本一致,泛化压力小很多。
视角定了之后,还有一个常被忽略的点:镜头畸变。仓储通道一般用 6mm 或 8mm 广角镜头,画面边缘畸变很明显。固定机位下畸变是确定性的,模型能自己学会,但前提是训练数据的机位分布要覆盖实际部署的所有点位。如果场端有 8 个相机位,至少要让每个点位都有样本进入训练集,而不是只在一条通道里采数据。
2.2 场景分层:白天、夜间、货架遮挡怎么切
AGV 仓储环境的干扰因素很集中:光照突变、货架遮挡、地面反光、同类目标交会。采集阶段就要按这些维度做场景分层,而不是简单按「拍多少张」来规划。
光照至少要分五档:强日间(靠窗通道会有阳光直射)、普通日间、黄昏、夜间补光、暗场(局部灯管损坏)。其中夜间补光最容易出问题——AGV 的大灯或场端补光灯照在环氧树脂地坪上会产生大面积反光带,模型容易把反光当成障碍物。采集时故意把反光严重的时段多采一些,当作训练数据而不是避开它,反而能让模型学会忽略反光。
遮挡分层要跟着货架布局走。仓储通道里的典型遮挡有三类:货架立柱挡住部分行人、两个托盘叠放、AGV 从货架缝隙穿过时车身被局部遮挡。这些样本不能靠随机拍,得拿着采集清单在场景里找着拍。每类遮挡至少占总样本量的 10%~15%,否则模型只在验证集上好看,真机遇到遮挡就漏检。
2.3 目录组织与格式转换:VOC 到 YOLO 的最小脚本
数据采集完,第一步是把原始文件整理成标准目录结构。下面是我常用的组织方式,train/val/test 按场景通道切分,而不是随机切分:
agv_dataset/ ├── images/ │ ├── train/ # 通道 A-F,含日间/黄昏样本,约 7200 张 │ ├── val/ # 通道 G,不含夜间,约 1200 张 │ └── test/ # 通道 H + 全部夜间样本,约 1600 张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── meta/ │ ├── scene_division.csv # 每张图所属通道、时段、光照标签 │ └── attribute.csv # 遮挡程度、截断属性表 ├── agv.yaml └── README.mdtrain/val/test 按通道切分,是为了防止同一个通道的连续帧同时出现在训练集和验证集里。如果随机切,模型其实见过验证集的「邻居帧」,mAP 会虚高很多,这个坑后面详述。meta 里的 scene_division.csv 一定保留,后面做难例挖掘和补采规划全靠它。
标注工具默认输出的往往是 VOC XML 格式,YOLO 训练需要 txt。转换脚本是数据集落地的第一个必写工具,这里给一个能直接跑的版本:
import xml.etree.ElementTree as ET from pathlib import Path # VOC XML 转 YOLO txt,类别顺序需要和 agv.yaml 里的 names 完全一致 CLASS_MAP = {"agv_robot": 0, "person": 1, "pallet": 2, "shelf": 3, "forklift": 4, "obstacle": 5} def voc_to_yolo_one(xml_path: Path, out_dir: Path): tree = ET.parse(xml_path) root = tree.getroot() img_w = float(root.find("size/width").text) img_h = float(root.find("size/height").text) lines = [] for obj in root.findall("object"): # difficult=1 的框通常是极模糊目标,直接跳过,避免噪声标签 if int(obj.find("difficult").text) == 1: continue name = obj.find("name").text if name not in CLASS_MAP: continue bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 归一化到 0~1,YOLO 格式是中心点 + 宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪越界框,防止训练时报错 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = out_dir / (xml_path.stem + ".txt") out_path.write_text("\n".join(lines)) # 批量转换:images/train 下的 xml 全部转成 labels/train 下的 txt xml_root = Path("agv_dataset/annotations/train") out_root = Path("agv_dataset/labels/train") for xml_file in xml_root.glob("*.xml"): voc_to_yolo_one(xml_file, out_root)这里有两个参数要注意:img_w和img_h用的是 XML 里的 size 字段,而不是重新读图。如果标注过程中有人用工具裁剪过图片但没更新 XML,尺寸会错位,导致标注框整体偏移,训练时 loss 怎么调都下不去。稳妥做法是脚本跑完后抽查 20 张图,把 txt 框画回原图比对一下,确认坐标没偏。CLASS_MAP的类别顺序必须和 yaml 的 names 完全一致,不然模型学到的是错位的类别映射,推理时全乱。
3. 标注工具选型与标签体系:AGV 数据集的类别树怎么设计
3.1 标注工具选型:LabelImg、X-AnyLabeling 还是 CVAT
数据集规模决定标注工具的选择。标注是 AGV 数据集建设里最耗时的一环,选错工具会导致返工成本极高。我的经验是三个档位:
| 工具 | 适用规模 | 输出格式 | 关键坑 |
|---|---|---|---|
| LabelImg | 单人、3000 张以内 | VOC XML / YOLO txt | 只画矩形框,圆弧或异形物体框很大,无辅助分割 |
| X-AnyLabeling | 单人、带 SAM 辅助 | VOC / COCO / YOLO | 需要本地模型配置,机器太老会卡;适合抽边框 |
| CVAT | 多人团队协作 | COCO / VOC / YOLO | 需要部署服务;导出格式要做二次字段映射 |
单人做小规模数据集,LabelImg 够用;超过 5000 张还手动拉框,手腕和眼睛都扛不住,我一般会切到 X-AnyLabeling,用 SAM 先出候选框再人工修正,速度能快一倍以上。多人协作必须上 CVAT,但导出时要注意它默认带label_id字段,和 YOLO 的class_id定义不完全一致,转换时容易串类别。另外,标注中途发现标签体系改动,用 LabelImg 改几百个 xml 还行,用 CVAT 改几千条标注记录更麻烦,所以标签体系一定要在启动标注前定死。
3.2 标签体系设计:类别树与遮挡属性怎么定
AGV 场景的标签体系不是「人能看见什么就标什么」,而是要问一个问题:AGV 的决策逻辑到底需要知道什么?
AGV 路径规划和避障需要的是可通行区域的语义,调度系统需要知道每台车在哪、托盘在哪。所以我常把类别控制在 6 个以内,每个类别都有明确的决策含义:
| 类别 | 标签名 | 决策用途 | 最小标注尺寸 |
|---|---|---|---|
| AGV 机器人 | agv_robot | 多车交汇避让,AGV 协同的关键目标 | 40×40 px |
| 人员 | person | 安全减速、停靠 | 30×60 px |
| 托盘 | pallet | 顶升对接、取货点确认 | 60×60 px |
| 货架 | shelf | 通道边界约束 | 100×100 px |
| 叉车 | forklift | 人工叉车混行区避让 | 80×80 px |
| 临时障碍 | obstacle | 绕行/停车等待,含纸箱、地牛、散件 | 30×30 px |
这里有一个容易混淆的点:托盘和货架下的货物怎么区分。我的约定是:托盘和货架作为结构化目标单独标;货架上的货箱、托盘上的码垛如果和货架/托盘轮廓重叠严重,不额外开「货物」类,而是统一归到所属的货架或托盘框内。因为 AGV 对接时关心的是托盘本身的位置,不是托盘上码了几层箱。如果强行标货物类,两个框高度重叠,模型训练时会被互相干扰,AP 反而下降。
遮挡属性的处理是另一个坑。YOLO 的 txt 格式只支持class x y w h,塞不下遮挡程度和截断信息。但这些信息对后续分析很有价值,比如夜间样本遮挡率高的帧,往往是难例挖掘的重点。常见做法是把属性写进文件名后缀,或者单独维护一张 attribute.csv,字段包括:image_name, scene_id, period, occlusion_ratio, truncated。属性不参与训练,但参与数据分析和补采决策。这个问题容易被忽略,等你想按遮挡率筛选数据时发现没记录,只能重新看一遍图,血泪教训。
3.3 标注规范:边界框贴边还是不贴边
标注规范不统一,是数据集质量最大的杀手。AGV 场景最容易出现的标注问题有三个。
第一个是框的范围不统一。同一个人,一张图框到帽子顶,另一张图框到头发,模型学到的目标边界是抖的。我一般定的规范是:框贴着目标可见区域的外沿,不包含投影和反光,不包含接触的地面阴影。尤其是托盘,四个腿和底面之间会有阴影,新手标的时候很容易把阴影包进去,导致框忽大忽小。
第二个是极小目标标不标。俯拍视角下,远处的人可能只有 20×30 像素,框都看不清。我建议在标签体系里定义最小标注尺寸,上表已经列出,小于这个尺寸的物体一律不标。否则会出现大量「标了但框不准」的样本,模型学到的全是噪声。标注工具一般有缩放功能,把图片放大 4 倍再判断是否达到最小尺寸。
第三个是类别混淆。最典型的是把叉车标成 AGV,或把地牛(手动液压车)标成障碍物。这类混淆一旦出现就是系统性的,模型会在叉车和 AGV 之间反复横跳。规避办法是给标注员一套带典型图的类别说明,每类至少放 5 张示例图,标之前集体过一遍。
标注完成后一定要做抽检。我习惯按 10% 比例抽检,重点看两类图:夜间图和多目标密集图。抽检发现单张图错误超过 2 处,这一批就翻工,不要抱着「先训练再修正」的心态,模型不会告诉你它学歪了,只会给你一个虚高的 mAP 和一堆莫名其妙的漏检。
4. 用 YOLOv8 训练自建 AGV 数据集:配置文件、训练命令与三个必调参数
4.1 训练前的配置文件:data yaml 与目录路径
数据集准备好,第一步是写 YOLOv8 的 data yaml。这里最常见的失误是用绝对路径,换台机器训练就炸。正确写法是相对路径配合path字段:
# datasets/agv/agv.yaml path: ./ # 相对于运行命令的目录,这里指 datasets/agv/ 下 train: images/train val: images/val test: images/test names: 0: agv_robot 1: person 2: pallet 3: shelf 4: forklift 5: obstaclepath字段的取值逻辑是:假如你在agv_dataset上一层目录运行训练命令,path就写./agv_dataset;如果直接在agv_dataset目录里运行,写./就行。names的顺序必须和第 2 章转换脚本里的CLASS_MAP完全一致,这是整个流程里最容易翻车的地方——类别 id 错一位,模型训练时 loss 正常下降,但推理结果全错,而且很难排查。
4.2 训练命令与超参:imgsz、mosaic、close_mosaic 怎么调
在 AGV 数据集上,我用得最顺的基线是 YOLOv8s。不是 v8 一定比 v5 强多少,而是 v8 的 anchor-free 头对小目标更友好,AGV 俯拍视角下远处的人属于典型小目标。训练命令如下:
yolo detect train \ data=datasets/agv/agv.yaml \ model=yolov8s.pt \ imgsz=640 \ epochs=120 \ batch=16 \ mosaic=0.5 \ close_mosaic=10 \ patience=20 \ project=runs/agv \ name=exp_021三个必调参数说清楚:
imgsz我固定用 640,不上 1280。AGV 俯拍场景的物体尺度比自动驾驶稳定,640 已经能覆盖上表定义的最小目标;上 1280 显存翻倍,mAP 提升一般不超过 2 个点,对真机实时推理也没帮助。如果项目对远处小目标要求极高,优先处理的是补采近距离样本,而不是硬上分辨率。
mosaic是 AGV 场景最需要压的参数。YOLOv8 默认mosaic=1.0,但仓储场景货架遮挡多,mosaic 会把不同通道的图拼在一起,制造大量「半张货架 + 半个人」的拼接样本,模型学到的是拼接缝特征而不是目标特征。我把 mosaic 压到 0.5,保留了数据增强的多样性,又避免了过度拼接。如果发现训练前期 loss 降得很慢,可以继续压到 0.3 试试。
close_mosaic=10是配套参数,最后 10 个 epoch 关闭 mosaic,让模型在真实分布上做最后的收敛。如果不关,验证集上 mAP 看着不错,但真机跑起来对完整目标的响应反而不稳定,因为模型已经习惯了看残缺目标。
4.3 训练日志怎么看:loss 曲线与 mAP 的合理区间
训练不是把命令跑完就完事,日志里有两个地方值得盯。第一个是 loss 曲线,box_loss在 120 epoch 下应该降到 0.8 附近,cls_loss降到 0.02 附近。如果cls_loss降不下去还反弹,大概率是标注里混入了类别错误,回去查标注抽检记录,别急着调参。
第二个是mAP50-95和mAP50的关系。AGV 这种目标尺度相对集中的数据集,mAP50一般比mAP50-95高 15~20 个点,如果差距超过 25 个点,说明模型对框的中心点不够准,可以在训练完做一轮 fine-tune:降低学习率,只微调最后两层,跑 30 epoch。我在 AGV 数据上用过这个办法,mAP50-95 能再涨 3~5 个点,代价是训练时间多 20 分钟,值得。
说到验证,很多人训练完只看model.val()的汇总指标就收工。我建议跑完训练后额外做一次带save_json=True的验证,输出每张图的检测详情,后面难例挖掘要用。
| 参数 | 默认值 | AGV 场景建议 | 原因 |
|---|---|---|---|
| imgsz | 640 | 640 | 俯拍场景目标尺度稳定,上 1280 收益低 |
| mosaic | 1.0 | 0.5 | 货架遮挡多,mosaic 拼出大量半遮挡假样本 |
| close_mosaic | 10 | 10 | 最后阶段恢复真实分布,稳定最终权重 |
| batch | 16 | 尽量 16 以上 | batch 太小 BN 不稳定,loss 震荡 |
| patience | 100 | 20 | AGV 数据集类少,后期过拟合快,早停防止权重漂移 |
5. AGV 数据集训练避坑:验证集泄漏、小目标漏检与标注不一致
5.1 mAP 虚高但真机漏检:验证集场景泄漏的锅
现象:训练完验证集 mAP50 到 0.95,模型上真机一测,通道另一端的目标大面积漏检。
原因:train/val/test 随机切分,同一个通道的连续帧同时出现在训练集和验证集里。模型在训练时见过验证集帧的「邻居」,验证集评估基本是开卷考试。AGV 是连续移动拍摄,相邻帧背景高度相似,随机切分等于泄漏。
解决:按采集场景切分数据,这是我反复强调的一点。第 2 章的目录结构里,train 放通道 A-F,val 放通道 G,test 放通道 H,不同通道之间互不重叠。如果场景点位数不够,退而求其次按时间段切:白天训练、夜间验证,也能保证基本隔离。切分完成后看一眼 val 集里的图片数量,如果超过总量的 20%,回退一些,验证集太大反而把训练集饿死。
5.2 夜间样本总学不好:类别分布失衡
现象:总 mAP 过了 0.9,但按夜间样本单独统计,person 类 mAP50 只有 0.6,AGV 在夜间对行人几乎不响应。
原因:夜间样本只占总量 5%,训练时模型把注意力全放在日间高频特征上。loss 是全体样本的平均,少数类样本的贡献被稀释。
解决:两个手段配合。一是补采,把夜间样本比例提到至少 15%,这是治本。二是在训练时给夜间样本所在的困难场景做损失加权,YOLOv8 没有现成的样本权重参数,可以在数据层面操作:把夜间图像做亮度增强副本放进训练集,相当于软重复采样。注意不要简单硬复制同一张图,模型会过拟合到具体像素,做一点亮度扰动再放进去才有效。
5.3 同一物体相邻帧标签忽大忽小:AP 不稳定
现象:训练 loss 收敛正常,但验证集里同一物体在相邻帧的预测框抖动明显,mAP50 波动超过 3 个点。
原因:标注时包含了阴影、反光或部分遮挡物,导致同一物理目标在不同帧里的标注框范围不一致。模型学到的是一个「可变形的目标边界」,预测自然飘。
解决:回查标注规范,重标问题严重的类别。AGV 场景里最容易犯的是托盘框包含了地面阴影——补光灯下托盘腿的影子可以拉出半米长,框就忽大忽小。我处理过一批这样的数据,重标后 mAP50 稳定性直接改善。规范就是 3.3 写的:框贴着目标可见区外沿,不包含阴影和投影。标注抽检时专门查这一类。
5.4 模型把托盘认成障碍物:漏标太多
现象:真机上 AGV 明明看到前方有托盘,模型却输出 obstacle,调度系统触发了不必要的停车。
原因:标注阶段漏标了部分托盘,尤其托盘摞在一起、只露出侧面时,标注员没意识到那是托盘。训练集里这个角度的托盘样本太少,模型学到的托盘特征是「四腿分开」的标准正视图,侧视托盘全部落入 background 或被归到 obstacle。
解决:检查托盘类别在不同角度下的样本直方图。我一般按「正视图、侧视图、堆叠、遮挡」四个维度统计,每个维度至少保留 300 张。不够就补采,专拍托盘堆叠区和货架底层的侧视样本。另外,漏标导致的问题无法靠调参解决,任何置信度阈值调整都救不回来,只能补数据。
5.5 补光灯造成的反光带:障碍物类误报飙升
现象:夜间补光下,地面出现大面积反光带,模型在无物体的地面上输出大量 obstacle 框,AGV 频繁减速。
原因:反光带在图像里的纹理和边缘特征接近障碍物,模型从训练数据里学到「这种亮带可能有东西」。如果训练集没有反光负样本,模型只能靠猜。
解决:这是负样本问题,不是正样本问题。采集时专门拍一段夜间空通道反光视频,抽帧后全部标注为「背景」加入训练集,不分配标签。YOLOv8 会在训练时把背景图作为负样本,模型学会抑制这类纹理。我一般会让反光负样本占夜间的 10% 左右,足够压低误报率。注意:不要把这些图分配 obstacle 标签,那样会把模型教歪——反光不是障碍物。
6. 用难例挖掘验证数据集价值:漏检高发帧筛选脚本与补采优先级
模型训完,指标好看只是第一步。真正的验收动作是跑一轮难例挖掘,把验证集里漏检率高的帧找出来,按场景维度归因,再决定补采方向。下面这个脚本是我每次训练后固定跑的:
from ultralytics import YOLO from pathlib import Path import numpy as np import pandas as pd model = YOLO("runs/agv/exp_021/weights/best.pt") label_dir = Path("datasets/agv/labels/val") miss_rows = [] for label_file in sorted(label_dir.glob("*.txt")): if label_file.stat().st_size == 0: continue img_file = label_file.with_suffix(".jpg") if not img_file.exists(): img_file = label_file.with_suffix(".png") # 兼容 png 原图 # 读 GT 框,YOLO txt 每行是 cls x y w h gt_boxes = [] for line in label_file.read_text().splitlines(): cls, x, y, w, h = map(float, line.split()) gt_boxes.append([x, y, w, h]) pred = model.predict(str(img_file), conf=0.25, verbose=False)[0] pred_boxes = pred.boxes.xywhn.numpy() if pred.boxes is not None else [] # 用 GT 中心点是否落在预测框内判断命中,简单有效 missed = 0 for gx, gy in [(b[0], b[1]) for b in gt_boxes]: hit = any( np.abs(p[0] - gx) <= p[2] / 2 and np.abs(p[1] - gy) <= p[3] / 2 for p in pred_boxes ) missed += not hit ratio = missed / len(gt_boxes) if ratio > 0.5: miss_rows.append([img_file.name, len(gt_boxes), missed, ratio]) pd.DataFrame(miss_rows, columns=["image", "gt_count", "missed", "miss_ratio"]) \ .sort_values("miss_ratio", ascending=False) \ .to_csv("agv_val_miss_ratio.csv", index=False)脚本逻辑不难:逐张读取验证集标签,跑模型预测,用 GT 中心点是否落入任意预测框判定命中,最后按漏检比例排序输出 CSV。conf=0.25要和训练时验证的阈值一致,否则阈值不同会导致误判。中心点判定是偏乐观的——如果预测框很小但 GT 很大,中心点落在框内也可能只覆盖了物体一小部分,所以阈值取 0.5,只筛严重漏检帧,不作为精确指标。
拿到 CSV 之后,按照image字段反查第 2 章的scene_division.csv,把漏检帧按通道和时段聚合。我自己的经验是,补采优先级这样排:第一,漏检帧最密集的通道加采一轮,优先补足该点位的光照条件;第二,新增机位,比如充电区、接驳区,这些区域一般是采集盲区,但 AGV 每天都要经过;第三,新光照条件,灯管老化偏黄时的通道、黄昏逆光的通道,各补 200 张就有明显效果。
这个脚本还有一个变体:把高置信误检帧也列出来,比如obstacle类置信度超过 0.8 但 GT 里没有对应框的图,这就是第 5 章反光带误报的数据来源,分析方法和漏检完全一样。我见过太多人做完数据集就扔一边,真机出问题再回头翻训练数据,那时候定位成本高得多。在 AGV 识别这种场景相对固定的任务里,每轮训练后跑一次难例挖掘、看前 20 张问题图再决定下一步,比盲目调超参和换网络结构都管用。我的习惯是先看数据再看指标,指标只能告诉你模型好不好,难例挖掘才能告诉你是数据不行还是模型不行。希望帮到你。
本文还有配套的精品资源,点击获取