社区计算机视觉课程文档

3D 数据线性代数基础

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

3D 数据线性代数基础

坐标系

大多数三维数据由物体组成,例如在空间中具有确定位置的点,通常用它们的三个笛卡尔坐标表示[X,Y,Z][X, Y, Z].

Axis handedness

然而,各种系统对于这个坐标系有不同的约定。最重要的区别是手性,即 X、Y 和 Z 轴的相对方向。记住这种差异的最简单方法是将中指向内弯曲,使你的拇指、食指和中指大致互相垂直。在你的左手上,你的拇指 (X)、食指 (Y) 和中指 (Z) 构成一个左手坐标系。类似地,你右手的手指构成一个右手坐标系。

在数学和物理学中,通常使用右手坐标系。然而,在计算机图形学中,不同的库和环境有不同的约定。值得注意的是,Blender、Pytorch3d 和 OpenGL(主要)使用右手坐标系,而 DirectX 使用左手坐标系。在这里,我们将遵循 Blender 和 NerfStudio,使用右手约定。

变换

能够旋转、缩放和平移空间中的这些坐标是很有用的。例如,如果一个物体正在移动,或者如果我们想将这些坐标从相对于某些固定轴集的世界坐标更改为相对于我们相机的坐标。

这些变换可以用矩阵表示。在这里,我们将使用 @ 来表示矩阵乘法。为了能够以一致的方式表示平移、旋转和缩放,我们采用三维坐标[x,y,z][x,y,z],并添加一个额外的坐标w=1w=1。这些被称为齐次坐标 - 更一般地,ww可以取任何值,并且四维线上的所有点[wx,wy,wz,w][wx, wy, wz, w]对应于相同的点[x,y,z][x,y,z]在三维空间中。然而,在这里,ww将始终为 1。

诸如 Pytorch3d 之类的库提供了用于生成和操作变换的各种功能。

另一个需要注意的约定 - OpenGL 将位置视为列向量 x(形状为 4x1),并通过将向量左乘矩阵 (M @ x) 来应用变换 M,而 DirectX 和 Pytorch3d 将位置视为形状为 (1x4) 的行向量,并通过将向量右乘矩阵 (x @ M) 来应用变换。为了在两种约定之间转换,我们需要取矩阵 M.T 的转置。我们将在一些代码片段中展示一个立方体如何在不同的变换矩阵下变换。对于这些代码片段,我们将使用 OpenGL 约定。

平移

平移,将空间中的所有点沿相同的距离和方向移动,可以表示为T=(100tx010ty001tz0001)T = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix}

其中t=[tx,ty,tz]t = [t_x,t_y,t_z]是平移所有点的方向向量。

为了亲自尝试平移,我们首先编写一个小助手函数来可视化立方体

import numpy as np
import matplotlib.pyplot as plt


def plot_cube(ax, cube, label, color="black"):
    ax.scatter3D(cube[0, :], cube[1, :], cube[2, :], label=label, color=color)
    lines = [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [4, 5],
        [5, 6],
        [6, 7],
        [7, 4],
        [0, 4],
        [1, 5],
        [2, 6],
        [3, 7],
    ]
    for line in lines:
        ax.plot3D(cube[0, line], cube[1, line], cube[2, line], color=color)
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.legend()
    ax.set_xlim([-2, 2])
    ax.set_ylim([-2, 2])
    ax.set_zlim([-2, 2])

现在,我们可以创建一个立方体,并将其左乘一个平移矩阵

# define 8 corners of our cube with coordinates (x,y,z,w) and w is always 1 in our case
cube = np.array(
    [
        [-1, -1, -1, 1],
        [1, -1, -1, 1],
        [1, 1, -1, 1],
        [-1, 1, -1, 1],
        [-1, -1, 1, 1],
        [1, -1, 1, 1],
        [1, 1, 1, 1],
        [-1, 1, 1, 1],
    ]
)

# translate to follow OpenGL notation
cube = cube.T

# set up figure
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

# plot original cube
plot_cube(ax, cube, label="Original", color="blue")

# translation matrix (shift 1 in positive x and 1 in positive y-axis)
translation_matrix = np.array([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]])

# translation
translated_cube = translation_matrix @ cube
plot_cube(ax, translated_cube, label="Translated", color="red")

输出应如下所示

output_translation

缩放

缩放是统一增大或减小物体大小的过程。缩放变换由一个矩阵表示,该矩阵将每个坐标乘以一个缩放因子。缩放矩阵由下式给出S=(sx0000sy0000sz00001)S = \begin{pmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

让我们尝试以下示例,将我们的立方体沿 X 轴缩放 2 倍,沿 Y 轴缩放 0.5 倍。

# set up figure
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

# plot original cube
plot_cube(ax, cube, label="Original", color="blue")

# scaling matrix (scale by 2 along x-axis and by 0.5 along y-axis)
scaling_matrix = np.array([[2, 0, 0, 0], [0, 0.5, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])


scaled_cube = scaling_matrix @ cube

plot_cube(ax, scaled_cube, label="Scaled", color="green")

输出应如下所示

output_scaling

旋转

绕轴旋转是另一种常用的变换。有许多不同的旋转表示方法,包括欧拉角和四元数,它们在某些应用中非常有用。同样,诸如 Pytorch3d 之类的库包含了用于执行旋转的各种功能。然而,作为一个简单的例子,我们将只展示如何构造绕三个轴中每个轴的旋转。

  • 绕 X 轴旋转Rx(α)=(10000cosαsinα00sinαcosα00001) R_x(\alpha) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \alpha & -\sin \alpha & 0 \\ 0 & \sin \alpha & \cos \alpha & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

围绕 X 轴正向旋转 20 度的简单示例在下面给出

# set up figure
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

# plot original cube
plot_cube(ax, cube, label="Original", color="blue")

# rotation matrix: +20 deg around x-axis
angle = 20 * np.pi / 180
rotation_matrix = np.array(
    [
        [1, 0, 0, 0],
        [0, np.cos(angle), -np.sin(angle), 0],
        [0, np.sin(angle), np.cos(angle), 0],
        [0, 0, 0, 1],
    ]
)


rotated_cube = rotation_matrix @ cube

plot_cube(ax, rotated_cube, label="Rotated", color="orange")

输出应如下所示

output_rotation
  • 绕 Y 轴旋转Ry(β)=(cosβ0sinβ00100sinβ0cosβ00001) R_y(\beta) = \begin{pmatrix} \cos \beta & 0 & \sin \beta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \beta & 0 & \cos \beta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

我们确信您可以使用上面的示例代码片段,并弄清楚如何实现绕 Y 轴的旋转。😎😎

  • 绕 Z 轴旋转Rz(β)=(cosβsinβ00sinβcosβ0000100001) R_z(\beta) = \begin{pmatrix} \cos \beta & -\sin \beta & 0 & 0 \\ \sin \beta & \cos \beta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

同样,您可以使用最后一个代码片段并实现绕 Z 轴的旋转吗❓

请注意,标准约定是,当旋转轴指向观察者时,正旋转角对应于逆时针旋转。另请注意,在大多数库中,余弦函数要求角度以弧度为单位。 要将角度从度转换为弧度,请乘以pi/180 pi/180.

组合变换

多个变换可以通过将它们的矩阵相乘来组合。 请注意,矩阵相乘的顺序很重要 - 矩阵从右向左应用。 要创建一个按顺序应用变换 P、Q 和 R 的矩阵,复合变换由下式给出X=R@Q@P X = R @ Q @ P.

如果我们想在一个操作中首先进行平移,然后进行旋转,然后再进行我们在上面所做的缩放,它看起来如下所示

# set up figure
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

# plot original cube
plot_cube(ax, cube, label="Original", color="blue")

# combination of transforms
combination_transform = rotation_matrix.dot(scaling_matrix.dot(translation_matrix))
final_result = combination_transform.dot(cube)
plot_cube(ax, final_result, label="Combined", color="violet")

输出应如下所示。

output_combined
< > 在 GitHub 上更新