自定义数据集 Pose 生成与坐标系约定内部文档

1. 目的

本文用于统一团队内部对以下问题的理解和实施口径:

  1. 什么是物体坐标系
  2. 什么是相机坐标系
  3. cam_R_m2ccam_t_m2c 到底表示什么
  4. 自定义数据集中 pose 应该如何生成
  5. pose 应该如何存到 BOP / LINEMOD / PVN3D 使用的文件里

本文不是训练脚本使用说明,而是给数据采集、标注、转换、训练、评估几方统一坐标系和 pose 定义。


2. 最核心的一句话

在当前 PVN3D / BOP / LINEMOD 语境里,pose 表示的是:

"物体模型坐标系中的点,如何变换到当前相机坐标系。"

也就是:

text 复制代码
X_cam = R * X_obj + t

其中:

  • X_obj 是物体坐标系中的 3D 点
  • R3x3 旋转矩阵
  • t3x1 平移向量
  • X_cam 是这个点在相机坐标系中的位置

这就是 BOP 里常写的:

  • cam_R_m2c
  • cam_t_m2c

其中:

  • m2c = model to camera

不是:

  • camera to model

这个方向如果搞反,后面所有投影、评估、可视化都会错。


3. 三个坐标系必须先说死

在团队内部,至少要把下面三个坐标系区分清楚。

3.1 物体坐标系 object / model frame

也可以叫:

  • 模型坐标系
  • CAD 坐标系
  • obj frame

它的来源是:

  • 物体 .ply / CAD 模型本身

模型上的每个顶点、关键点、中心点,初始都定义在这个坐标系里。

比如:

  • corners.txt
  • farthest.txt
  • .ply 顶点

本质上全都在物体坐标系里。

3.2 相机坐标系 camera frame

也可以叫:

  • cam frame
  • 当前帧相机坐标系

这是每一帧 RGB-D 图像对应的相机局部坐标系。

通常约定:

  • 原点在相机光心
  • Z 轴朝前
  • X 轴向右
  • Y 轴向下或向上,取决于具体约定和实现

对当前仓库来说,关键点不在于口头描述,而在于:

  • RGB 投影
  • 深度反投影
  • .ply 变换

必须都使用同一套约定。

3.3 世界坐标系 world frame

如果你的采集系统里有:

  • 机械臂
  • 转台
  • Vicon
  • ArUco / AprilTag 参考板
  • 多相机外参系统

那么往往还会有一个世界坐标系。

但要强调:

  • PVN3D 单帧训练与评估并不直接需要世界坐标系
  • 它最终只需要 model -> camera 的变换

世界坐标系的价值主要在于:

  1. 帮助采集时建立统一参考
  2. 方便从多种传感器反推每帧 R, t

4. 变换关系到底是什么

4.1 正向变换

如果某个点在物体坐标系中的坐标是:

text 复制代码
X_obj = [x, y, z]^T

则它在相机坐标系中的位置是:

text 复制代码
X_cam = R * X_obj + t

写成齐次矩阵形式:

text 复制代码
[X_cam]   [ R  t ] [X_obj]
[  1  ] = [ 0  1 ] [  1  ]

这里的 T_cam_obj = [R|t] 就是当前帧的 6D pose。

4.2 逆变换

如果你手上拿到的是相机坐标系中的点,想反推回物体坐标系,则需要取逆:

text 复制代码
X_obj = R^T * (X_cam - t)

也就是说:

  • R_inv = R^T
  • t_inv = -R^T * t

这一步常见于:

  • 可视化调试
  • 世界系到模型系的换算
  • 校验 pose 是否方向写反

4.3 当前仓库是如何使用这个变换的

在当前仓库中,模型点变换的典型写法是:

核心形式都是:

python 复制代码
pts_cam = np.dot(pts_obj, pose[:, :3].T) + pose[:, 3]

这和上面的公式本质一致,只是点数组通常是按行存储,因此写成右乘转置形式。


5. cam_R_m2ccam_t_m2c 应该如何理解

5.1 命名含义

在 BOP / LINEMOD 常见标注中:

  • cam_R_m2c
  • cam_t_m2c

含义是:

  • R: model to camera 的旋转
  • t: model to camera 的平移

其中:

  • m = model
  • c = camera

所以:

  • cam_R_m2c 不是"相机的旋转"
  • cam_t_m2c 也不是"相机在世界系里的位置"

它们描述的是:

"把模型上的点放到当前相机坐标系里时,该怎么变换。"

5.2 为什么很多人会弄反

因为很多采集系统更自然得到的是:

  • 相机相对于世界的位姿
  • 物体相对于世界的位姿
  • 或者相机相对于物体的位姿

而训练与评估真正需要的是:

  • 物体相对于当前相机的位姿

这中间可能需要做一次或多次坐标系拼接与求逆。

所以在团队协作里,一定不能只写"pose 已有",而必须明确写:

  • pose 的方向
  • pose 所在坐标系
  • 单位

6. pose 与投影、深度、模型的关系

pose 本身不是孤立存在的,它必须同时和以下三者对齐:

  1. 相机内参
  2. 深度图尺度
  3. 物体模型尺度

6.1 与相机内参的关系

如果 R, t 是对的,但相机内参 K 错了,那么:

  • 3D 点投到图像上的位置会错
  • 由深度反投影的点云也会错

最终表现为:

  • 可视化投影偏移
  • 训练监督不稳定
  • 测试评估异常

6.2 与模型尺度的关系

如果 .ply 的单位和 cam_t_m2c 单位不一致,例如:

  • 模型是米
  • cam_t_m2c 是毫米

那么即使方向对了,投影和评估也会严重失真。

当前仓库的 LINEMOD 流程中,模型读取时常假定 .ply 是毫米,再除以 1000.0 转成米,见:

因此团队内部必须统一:

  1. 模型文件用什么单位
  2. cam_t_m2c 用什么单位
  3. 转换脚本在哪一步做单位换算

6.3 与深度尺度的关系

如果深度图单位是毫米,而你当成米来用,反投影得到的点云会整体缩放 1000 倍。

这会直接影响:

  • 点云输入
  • 偏移监督
  • ADD / ADDS

所以必须记录:

  • 深度图原始单位
  • depth_scale
  • 点云恢复时的换算公式

7. 自定义数据集中 pose 的几种生成方法

下面按团队工程实现来讲,不只说"名字",而是说每种方法的输入、输出、优缺点和适用场景。

7.1 方法一:人工交互式标注

这是最传统也最直接的方法。

输入

需要有:

  1. 目标物体 3D 模型 .ply
  2. 每帧 RGB 图像
  3. 最好还要有深度图
  4. 相机内参
过程

标注者在工具里手动调整:

  • 模型旋转
  • 模型平移

使其在图像中与真实物体对齐。

如果工具支持深度或点云叠加,通常会更准。

手动调整时实际在调什么

人工调整 6D pose,本质上是在调 6 个自由度:

  • 平移:tx, ty, tz
  • 旋转:roll, pitch, yaw

在当前文档的统一定义下,这些量最终都要落到:

text 复制代码
X_cam = R * X_obj + t

也就是:

  • cam_R_m2c
  • cam_t_m2c

这里尤其要强调:

  • 交互界面里看到的欧拉角通常只是"编辑方式"
  • 最终落盘必须仍然是 model -> cameraR, t

对手调人员来说,可以按下面方式理解 6 个自由度:

  • tx:主要影响投影在图像中的左右位置
  • ty:主要影响投影在图像中的上下位置
  • tz:主要影响模型投影大小和前后距离
  • yaw:常表现为物体左右转头
  • pitch:常表现为物体前后俯仰
  • roll:常表现为物体在图像平面内旋转

但要注意:

  • 不同软件和脚本使用的欧拉角顺序可能不同
  • XYZZYXyaw-pitch-roll 不能默认等价

因此团队内部如果使用欧拉角作为中间表示,必须明确:

  1. 旋转轴顺序
  2. 角度单位是度还是弧度
  3. 最终如何转换回旋转矩阵
推荐的手调顺序

不建议 6 个量同时乱调。实际操作中,推荐固定为以下顺序:

  1. 先确认初始 pose、坐标系方向、单位没有根本性错误
  2. 先调 tz,让模型投影大小接近真实物体
  3. 再调 tx, ty,让模型整体位置落到目标上
  4. 再调 yaw, pitch, roll,修正轮廓和局部结构朝向
  5. 最后对 6 个自由度做小范围联调

这样做的原因是:

  • tz 决定整体尺度,先调它最直观
  • 平移定下来后,再看旋转更容易判断
  • 最后一轮联调可以修正"旋转改变后视觉重心变化"带来的小偏差
如何判断"已经对齐好"

手调不能只凭"差不多",建议至少检查下面四类信息。

  1. 轮廓对齐
  • 模型外轮廓应和真实物体边界基本重合
  • 尖角、长边、圆弧边界优先看
  1. 关键结构对齐
  • 孔洞
  • 缺口
  • 把手
  • 凹槽
  • 棱边

这些局部结构往往比整体外轮廓更能暴露旋转误差。

  1. 遮挡关系合理
  • 应该被遮挡的部分不能"穿出来"
  • 可见区域不能莫名缺失
  1. 深度关系合理
  • 如果有深度图,最好同时检查模型深度与实测深度是否贴合
  • 很多 tz 偏差在 RGB 上不明显,但在深度上会非常明显
推荐的交互能力

一个实用的人工 pose 微调工具,至少应支持:

  1. 加载 .ply 模型
  2. 加载 RGB 图
  3. 最好还能加载深度图
  4. 输入相机内参
  5. 输入初始 R, t
  6. 在 RGB 上实时投影模型
  7. 调整 tx ty tz + roll pitch yaw
  8. 导出 cam_R_m2ccam_t_m2c

从内部实现角度,建议至少提供两档步长:

  • 粗调步长:快速接近正确位姿
  • 细调步长:最后精修

例如:

  • 平移粗调可按 5 mm
  • 平移细调可按 1 mm
  • 旋转粗调可按 5 deg
  • 旋转细调可按 1 deg

如果后续需要提升效率,可以再增加:

  • 鼠标拖拽平移
  • 鼠标滚轮调 tz
  • 深度叠加
  • 点云叠加
  • 轮廓高亮
工具选型建议

人工交互式标注常见有三类路线。

  1. 通用 3D 软件

典型工具有:

  • Blender
  • MeshLab
  • CloudCompare

适合:

  • 查看模型
  • 做粗姿态摆放
  • 辅助检查点云和深度对齐

但通常存在下面问题:

  • 默认坐标系与工程代码未必一致
  • 导出的旋转和平移往往还要做额外转换
  • 不适合作为大批量逐帧精修的主工具

其中:

  • Blender 更适合做姿态粗调和背景图对齐
  • MeshLab / CloudCompare 更适合辅助检查点云、深度、几何贴合情况
  1. 自研轻量交互工具

最推荐的路线是:

  • Open3D + OpenCV

原因是可以直接围绕项目真实需要来做:

  • RGB 图
  • 深度图
  • .ply 模型
  • 相机内参
  • R, t 导出格式

对于当前 PVN3D 项目,这是最实用的方案,尤其适合:

  • 小规模高质量测试集
  • 已有初值、需要人工精修
  • 希望后续直接接训练和评估脚本
  1. 面向 6D pose / 数据集制作的专用标注工具

这类工具本质上也是:

  • 把模型叠加到图像或点云上
  • 人工调整 R, t

但它们通常不是当前仓库内置能力,需要单独选型或自研适配。

团队内部的实际建议是:

  • 小规模高质量测试集,优先用 Open3D + OpenCV 自写 pose 手调工具
  • 如果团队已经熟悉 3D 软件,可以先用 Blender 做粗调
  • 不建议直接使用普通 2D 标注工具,因为它们不适合 6D pose 微调
人工手调时最容易犯的错误
  1. model -> camera 写成了 camera -> model
  2. .ply 单位与 cam_t_m2c 单位不一致
  3. 相机内参填错,导致越靠边缘偏差越大
  4. 欧拉角顺序和旋转矩阵转换顺序不一致
  5. 只看 RGB,不看深度,导致 tz 偏差长期未被发现

因此在团队内部,人工手调的验收标准不应只写"人工对齐完成",而建议至少记录:

  1. 是否检查了 RGB 投影轮廓
  2. 是否检查了关键结构
  3. 是否检查了深度或点云贴合
  4. 最终导出的是否为 cam_R_m2ccam_t_m2c
输出

最终输出每帧:

  • R
  • t

也就是:

  • cam_R_m2c
  • cam_t_m2c
优点
  • 不依赖外部定位系统
  • 对现有真实场景最容易落地
  • 适合少量高质量测试集
缺点
  • 成本高
  • 容易有人工误差
  • 多帧、多对象时效率很低
适用场景
  • 自采测试集规模不大
  • 需要高质量 benchmark
  • 当前没有机械臂 / 外部定位系统

7.2 方法二:基于 AprilTag / ArUco / 标定板 / 外部定位系统

这是最常见的工程化真实数据方案。

基本思路

先建立一个外部参考系,例如:

  • 转台坐标系
  • 标定板坐标系
  • 机械臂基坐标系
  • 世界参考板坐标系

然后分别求:

  1. 相机相对于参考系的位姿
  2. 物体相对于参考系的位姿

最后拼出:

text 复制代码
T_cam_obj = T_cam_world * T_world_obj

或等价写法:

text 复制代码
T_cam_obj = T_cam_ref * T_ref_obj
输入

需要有:

  1. 相机标定结果
  2. 参考板几何信息
  3. 物体在参考系下的位置或姿态
  4. 采集时每帧的参考观测
输出

输出仍然必须整理为:

  • cam_R_m2c
  • cam_t_m2c
优点
  • 精度通常比纯人工标注更高
  • 可以批量处理
  • 适合搭建稳定采集流水线
缺点
  • 需要额外硬件或外部参考
  • 坐标系链路更复杂
  • 容易在求逆和矩阵乘法顺序上出错
适用场景
  • 要批量构建训练集
  • 要长期重复采集
  • 团队能维护一套稳定标定流程

7.3 方法三:仿真 / Blender 自动生成

这类方法本质上不是"真实标注",而是:

  • 直接在仿真环境里知道物体和相机的位姿
输入

需要有:

  1. 目标物体 CAD / .ply
  2. 虚拟相机参数
  3. 场景布置
  4. 渲染脚本
过程

在 Blender 或其他渲染器里:

  1. 放置物体
  2. 放置相机
  3. 自动输出 RGB / depth / mask
  4. 同步导出精确 R, t
输出

每帧输出:

  • RGB
  • depth
  • mask
  • cam_R_m2c
  • cam_t_m2c
优点
  • pose 精确
  • 标注成本低
  • 适合构造大量 renders/
缺点
  • 有 sim-to-real gap
  • 如果材质、光照、噪声不真实,泛化会差
适用场景
  • 构造 PBR 或渲染训练数据
  • 补充真实数据不足
  • 做数据增强

8. 如果你的真实采集里有世界坐标系,如何换成 model -> camera

这部分是团队最容易出错的地方。

8.1 常见已知量

很多采集系统里,容易直接拿到的是:

  • T_world_cam
  • T_world_obj

或者:

  • T_cam_world
  • T_world_obj

而训练要的是:

  • T_cam_obj

8.2 常见换算方式

如果你已知:

  • T_world_cam
  • T_world_obj

则:

text 复制代码
T_cam_obj = inv(T_world_cam) * T_world_obj

如果你已知:

  • T_cam_world
  • T_world_obj

则:

text 复制代码
T_cam_obj = T_cam_world * T_world_obj

如果你已知的是:

  • T_obj_cam

也就是相机在物体坐标系下的位姿,那么必须先取逆:

text 复制代码
T_cam_obj = inv(T_obj_cam)

8.3 团队内部建议

无论原始采集链路怎么设计,最终落盘前统一转换成:

  • T_cam_obj
  • cam_R_m2c
  • cam_t_m2c

不要把多种方向的 pose 混在同一项目里。


9. 如何验证一个 pose 是不是写对了

这是比"怎么算"更重要的工程步骤。

9.1 方法一:投影验证

最实用的方法是:

  1. 取物体模型点或包围盒角点
  2. R, t 把它们变换到相机坐标系
  3. 再用相机内参投影到 RGB 图像上
  4. 看投影轮廓是否与真实物体重合

如果投影整体漂移,常见原因是:

  • R, t 方向写反
  • 内参错
  • 模型单位错
  • cam_t_m2c 单位错

9.2 方法二:mask 一致性验证

如果有真实 mask,可以:

  1. 用模型和 pose 渲染一个二值 mask
  2. 和人工或自动生成的 mask 做重叠比较

如果 IoU 很差,说明:

  • pose 不准
  • mask 不准
  • 或者两者之一的坐标系 / 单位不一致

9.3 方法三:深度一致性验证

如果有深度图,可以:

  1. 根据 pose 渲染模型深度
  2. 与实测深度比较

这一步尤其能发现:

  • 平移尺度错误
  • 模型尺度错误

9.4 方法四:关键点验证

如果你已经有:

  • corners.txt
  • farthest*.txt

可以把这些关键点投影回图像,看它们是否落在合理位置。


10. 团队内部推荐的 pose 生产流程

为了降低后续训练和评估阶段的排障成本,建议团队按下面流程组织。

10.1 第一步:固定坐标系定义

在任何采集和标注开始前,先文档化:

  1. 模型坐标系定义
  2. 相机坐标系定义
  3. 深度单位
  4. 模型单位
  5. pose 最终输出方向

至少要明确写出:

  • 最终统一输出 T_cam_obj
  • cam_t_m2c 用毫米还是米
  • .ply 用毫米还是米

10.2 第二步:统一原始数据存储

原始采集建议至少保留:

text 复制代码
raw_capture/
├── rgb/
├── depth/
├── pose_raw/
├── camera/
└── metadata/

其中 pose_raw/ 存的是原始来源,例如:

  • 机械臂输出
  • 标定板求解结果
  • 人工标注结果

不要一开始就直接只保留转换后的 gt.yml

10.3 第三步:统一写一个 pose 标准化脚本

建议团队内部维护一个脚本,专门做:

  1. 单位转换
  2. 坐标系方向统一
  3. 世界系到相机系的变换
  4. 输出 cam_R_m2ccam_t_m2c

这个脚本应该是:

  • 可重复执行的
  • 可追踪版本的
  • 不要手工散改

10.4 第四步:可视化验收后再导出正式标注

不要只看数值。

必须至少做一次:

  1. 模型投影到 RGB
  2. 与实物对齐检查
  3. 随机抽帧人工复核

通过后再批量导出:

  • scene_gt.json
  • gt.yml

10.5 第五步:保留可追溯元信息

建议每批数据额外存:

  1. 相机标定时间
  2. 模型版本
  3. pose 生成方式
  4. 单位说明
  5. 转换脚本版本

否则后面一旦发现某一批数据错了,很难定位。


11. pose 在文件中的推荐存储方式

11.1 BOP 风格

推荐保存到:

text 复制代码
scene_gt.json
scene_camera.json

其中:

  • scene_gt.json 记录每帧每个实例的 cam_R_m2ccam_t_m2c
  • scene_camera.json 记录每帧的 cam_Kdepth_scale

11.2 LINEMOD / PVN3D 风格

推荐保存到:

text 复制代码
gt.yml
train.txt
test.txt

其中:

  • gt.yml 记录每帧的姿态和 obj_id
  • train.txt / test.txt 记录参与训练或测试的帧号

11.3 内部原始格式

如果团队正在建设自己的采集流程,建议同时保留一份更适合工程调试的内部格式,例如:

text 复制代码
pose_raw/000001.json

每个 JSON 至少包括:

  1. 原始 pose 来源
  2. 坐标系说明
  3. 单位说明
  4. 最终标准化后的 R, t

这样后续导出成 BOP / LINEMOD 时会更稳。


12. 团队内最容易出错的地方

12.1 把 camera -> model 当成了 model -> camera

这是第一大高频错误。

症状通常是:

  • 投影结果完全错位
  • 旋转看起来像对了,但平移很奇怪

12.2 .ply 单位和 cam_t_m2c 单位不一致

症状通常是:

  • 方向看起来对
  • 但投影大小严重不对
  • ADD / ADDS 很差

12.3 相机内参不匹配

症状通常是:

  • 中心位置差不多
  • 越靠图像边缘越偏

12.4 参考系链路求逆顺序错

在世界系、标定板系、相机系、物体系之间做矩阵拼接时,最容易出错的就是:

  • 先乘谁
  • 谁要取逆

团队内部必须养成习惯:

  • 每写一个公式,都明确它的左右两端坐标系

12.5 只保留最终 gt.yml,丢失原始 pose 来源

这样一旦后面发现错误,几乎无法追溯。


13. 团队内部建议的统一口径

为避免后续数据、训练、评估、可视化各说各话,建议统一下面这些口径。

  1. pose 最终统一保存为 model -> camera
  2. 团队内部统一使用 T_cam_obj 表示最终训练 / 评估所需姿态
  3. 所有标注和转换脚本都必须显式写清单位
  4. 原始采集结果与最终训练格式分开保存
  5. 任何一批新数据都必须做模型投影验收

14. 总结

在自定义数据集中,pose 不是"一个随手记下来的 3x4 矩阵",而是整个训练与评估链路里最核心的几何契约。

对当前 PVN3D 项目来说,必须始终坚持下面这条定义:

text 复制代码
X_cam = R * X_obj + t

也就是:

  • cam_R_m2c
  • cam_t_m2c

描述的是:

  • 物体模型坐标系中的点
  • 如何变换到当前相机坐标系

无论你的 pose 来源是:

  1. 人工交互式标注
  2. AprilTag / ArUco / 外部定位系统
  3. Blender / 仿真自动生成

最终都必须转换到这一定义,并经过:

  1. 单位统一
  2. 坐标系统一
  3. 投影验证
  4. 文件格式导出

只有这样,后续的:

  • 数据转换
  • PVN3D 训练
  • demo
  • eval

才能在同一套几何意义下正常工作。

相关推荐
不懒不懒2 小时前
《从仿射变换到实时手势识别:构建完整换脸与手势交互系统的全流程指南》
人工智能·opencv·计算机视觉
Fang_YuanAI2 小时前
AI正在重构电商行业
大数据·人工智能·ai·重构·aigc·教育电商·电商
小程故事多_802 小时前
破局 AI 编码乱象:SDD 规范驱动 + OpenSpec+SuperPowers 双框架,让 AI 写对每一行可追溯代码
开发语言·人工智能·aigc·ai编程
枫叶v.2 小时前
Prompt Engineering、Context Engineering、Harness Engineering:它们到底是什么关系呢
大数据·人工智能·prompt
杜子不疼.2 小时前
AI Agent 智能体开发入门:AutoGen 多智能体协作实战教程
java·人工智能·spring
财经汇报2 小时前
联易融的“反直觉“之年:供应链金融估值重构进行时
人工智能·金融
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2026-04-08)
人工智能·ai·大模型·github·ai教程
人道领域2 小时前
OpenClaw 源码泄露风波:一场由 “手滑” 引发的 AI 安全大地震
人工智能·安全·open claw
Mr.Cheng.2 小时前
ALPHAEDIT: NULL-SPACE CONSTRAINEDKNOWLEDGE EDITING FOR LANGUAGE MODELS
人工智能·语言模型·自然语言处理