PLY 模型、分割图、RGB 图、深度图之间的关系与坐标系变换详解

1. 写这篇文档的目的

很多人在刚接触 6D poseBOPLINEMODPVN3D 这类数据时,最容易混淆的点不是代码,而是下面这个问题:

一个 .ply 模型文件里明明只有几何形状,看起来既没有相机,也没有图像,更没有"参考世界"。

那它到底是怎么和 RGB 图像、depth 图像、mask 分割图联系起来的?

如果这个问题没有彻底想清楚,后面会连续出现这些误区:

  • 以为 .ply "没有坐标系"
  • 以为 cam_R_m2c / cam_t_m2c 是"相机在世界里的位姿"
  • 以为分割图是直接从 .ply 里长出来的
  • 以为 RGB 对齐了就代表 pose 正确
  • 以为深度图只是辅助可视化,不影响几何关系
  • 以为模型单位、深度单位、平移单位可以随便混

本文专门把这条链路完整讲清楚。


2. 先给结论

.ply 文件虽然通常只保存物体形状,但它里面的每个顶点坐标其实天然就定义在一个"模型坐标系"里。

也就是说:

  • .ply 不是"没有坐标系"
  • .ply 是"没有世界坐标系和相机坐标系"
  • 但它一定有自己的局部坐标系,也就是 model frame / object frame

这个模型坐标系中的点,通过一个 model -> camera 的刚体变换:

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

就可以被放到当前帧相机坐标系中。

然后再通过相机内参 K 投影到图像平面:

text 复制代码
[u, v, 1]^T ~ K * X_cam

一旦完成这两步:

  1. .ply 模型和当前帧相机建立了三维关系
  2. 模型和 RGB 图建立了二维投影关系
  3. 模型和深度图建立了三维深度一致性关系
  4. 模型和分割图建立了像素占据区域关系

所以,真正把它们联系在一起的核心不是 .ply 本身,而是:

  • 模型坐标系定义
  • 相机内参 K
  • cam_R_m2c
  • cam_t_m2c
  • 深度尺度定义

3. 你必须先分清的 4 个东西

3.1 .ply 模型文件

.ply 在这里最重要的信息通常是:

  • 顶点坐标 x, y, z
  • 可能有面片 face
  • 可能有法向、颜色等附加信息

但在 pose 问题里,最本质的是:

  • 每个顶点坐标都是在模型自己的局部坐标系里定义的

例如模型中有一个点:

text 复制代码
X_obj = [10, 20, 30]^T

这不是图像坐标,不是世界坐标,也不是相机坐标。

它表示:

  • 这个点相对于模型坐标系原点,位于 (10, 20, 30)

3.2 RGB 图像

RGB 图像是相机拍到的二维颜色图。

它本身只告诉你:

  • 每个像素位置 (u, v) 有什么颜色

RGB 图像并不直接告诉你:

  • 这个像素对应的三维点在哪
  • 物体的三维姿态是什么

RGB 和模型发生联系,是因为模型投影到了 RGB 平面上。

3.3 深度图

深度图是每个像素对应的深度测量值。

它通常表示:

  • 从相机出发,沿该像素视线方向,到物体表面的距离

但要注意:

  • 有的深度图单位是毫米
  • 有的是米
  • 有的是原始整数值,需要再乘 depth_scale

深度图本身仍然在"图像像素索引"上组织,但它比 RGB 多了一层三维信息。

3.4 分割图 mask

分割图通常是一个二维图像,告诉你:

  • 哪些像素属于某个物体
  • 哪些像素不属于它

分割图的本质是"像素集合",不是三维几何本体。

它可以来自:

  • 人工标注
  • 自动分割模型
  • 根据已知 pose 把 .ply 渲染到图像上得到可见区域

所以分割图不是从 .ply 直接"天然附带"的,而是通过图像空间中的映射关系得到的。


4. .ply 为什么说"有坐标系"

很多人说"PLY 没有参考系",这句话一半对,一半不对。

4.1 对的部分

.ply 文件通常不会告诉你:

  • 这个模型在世界里的位置
  • 这个模型面对哪台相机
  • 这个模型相对某一帧图像的姿态

也就是说,它通常不包含:

  • 世界坐标系定义
  • 当前帧相机位姿
  • 当前帧 R, t

4.2 不对的部分

只要 .ply 里有顶点坐标,它就一定隐含一个坐标系。

因为顶点必须相对于某个原点和某组轴来表达。

比如:

text 复制代码
vertex 1: (0, 0, 0)
vertex 2: (100, 0, 0)
vertex 3: (0, 50, 0)

这里已经隐含了:

  • 原点在哪里
  • x 轴正方向是哪边
  • y 轴正方向是哪边
  • z 轴正方向是哪边
  • 长度单位是什么

所以更准确的话应该是:

.ply 有模型局部坐标系,但通常没有和某一帧图像绑定的相机外参。`


5. 这条链路里真正存在的几个坐标系

5.1 模型坐标系 object/model frame

这是 .ply 顶点所在的坐标系。

特点:

  • 原点由建模或重建流程定义
  • 轴方向由建模软件或数据集约定定义
  • 所有模型顶点都在这个坐标系里
  • 关键点、中心点、包围盒也通常在这个坐标系中定义

记作:

text 复制代码
X_obj

5.2 相机坐标系 camera frame

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

在常见视觉约定中:

  • 原点在相机光心
  • +Z 指向镜头前方
  • X, Y 的方向由数据集约定决定

记作:

text 复制代码
X_cam

5.3 图像像素坐标系 image plane

这是二维图像坐标系。

记作:

text 复制代码
(u, v)

它和三维坐标系不同。

它只表示:

  • 像素列号
  • 像素行号

5.4 世界坐标系 world frame

有些采集系统里会有世界坐标系,比如:

  • 转台坐标系
  • 标定板坐标系
  • 机械臂基坐标系

但在单帧 PVN3D / BOP / LINEMOD 的最终监督里,世界坐标系不是必须的。

最终真正需要落盘的是:

text 复制代码
model -> camera

6. .ply、RGB、depth、mask 是如何被"绑"到一起的

真正的联系分三层。

6.1 第一层:模型到相机

给定:

  • 模型点 X_obj
  • 旋转矩阵 R
  • 平移向量 t

就有:

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

在 BOP/LINEMOD 里:

  • R = cam_R_m2c
  • t = cam_t_m2c
  • m2c = model to camera

这一步的意思是:

  • 把模型坐标系中的几何点,搬到当前相机坐标系下

这一步之后,模型已经和当前图像帧建立了三维关系。

6.2 第二层:相机到 RGB 图像

相机坐标中的点 X_cam = [x, y, z]^T 通过相机内参 K 投影到图像:

text 复制代码
u = fx * x / z + cx
v = fy * y / z + cy

矩阵形式:

text 复制代码
[u', v', w']^T = K * [x, y, z]^T
u = u' / w'
v = v' / w'

这样就能把模型上的每个三维点映射到 RGB 图上。

于是你可以:

  • 画轮廓
  • 画投影点
  • 画网格边
  • 画半透明实体叠加

这就是 pose_tuner 为什么能够把 .ply 叠加到 RGB 上。

6.3 第三层:相机到深度图

如果某个像素 (u, v) 上的深度图值是 d,那么结合相机内参可反投影为相机坐标系中的三维点:

text 复制代码
x = (u - cx) * d / fx
y = (v - cy) * d / fy
z = d

于是:

  • 深度图可以变成相机坐标系点云
  • 渲染后的模型深度也可以变成一个"理论深度图"

然后比较:

  • 实测深度
  • 模型渲染深度

就能检查 tz 是否正确、表面是否贴合、遮挡是否合理。

6.4 第四层:模型到分割图

一旦模型已经通过 R, t, K 投影到图像上,你就能知道:

  • 模型在图像里占据哪些像素

于是分割图就可以由这些像素生成。

也就是说,分割图来自"模型投影后的像素区域",而不是来自 .ply 自己。


7. 为什么说分割图和 pose 有强关系

分割图可以有两种来源。

7.1 手工或网络分割得到的 mask

这种情况下:

  • mask 先存在
  • pose 需要去解释这个 mask 对应的是哪个三维姿态

7.2 已知 pose 后由模型渲染得到的 mask

这种情况下:

  • 先有 .ply + R + t + K
  • 再把模型投影/渲染到图像平面
  • 得到 silhouette / visible mask

所以在标准数据集里,mask 常常不是独立真理,而是和 pose 紧密耦合的产物。

换句话说:

  • .ply 决定物体长什么样
  • R, t 决定它在相机前怎么摆
  • K 决定它落到图像的哪里
  • mask 是这个结果在二维像素上的占据集合

8. 一张图像帧里,这四类数据分别扮演什么角色

设当前帧有如下数据:

  • model.ply
  • rgb/000123.png
  • depth/000123.png
  • mask/000123_000000.png
  • scene_camera.json
  • scene_gt.json

那么它们的角色是:

8.1 model.ply

定义:

  • 物体在模型坐标系中的几何形状

8.2 scene_gt.json

定义:

  • 当前帧这个物体实例从 model -> camera 的外参
  • cam_R_m2ccam_t_m2c

8.3 scene_camera.json

定义:

  • 当前相机内参 cam_K
  • 可能还包含深度尺度信息

8.4 rgb

提供:

  • 二维颜色观测

8.5 depth

提供:

  • 每个像素的几何距离观测

8.6 mask

提供:

  • 哪些像素属于该物体

它们一起构成一条完整链路:

text 复制代码
PLY 顶点 --(R,t)--> 相机坐标点 --(K)--> RGB 像素
                              \
                               \--> 渲染深度 / 占据区域 / mask

9. 一个完整的数值例子

假设 .ply 中某个点为:

text 复制代码
X_obj = [10, 0, 0]^T   (单位: mm)

当前帧标注为:

text 复制代码
R = I
t = [0, 0, 700]^T   (单位: mm)

则该点在相机坐标系中:

text 复制代码
X_cam = [10, 0, 700]^T

如果相机内参为:

text 复制代码
fx = 572.4
fy = 573.6
cx = 325.3
cy = 242.0

则投影像素约为:

text 复制代码
u = 572.4 * 10 / 700 + 325.3 ≈ 333.48
v = 573.6 * 0 / 700 + 242.0 = 242.0

这说明:

  • 模型上的这个点会落到 RGB 图上的 (333.48, 242.0) 附近

如果该位置的深度图读数也是接近 700 mm,说明:

  • 几何关系是自洽的

如果这个点落在 mask 内部,也说明:

  • 该点投影确实处于物体像素区域

10. 为什么 .ply 只有形状也足够做 6D pose

因为 6D pose 问题的本质不是"模型自己会不会说话",而是:

已知一个物体的三维几何形状,求它在当前相机坐标系中的刚体变换。

.ply 提供了三维形状后,剩下的全部联系都来自:

  • 相机成像模型
  • 位姿标注或估计
  • 深度或颜色观测

所以在很多 pose 数据集里,.ply 只需要提供:

  • 顶点
  • 面片
  • 单位
  • 模型坐标系定义

就足够了。


11. "分割模型文件"和 .ply 的关系

你提到"分割模型文件",这个词在实际工程里可能有两种意思。

11.1 指每个物体一个独立的 .ply

例如:

  • obj_000001.ply
  • obj_000002.ply

这种情况下,"分割"只是指:

  • 每个对象的几何模型单独存储

它和图像分割不是一回事。

11.2 指分割结果文件 mask

例如:

  • mask_visib/000000_000000.png
  • mask/000000_000000.png

这种情况下,它是图像空间中的物体像素区域,不是 3D 模型本体。

所以一定要把这两类"分割"分开:

  • 3D 模型文件分对象存放
  • 2D 图像分割 mask 表示像素归属

12. 为什么 RGB 对了,pose 仍然可能不对

这是非常常见的坑。

12.1 只看轮廓,忽略深度

如果只看 RGB 叠加,很多时候:

  • tx, ty 看起来差不多
  • tz 实际偏了很多

因为:

  • 小幅度前后移动,二维轮廓变化可能不大
  • 但深度图和三维位置会明显不一致

12.2 对称物体

对于对称物体:

  • 不同旋转可能在 RGB 上看起来很像
  • 但在坐标系定义上是不同 pose

12.3 单位不一致

如果:

  • .ply 是毫米
  • cam_t_m2c 你当成米

那模型会整体缩放错 1000 倍,投影和深度都会出问题。


13. 为什么深度图特别重要

深度图是把二维图像和三维几何强行绑在一起的关键中介。

RGB 只能给你:

  • 外观
  • 纹理
  • 轮廓

深度还能给你:

  • 点到相机的距离
  • 表面是否贴合
  • 遮挡关系是否合理
  • tz 是否错误

所以在 pose_tuner 这种工具里,深度窗口的意义不是"好看",而是:

  • 辅助验证 pose 与几何是否真实一致

14. BOP / LINEMOD / PVN3D 里常见的真实关系

在这类项目里,通常会看到:

14.1 模型

text 复制代码
lm_models/models/obj_000001.ply

表示:

  • 物体 1 的三维模型

14.2 当前帧相机参数

json 复制代码
{
  "cam_K": [fx, 0, cx, 0, fy, cy, 0, 0, 1]
}

表示:

  • 当前帧的内参

14.3 当前帧 pose

json 复制代码
{
  "cam_R_m2c": [...],
  "cam_t_m2c": [...]
}

表示:

  • 模型如何放到当前相机坐标系

14.4 当前帧 RGB / depth / mask

表示:

  • 当前这帧真实观测到的像素、距离、分割区域

它们不是彼此孤立的文件,而是一组共同描述"同一时刻同一物体实例"的观测和几何关系。


15. 常见错误理解

15.1 错误一:PLY 没坐标系

正确说法:

  • PLY 有模型局部坐标系
  • 只是通常没有写明和当前相机的外参关系

15.2 错误二:cam_R_m2c 是相机姿态

正确说法:

  • 它是模型到相机的变换
  • 不是"相机在世界里怎么放"

15.3 错误三:mask 和 pose 无关

正确说法:

  • 很多 mask 恰恰是由 pose 渲染或约束得到的

15.4 错误四:RGB 对齐就够了

正确说法:

  • 还要检查深度一致性和单位一致性

15.5 错误五:深度图和 cam_t_m2c 单位可以不一致

正确说法:

  • 只要单位不一致,反投影和渲染深度就会系统性错位

16. 在工程里应当如何自检

拿到一套新数据时,建议按下面顺序检查。

16.1 检查模型坐标系

确认:

  • 原点定义
  • 轴方向
  • 长度单位

16.2 检查 pose 定义

确认:

  • model -> camera
  • 不是 camera -> model

16.3 检查内参

确认:

  • fx, fy, cx, cy 对应当前分辨率

16.4 检查深度尺度

确认:

  • 深度图原始值单位
  • 是否需要乘 depth_scale

16.5 检查投影效果

确认:

  • 模型轮廓是否落在物体上
  • 深度是否与真实表面贴合
  • mask 是否覆盖合理

17. 用一句最简洁的话重新概括

如果只记一句话,就记这个:

.ply 提供的是"物体在模型坐标系中的三维形状";
cam_R_m2ccam_t_m2c 把这个形状放到当前相机坐标系;
K 把它投影到 RGB 图像;

深度图验证它在三维上的距离是否正确;

分割图表示它在二维像素上占据哪些区域。`


18. 最后给一个统一公式链

设模型点为:

text 复制代码
X_obj

先变换到相机坐标系:

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

再投影到图像平面:

text 复制代码
[u', v', w']^T = K * X_cam
u = u' / w'
v = v' / w'

如果同时有深度值 d(u, v),可以反投影:

text 复制代码
x = (u - cx) * d / fx
y = (v - cy) * d / fy
z = d

因此这四类数据的关系是:

text 复制代码
PLY(模型形状)
  -> 通过 R,t 进入相机坐标系
  -> 通过 K 落到 RGB 像素
  -> 通过深度比较验证三维距离
  -> 通过渲染占据区域得到 mask / segmentation

19. 结合当前仓库应如何理解

对当前仓库来说,最重要的工程约定是:

  • .ply 提供模型几何
  • cam_R_m2c / cam_t_m2c 表示 model -> camera
  • RGB 用来看投影对齐
  • 深度用来看几何贴合
  • mask 用来限定物体像素区域

因此后续无论你在做:

  • pose_tuner
  • BOP 数据转换
  • PVN3D 训练数据整理
  • 真实采集数据标注

都必须守住同一个原则:

不要把"模型本身的局部坐标系"与"当前帧相机下的姿态"混为一谈。

这两者一旦混了,后续所有投影、深度、mask、训练监督都会一起错。

相关推荐
2601_950760792 小时前
IFN-γ蛋白在肿瘤免疫中的双重作用机制研究
人工智能
乱世刀疤2 小时前
ubuntu24上安装openclaw后配置钉钉通道
人工智能·openclaw
gaozhiyong08132 小时前
2026年DeepSeek-V4官网VS Gemini 3.1 pro 官网硬核技术拆解:开源模型的性价比革命
人工智能
冬至喵喵2 小时前
Agent Harness: 一套让 AI Agent 能够驾驭和控制 GUI 软件的适配层
人工智能
踩着两条虫2 小时前
AI驱动的 Vue3应用开发平台深入探究(十五):扩展与定制之自定义设置器与属性编辑器
前端·vue.js·人工智能·低代码·系统架构·编辑器
Surmon8 小时前
彻底搞懂大模型 Temperature、Top-p、Top-k 的区别!
前端·人工智能
见行AGV机器人10 小时前
无人机脉动线中的AGV小车
人工智能·无人机·agv·非标定制agv
廋到被风吹走10 小时前
【AI】从 OpenAI Codex 到 GitHub Copilot:AI 编程助手的技术演进脉络
人工智能·github·copilot
newsxun10 小时前
DHA之后,大脑营养进入GPC时代?
人工智能