3. 二维平移变换:平移变换的数学原理、平移矩阵、Python实现平移变换
好,咱们今天聊聊二维图形里最基础、也最直观的一个变换——平移。
说白了,平移就是让一个点或者一个图形,在平面上「挪个地方」。你想想看,把一个正方形从屏幕左边拖到右边,这就是平移。听起来简单吧?但背后的数学原理,以及怎么用代码实现,还是有不少细节值得琢磨的。
3.1 平移变换的数学原理
先从一个点说起。假设平面上有一个点 P,它的坐标是 (x, y)。我想把它往右移动 tx 个单位,往上移动 ty 个单位。那移动后的新坐标 (x', y') 是多少?
这个太直观了:
x' = x + tx
y' = y + ty
嗯,就这么简单。tx 和 ty 就是平移的量。如果 tx 是正数,就往右移;负数就往左移。ty 同理,正数往上,负数往下。
但问题来了——我们后面要学旋转、缩放、错切这些变换。如果每个变换都单独写一套公式,代码会变得很乱。有没有一种统一的方式,能把所有变换都表达成「矩阵乘法」?
有。这就是引入齐次坐标的原因。
核心思想:把二维点 (x, y) 扩展成三维向量 (x, y, 1)。这样平移就能用矩阵乘法表示了。
为什么非要加一个 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
你看,结果完全一致。但形式上,我们统一成了矩阵乘法。后面学旋转、缩放时,你会发现这个「加一维」的技巧有多好用。
3.2 平移矩阵
我们把上面那个 3x3 的矩阵叫做平移矩阵,记作 T(tx, ty):
T(tx, ty) =
[1 0 tx]
[0 1 ty]
[0 0 1]
这个矩阵有几个特点:
- 左上角 2x2 是单位矩阵,表示「不旋转、不缩放」
- 第三列前两行是平移量 tx 和 ty
- 第三行永远是 [0, 0, 1],这是齐次坐标的「保底」行
小技巧:如果你想把一个图形先平移再平移,连续两次平移的结果,等于把平移量相加。即:T(tx1, ty1) * T(tx2, ty2) = T(tx1+tx2, ty1+ty2)。这个性质在实际编程中很常用。
另外要注意,矩阵乘法不满足交换律。但平移矩阵之间是交换的——先右移再上移,和先上移再右移,结果一样。这算是平移的一个「特权」吧。旋转和缩放可没这么友好。
3.3 Python实现平移变换
好,理论说完了,咱们动手写代码。我个人习惯用 NumPy 来处理矩阵运算,配合 Matplotlib 来可视化。
先定义一个平移矩阵的生成函数:
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 transform_points(points, matrix):
"""
对点集应用变换矩阵
points: 形状为 (N, 2) 的数组,每行是一个点的 (x, y)
matrix: 3x3 变换矩阵
返回: 变换后的点集 (N, 2)
"""
# 转为齐次坐标:加一列 1
ones = np.ones((points.shape[0], 1))
homogeneous = np.hstack([points, ones]) # (N, 3)
# 应用变换
transformed = homogeneous @ matrix.T # (N, 3)
# 转回二维坐标:去掉最后一列
return transformed[:, :2]
我曾经踩过的一个坑:这里用的是 @ matrix.T 而不是 matrix @ homogeneous.T。因为我们的点是按行排列的,每行一个点。如果搞反了维度,会得到形状错误的结果。嗯,这个细节调试了我半小时。
来,我们画个三角形,把它平移一下看看效果:
import matplotlib.pyplot as plt
# 定义一个三角形(三个顶点)
triangle = np.array([
[0, 0],
[2, 0],
[1, 1.5]
])
# 生成平移矩阵:右移3,上移2
T = translation_matrix(3, 2)
# 应用变换
triangle_moved = transform_points(triangle, T)
# 可视化
plt.figure(figsize=(6, 6))
# 画原始三角形
plt.fill(triangle[:, 0], triangle[:, 1],
alpha=0.5, label='原始', color='skyblue')
# 画平移后的三角形
plt.fill(triangle_moved[:, 0], triangle_moved[:, 1],
alpha=0.5, label='平移后', color='salmon')
plt.axhline(0, color='gray', linewidth=0.5)
plt.axvline(0, color='gray', linewidth=0.5)
plt.grid(True, linestyle='--', alpha=0.3)
plt.axis('equal')
plt.legend()
plt.title('二维平移变换示例')
plt.show()
运行这段代码,你会看到两个三角形——蓝色的在原点附近,红色的跑到了右上方。形状完全没变,只是位置变了。这就是平移的本质:保持形状和朝向,只改变位置。
3.4 知识体系总览
为了让你对整个平移变换有个全局认识,我画了一张结构图:
这张图把平移变换的三个核心模块串起来了。从左到右:数学公式是基础,矩阵是表达形式,Python代码是落地工具。三者缺一不可。
3.5 实际应用中的注意事项
最后,分享几个我在项目中积累的经验:
- 坐标系方向:屏幕坐标系和数学坐标系 y 轴是反的。在屏幕上,y 正方向朝下。所以「上移」在屏幕上是减小 y 值。写代码时一定要确认你用的库是什么坐标系。
- 浮点精度:多次平移后,坐标可能会有微小误差。比如平移 0.1 十次,理论上应该回到原位,但实际可能是 0.9999999。必要时做四舍五入。
- 性能优化:如果你要对成千上万个点做平移,别用循环。用 NumPy 的向量化操作,一次矩阵乘法搞定。我见过有人用 for 循环平移 10 万个点,跑了 3 秒——改成矩阵乘法后只要 0.01 秒。
一个小练习:试着把上面的三角形先右移 2,再上移 3,然后左移 1。看看最终位置在哪里。再用矩阵乘法验证一下:T(-1, 0) * T(0, 3) * T(2, 0) 的结果矩阵是什么?
好了,平移变换就讲到这里。它虽然简单,但却是理解后面所有变换的基石。你想想看,如果没有齐次坐标这个「升维」的技巧,我们后面要讲的旋转、缩放、复合变换,都会变得非常别扭。所以,请务必把平移矩阵的推导和代码实现都亲手过一遍。