一、二维平移变换:从矩阵到代码

各位同学,今天我们来聊聊图形学里最基础、也最容易被忽视的一个变换——平移。

说实话,我刚学图形学那会儿,觉得平移太简单了。不就是把点挪个位置吗?x加个dx,y加个dy,完事。但后来在项目中踩了坑才发现,事情没那么简单。

1.1 平移的数学本质

先看一个点 P(x, y)。要把它平移到新位置 P'(x', y'),平移向量为 (tx, ty)。

公式很简单:

x' = x + tx
y' = y + ty

嗯,这就是二维平移的全部数学原理。但问题来了——

为什么我们不能直接用矩阵乘法表示平移?

你想想看,旋转、缩放都可以写成 2×2 矩阵乘法的形式:

[x']   [a  b] [x]
[y'] = [c  d] [y]

但平移是加法,不是乘法。这就导致了一个尴尬的局面:

  • 旋转:矩阵乘法
  • 缩放:矩阵乘法
  • 平移:矩阵加法

操作不统一,组合变换时就非常麻烦。我记得第一次做复合变换时,代码写得又臭又长,就是因为平移没法和其他变换合并成一个矩阵。

1.2 齐次坐标:一个巧妙的 trick

那怎么办?

图形学的前辈们想了个办法——给二维点加一个维度。

把点 (x, y) 变成 (x, y, 1)。这就是齐次坐标。

为什么加个 1 就能解决问题?我们来看看:

[x']   [1  0  tx] [x]
[y'] = [0  1  ty] [y]
[1 ]   [0  0  1 ] [1]

展开计算:

x' = 1·x + 0·y + tx·1 = x + tx
y' = 0·x + 1·y + ty·1 = y + ty
1  = 0·x + 0·y + 1·1  = 1

结果完全一样!但形式上变成了矩阵乘法。

核心思想:齐次坐标用增加一个维度的方法,把加法伪装成了乘法。这样所有变换都可以统一用矩阵乘法表示。

1.3 齐次坐标的几何意义

你可能会问:为什么是 1,不是 2 或 3?

其实齐次坐标 (x, y, w) 对应的是二维空间中的点 (x/w, y/w)。当 w=1 时,就是普通的点。当 w=0 时,表示无穷远处的点——也就是方向向量。

我个人习惯把 w 理解为「缩放因子」。w=1 是标准状态,w=0 是方向,w≠1 时可以通过归一化转回标准状态。

小技巧:判断一个向量是点还是方向,看 w 分量。w=1 是点,w=0 是方向。平移对方向向量没影响,因为方向没有位置概念。

1.4 代码实现:Python + NumPy

理论说完了,上代码。我用 NumPy 实现一个完整的二维平移变换。

import numpy as np

def translation_matrix(tx, ty):
    """
    生成二维平移矩阵(齐次坐标形式)
    tx: x方向平移量
    ty: y方向平移量
    """
    return np.array([
        [1, 0, tx],
        [0, 1, ty],
        [0, 0, 1]
    ])

def apply_transform(points, matrix):
    """
    对点集应用变换
    points: 形状为 (N, 2) 的数组,每行是一个二维点
    matrix: 3x3 变换矩阵
    """
    # 转换为齐次坐标
    ones = np.ones((points.shape[0], 1))
    homo_points = np.hstack([points, ones])  # (N, 3)
    
    # 应用变换
    transformed = homo_points @ matrix.T  # 注意矩阵乘法顺序
    
    # 转回二维坐标
    return transformed[:, :2]

# 示例:平移一个三角形
triangle = np.array([
    [0, 0],
    [1, 0],
    [0.5, 1]
])

T = translation_matrix(2, 3)
triangle_translated = apply_transform(triangle, T)

print("原始三角形顶点:")
print(triangle)
print("\n平移后三角形顶点:")
print(triangle_translated)

运行结果:

原始三角形顶点:
[[0.  0. ]
 [1.  0. ]
 [0.5 1. ]]

平移后三角形顶点:
[[2. 3.]
 [3. 3.]
 [2.5 4.]]

每个点的 x 加了 2,y 加了 3,完美。

1.5 避坑指南

我曾经在项目里犯过一个低级错误——矩阵乘法的顺序搞反了。

NumPy 中,如果你用 matrix @ points,要求 matrix 的列数等于 points 的行数。但我们的 points 是 (N, 3),matrix 是 (3, 3),直接乘会报错。

正确的做法是 points @ matrix.T,或者把 points 转置成 (3, N) 再乘。

注意:NumPy 的 @ 运算符和 matmul 函数,对二维数组执行的是矩阵乘法。一定要搞清楚维度匹配关系,否则结果完全不对。

1.6 本章知识体系

下面这张图总结了二维平移变换的核心逻辑:

二维平移变换知识体系 问题:平移是加法 无法用2×2矩阵乘法表示 解决方案:齐次坐标 增加维度,加法变乘法 平移矩阵形式 [1 0 tx] [0 1 ty] [0 0 1 ] 齐次坐标表示 点 (x, y) → (x, y, 1) 方向 (dx, dy) → (dx, dy, 0) 统一用3×3矩阵表示所有变换

1.7 总结

二维平移变换看似简单,但引出了图形学中一个非常重要的概念——齐次坐标。

  • 平移的数学本质:加法运算,无法用 2×2 矩阵表示
  • 齐次坐标:给二维点加一个维度 w,w=1 表示点,w=0 表示方向
  • 平移矩阵:3×3 矩阵,左上角是单位矩阵,右上角是平移量
  • 代码实现:用 NumPy 操作 3×3 矩阵,注意乘法顺序

说实话,齐次坐标这个概念我当初学的时候也觉得有点绕。但用多了就会发现,它真的是图形学里最优雅的设计之一。后面讲旋转、缩放、复合变换时,你会越来越体会到它的好处。

好,这一章就到这里。代码部分建议你亲自跑一遍,改改参数看看效果。实践出真知嘛。

课后练习:实现一个函数,输入一个矩形(四个顶点)和平移向量,输出平移后的矩形坐标。试试用循环和向量化两种方式实现,感受一下 NumPy 向量化的威力。

可可精品资料库