社区计算机视觉课程文档

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),并通过将向量与矩阵预乘来应用变换 MM @ x),而 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 上更新