自动驾驶-nuscenes 3D 映射真实场景分析
请你从一个实际的场景来将上边整个概念串讲起来,如全局原点选哪里,怎么从全局坐标变为自车坐标,然后再到相机、像素,如一个自动驾驶车,从深圳南山腾讯总部,自驾开到深圳福田平安大厦,这个期间 全局原点在哪里,nuscene 又是怎么采集数据的,然后怎么转换从全局系到车身、相机、像素等,请从实际场景中解读,并对关键场景画真实场景图辅助解释。
下面我用一个真实可想象的场景,把之前所有概念完整串起来讲一遍。
场景设定
一辆采集车从深圳南山腾讯总部出发,沿深南大道自驾开到福田平安金融中心(全程约 12 公里)。
我们假设这辆车完全按照 NuScenes 的采集方式工作。
1. NuScenes 实际是怎么采集数据的?
NuScenes 不是把整个城市当成一个超大全局坐标系,而是把长途驾驶切成很多短片段(每个 scene / log 大约 20 秒,行驶 100~200 米)。
每个短片段都有自己独立的「局部全局坐标系」。
关键点:
从腾讯总部到平安大厦,会被切成大概 60~80 个独立的 scene。
每个 scene 的全局原点都是重新人为选定的,通常选在该 scene 起点附近。
2. 全局原点到底选在哪里?(以其中一个 scene 为例)
假设我们现在看其中一个 scene:车刚从腾讯总部东门出来,沿着深南大道往东开了大约 150 米。
这个 scene 的全局原点会选在:
- 离当前行驶区域很近的一个固定点(比如路口某个角、或者地图上一个方便的参考点)
- 目的是让这个 scene 里所有坐标数值都比较小(几十米到一两百米)

如果把整个深圳用一个原点(比如选在市民中心),那坐标会变成几万米,计算精度和可视化都会很麻烦。所以 NuScenes 选择「每个短 scene 一个本地原点」。
3. 完整变换链条(用这个真实 scene 串起来)
我们以这个 scene 里「前方 40 米处有一辆白色轿车」为例,完整走一遍从全局到像素的过程。
步骤 A:全局坐标系下的标注
白色轿车在本 scene 全局坐标系中的位置是:
[
\text{translation} = [42.5,\ 8.3,\ 0.4]\ \text{米}
]
(中心点坐标 + 尺寸 + 朝向四元数)
步骤 B:全局 → 车身(取逆)
此时采集车自己的 ego_pose 告诉你:
「我现在在全局坐标系里的位置和朝向」。
要把白色轿车的点从全局变到「以我自己车身为中心」的坐标系,必须对 ego_pose 取逆。

步骤 C:车身 → 相机(再次取逆)
相机安装在车顶或挡风玻璃后方,它相对车身有一个固定的外参(calibrated_sensor)。
官方给的是「相机在车身坐标系中的位置」,我们要把点从车身变到相机,所以还要再取一次逆。
步骤 D:相机 → 像素(内参 K 投影)
用相机内参矩阵 K 把相机坐标系下的 3D 点投影到图像平面,得到像素坐标 (u, v)。
最终在前视相机图像上,就能画出这个白色轿车的 3D 框。
4. 把整个从南山到福田的过程串起来看
- 采集阶段:车载 GPS/IMU + 6 个相机 + 1 个 LiDAR 全程录制。数据被切成很多 20 秒的 scene。
- 每个 scene 处理:
- 人为选一个靠近该段的本地原点 → 建立局部全局坐标系
- 用高精度定位把所有帧的 ego_pose 对齐到这个局部全局系
- 人工 / 半自动标注所有物体的 3D 框(都在局部全局系下)
- 使用时(你的代码):
- 读某个 scene 的标注(全局)
- 用该帧的 ego_pose 取逆 → 到车身
- 用相机 calib 取逆 → 到相机
- 用 K 投影 → 到像素
- 画在对应相机图像上做验证
即使车从腾讯开到了平安大厦,每个小 scene 的全局原点都是「就近选择」的,互不干扰。这样既保证了局部精度,又避免了超大坐标带来的数值问题。
总结一张总览图

这就是把所有概念放到真实深圳道路上的完整故事。
如果你想继续深入某一个具体环节(比如「某个 scene 的 ego_pose 具体长什么样」或「相机外参在车上的实际安装位置」),告诉我,我再针对那一块画更细的图。
全局坐标系和自车的坐标系
为什么全局坐标系 取逆后 就为自车的坐标系?这个逻辑是什么意思?比如自车 在全局坐标系的位置为 (12, 25, 0.6), 那么取逆的逻辑是什么,怎么发现要这么做?自车坐标系原点在车上,为什么还要这么搞,不是很明白。
下面用最直白的逻辑 + 你给的具体数字把这个困惑彻底讲清楚。
核心问题重述
你说:
自车在全局坐标系的位置是 (12, 25, 0.6),
自车坐标系的原点本来就在车上,
为什么还要对 ego_pose 取逆,才能得到「自车坐标系」?
这是很多人最卡的地方。我们一步步拆开。
1. 先分清两件事
- 自车坐标系的原点:永远钉在车上(后轴中心或 IMU 位置)。
- 标注数据给的位置:所有物体(其他车、行人)的坐标是写在全局坐标系里的。
所以问题变成:
「全局里的一个点,在『以我车子为原点』的坐标系里,应该是多少?」
这就是在做坐标变换,不是简单把原点挪一挪那么简单。
2. 用你给的数字举最简单的例子(先忽略旋转)
假设:
- 自车在全局的位置 ( \mathbf{t} = (12,\ 25,\ 0.6) )
- 前方有一辆目标车,它在全局的位置是 ( \mathbf{p}_{global} = (30,\ 25,\ 0.6) )
(两车在同一条水平线上,目标车在自车正东边 18 米处)
如果我们不做任何变换,直接用全局坐标,目标车的坐标是 (30, 25, 0.6)。
但自车坐标系的原点在自己车上,它「看到」目标车应该是相对自己的位置:(18, 0, 0)。
怎么算出来的?
[
\mathbf{p}{ego} = \mathbf{p}{global} - \mathbf{t} = (30-12,\ 25-25,\ 0.6-0.6) = (18,\ 0,\ 0)
]
这就是平移部分的逆变换:先减去自车在全局的位置。

3. 再加上旋转(真实情况更完整)
自车不可能永远正对着全局的 X 轴。它有朝向(四元数或旋转矩阵 R)。
egopose 完整描述的是:
[
\mathbf{p}{global} = R \cdot \mathbf{p}_{ego} + \mathbf{t}
]
(把「车身坐标系里的点」变到全局)
我们现在要反过来:已知 (\mathbf{p}{global}),求 (\mathbf{p}{ego})。
数学上对上面公式两边同时做逆操作:
[
\mathbf{p}{ego} = R^{T} \cdot (\mathbf{p}{global} - \mathbf{t})
]
这就是代码里写的:
points = points - translation # 减 t
points = R.T @ points # 乘 R 的转置(逆)
逻辑本质:
先把点「搬」到以自车位置为新原点,再把坐标系「转正」到自车自己的朝向。
4. 为什么必须这么做?用「人」来比喻
把自车想成你自己:
- 你站在房间里位置 (12, 25),面朝某个方向。
- 房间里有一把椅子,它的全局位置是固定的。
- 你想知道:「这把椅子在我的左边还是右边?离我多远?」
你不会直接用椅子的全局坐标,而是会:
- 用自己的位置去减(平移逆)
- 根据自己当前面朝的方向重新理解左右前后(旋转逆)
这就是「取逆」的全部逻辑。

5. 完整带数字的例子(包含旋转)
假设:
- 自车位置 ( \mathbf{t} = (12,\ 25,\ 0.6) )
- 自车朝向:绕 Z 轴旋转了 90°(车头朝全局 Y 轴正方向)
- 目标点全局坐标 ( \mathbf{p}_{global} = (12,\ 40,\ 0.6) )(在自车正北方 15 米)
旋转矩阵 R(90°):
[
R = \begin{bmatrix} 0 & -1 & 0 \ 1 & 0 & 0 \ 0 & 0 & 1 \end{bmatrix}
]
取逆后:
[
\mathbf{p}{ego} = R^{T} (\mathbf{p}{global} - \mathbf{t}) = R^{T} (0,\ 15,\ 0) = (15,\ 0,\ 0)
]
结果是 (15, 0, 0) → 正好在自车正前方 15 米。完全符合「车头朝北时,北方的点变成自己的前方」。
如果不取逆,直接用全局坐标,你永远得不到「相对自己」的正确位置。
6. 一句话总结逻辑
| 官方给的 ego_pose | 它的含义 | 我们真正需要的 | 所以怎么办 |
|---|---|---|---|
| ( T_{global \leftarrow ego} ) | 把车身坐标点 → 变成全局坐标点 | 把全局坐标点 → 变成车身坐标点 | 取逆 |
自车坐标系的原点确实在车上,但数据不是用车身坐标给的,而是用全局坐标给的。
要把「别人写在全局本子上的数字」翻译成「以我自己为原点的数字」,就必须对「我在全局的位姿」做逆变换。
这就是全部逻辑。
如果还觉得抽象,我可以再用你给的 (12, 25, 0.6) 配上一张带真实旋转的动态步骤图,你要吗?
四元数(Quaternion)是什么?
四元数是另一种表示旋转的方式,比旋转矩阵更紧凑、更稳定。

在代码里我们通常会立刻把它转成旋转矩阵再用:
from pyquaternion import Quaternion
q = Quaternion(rotation) # 四元数
R = q.rotation_matrix # 变成 3×3 旋转矩阵
下面用最直白的方式讲清楚这句话的意思,以及四元数和旋转矩阵 R到底是什么。
这个词在数学和物理里很常见,中文叫四元数。
简单来说,它是复数的一种扩展,由一个实部和三个虚部组成。如果你是在看 3D 游戏开发、计算机图形学或者机器人控制相关的资料,经常会遇到它,主要用来平滑地表示和计算三维空间里的旋转,能完美避开“万向节死锁”这种坑。
1. “它有朝向”是什么意思?
自车不只是停在某个位置 ((12, 25, 0.6)),它还面朝某个方向。
- 可能车头朝东
- 可能车头朝北
- 也可能斜着开
这个「面朝哪个方向」就叫做朝向(orientation / heading)。
在坐标变换里,光知道位置(平移)不够,还必须知道朝向(旋转),才能正确把全局点变成「相对我自己」的点。
2. 旋转矩阵 R 是什么?
旋转矩阵 R 是一个 (3 \times 3) 的数字表格,专门用来描述「怎么转」。
它的作用很简单:
把一个点绕某个轴旋转一定角度后,新的坐标是多少。
最常见的例子(绕 Z 轴旋转,也就是水平转向):
-
转 (0^\circ)(车头朝东,假设全局 X 是东):
[
R = \begin{bmatrix}
1 & 0 & 0 \
0 & 1 & 0 \
0 & 0 & 1
\end{bmatrix}
] -
转 (90^\circ)(车头朝北):
[
R = \begin{bmatrix}
0 & -1 & 0 \
1 & 0 & 0 \
0 & 0 & 1
\end{bmatrix}
]
代码里用的就是这个矩阵:
points = R.T @ points # 取逆就是转置

3. 四元数(Quaternion)是什么?
四元数是另一种表示旋转的方式,比旋转矩阵更紧凑、更稳定。
它由 4 个数字组成:
[
q = [w,\ x,\ y,\ z]
]
- (w):实部(和旋转角度有关)
- (x, y, z):虚部(和旋转轴有关)
NuScenes 里 ego_pose["rotation"] 存的就是这个四元数。
为什么用四元数而不是直接用角度?
- 避免万向节锁(Gimbal Lock)问题
- 插值更平滑(动画、轨迹平滑时很有用)
- 存储只需要 4 个数,而旋转矩阵要 9 个数
- 计算旋转时效率高、数值更稳定
在代码里我们通常会立刻把它转成旋转矩阵再用:
from pyquaternion import Quaternion
q = Quaternion(rotation) # 四元数
R = q.rotation_matrix # 变成 3×3 旋转矩阵
4. 四元数和旋转矩阵的关系
它们是同一种旋转的两种写法,可以互相转换。
| 表示方式 | 形式 | 优点 | 缺点 |
|---|---|---|---|
| 旋转矩阵 R | (3\times3) 矩阵 | 直观,直接矩阵乘法 | 占内存,易累积误差 |
| 四元数 q | 4 个数 ([w,x,y,z]) | 紧凑、稳定、好插值 | 不直观 |
NuScenes 选择存四元数,用的时候再转成矩阵。

5. 用一个完整小例子串起来
假设自车:
- 位置 ( t = (12, 25, 0.6) )
- 朝向:绕 Z 轴转了 (90^\circ)(车头朝北)
对应的四元数大约是:
[
q = [0.7071,\ 0,\ 0,\ 0.7071]
]
对应的旋转矩阵就是上面那个 (90^\circ) 的矩阵。
此时如果全局有一个点在自车正北方 15 米,经过:
- 减去 ( t )
- 乘以 ( R^T )
就会变成自车坐标系下的 ((15, 0, 0)) —— 正好在自己正前方。
一句话记住
- 朝向 = 车头指向哪里
- 旋转矩阵 R = 用 (3\times3) 数字表精确描述这个朝向
- 四元数 = 用 4 个数字更优雅地描述同一个朝向(NuScenes 实际存储的方式)
两者本质一样,只是表达形式不同。代码里看到 Quaternion(...).rotation_matrix 或 .T,就是在处理这个「朝向」。
需要我再用更简单的 2D 动画式步骤(只转平面)把四元数数字怎么对应到角度讲一遍吗?
旋转矩阵给什么是 3 x 3 的矩阵?
上边的旋转矩阵给什么是 3 x 3 的矩阵,四元数为什么是用 [w,x, y, z] 表示,怎么来的,请解释清楚
下面把两个问题讲透:为什么旋转矩阵一定是 3×3? 以及 四元数为什么是 [w, x, y, z],它到底是怎么来的?
一、为什么旋转矩阵是 3×3?
三维空间中的一个点,用三个数字表示:
[
\mathbf{p} = \begin{bmatrix} x \ y \ z \end{bmatrix}
]
旋转是一种线性变换:把一个 3 维向量变成另一个 3 维向量,而且要保持长度不变、角度不变。
任何把 3 维向量变成 3 维向量的线性变换,都可以用一个 3 行 3 列的矩阵来表示:
[
\mathbf{p}' = R \mathbf{p}
]
其中 ( R ) 必须是 ( 3 \times 3 )。
这就是最根本的原因:输入是 3 个数,输出也是 3 个数,所以矩阵必须是 3×3。
额外要求(保证它是“纯旋转”而不是拉伸或镜像):
- ( R^T R = I )(正交)
- ( \det(R) = 1 )(不翻转)

二、四元数为什么是 [w, x, y, z]?它是怎么来的?
1. 历史与数学来源
四元数是 1843 年爱尔兰数学家汉密尔顿(William Rowan Hamilton)发明的。
他原本想把复数从 2 维扩展到 3 维,但怎么也找不到合适的乘法规则。有一天他突然意识到:必须扩展到 4 维才行。
于是定义了四个单位:
[
1,\quad i,\quad j,\quad k
]
满足:
[
i^2 = j^2 = k^2 = ijk = -1
]
任何一个四元数就写成:
[
q = w + x\,i + y\,j + z\,k
]
也就是我们常写的 [w, x, y, z]。
2. 为什么能表示 3D 旋转?(最关键的推导)
任意一个 3D 旋转,都可以用「绕某个轴转某个角度」来描述(轴角表示):
- 旋转轴:单位向量 ( \mathbf{u} = (u_x, u_y, u_z) )
- 旋转角度:( \theta )
把这个轴角信息「塞」进四元数的方法是:
[
\begin{align}
w &= \cos\frac{\theta}{2} \
x &= u_x \sin\frac{\theta}{2} \
y &= u_y \sin\frac{\theta}{2} \
z &= u_z \sin\frac{\theta}{2}
\end{align}
]
所以四元数自然就变成了四个数 [w, x, y, z]。
为什么是 θ/2?
这是为了让两个四元数相乘时,角度能正确相加(半角公式),并且保证它是单位四元数(长度为 1)。

3. 直观理解四个数的含义
| 分量 | 含义 | 来源 |
|---|---|---|
| w | 与旋转角度相关 | (\cos(\theta/2)) |
| x | 旋转轴在 X 方向的分量 | (u_x \sin(\theta/2)) |
| y | 旋转轴在 Y 方向的分量 | (u_y \sin(\theta/2)) |
| z | 旋转轴在 Z 方向的分量 | (u_z \sin(\theta/2)) |
当 θ = 0(不转)时,q = [1, 0, 0, 0]
当绕 Z 轴转 90° 时,q ≈ [0.707, 0, 0, 0.707]
三、对比总结
| 表示方式 | 为什么是这个尺寸/形式 | 本质来源 |
|---|---|---|
| 旋转矩阵 | 3×3(因为要变换 3 维向量) | 线性代数中的线性变换 |
| 四元数 | 4 个数 [w,x,y,z] | 把「轴 + 角度」编码进 4 维复数 |
两者完全等价,可以互相转换。NuScenes 存四元数是因为更紧凑、数值更稳定;真正做矩阵乘法时再转成 3×3 旋转矩阵。
这就是它们「为什么是这个样子」的完整原因。
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)