一、二维坐标变换:平移、旋转、缩放

各位同学,咱们今天聊聊二维坐标变换。说实话,这玩意儿是仿真里最基础、也最绕不开的内容。我做了这么多年仿真,几乎每个项目都要跟它打交道。你想想看,不管是机械臂的运动模拟,还是图像处理里的像素操作,本质上都是在做坐标变换。

二维坐标变换,说白了就是三种操作:平移、旋转、缩放。别小看这三个词,它们组合起来能玩出花来。今天我就带大家把这三种变换彻底吃透。

1.1 平移变换

平移,顾名思义,就是把一个点从A位置挪到B位置。比如点P(x, y)想往右走3个单位,再往上走2个单位,那新坐标就是(x+3, y+2)。

用矩阵表示就是:

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

这里tx和ty就是x方向和y方向的平移量。为什么用3x3矩阵?嗯,这里要注意——这是为了把平移和后面的旋转、缩放统一成一种表示方式。我们管这叫齐次坐标。

我的小经验:刚开始学的时候,我也觉得齐次坐标多此一举。后来做项目时发现,用齐次坐标能把所有变换统一成矩阵乘法,代码写起来特别清爽。你试试看,不用齐次坐标的话,平移得单独写加法,旋转和缩放用乘法,代码里到处都是if-else,维护起来想哭。

1.2 旋转变换

旋转稍微复杂一点。绕原点逆时针旋转θ角度,公式是:

x' = x*cosθ - y*sinθ
y' = x*sinθ + y*cosθ

矩阵形式:

[x']   [cosθ  -sinθ  0] [x]
[y'] = [sinθ   cosθ  0] [y]
[1 ]   [0      0     1] [1]
我曾经踩过的坑:有一次做机器人运动学仿真,旋转角度算出来总是反的。查了半天才发现——我用的数学库是顺时针为正,而我自己习惯逆时针为正。所以啊,一定要搞清楚你的坐标系方向和旋转正方向。建议在代码开头就明确注释清楚。

旋转有个重要性质:它是正交变换。什么意思?就是旋转前后,图形的形状和大小都不变,只是朝向变了。这个性质在仿真里特别有用,比如做刚体运动模拟时,我们经常用旋转矩阵来保证物体不变形。

1.3 缩放变换

缩放就是放大或缩小。沿x轴缩放sx倍,沿y轴缩放sy倍:

[x']   [sx  0   0] [x]
[y'] = [0   sy  0] [y]
[1 ]   [0   0   1] [1]

如果sx = sy,就是均匀缩放,图形形状不变。如果sx ≠ sy,就是非均匀缩放,图形会被拉伸或压缩。

重点来了:缩放变换的参考点默认是原点。如果你想以某个点为中心缩放,需要先平移到原点,缩放完再平移回去。这个套路在仿真里太常见了,我几乎每个项目都会用到。

1.4 三种变换的统一表示

现在我们把三种变换放在一起看。用齐次坐标,它们都是3x3矩阵:

变换类型 矩阵形式 参数说明
平移 [1 0 tx; 0 1 ty; 0 0 1] tx, ty为平移量
旋转 [cosθ -sinθ 0; sinθ cosθ 0; 0 0 1] θ为旋转角度(逆时针)
缩放 [sx 0 0; 0 sy 0; 0 0 1] sx, sy为缩放因子

组合变换时,矩阵乘法的顺序很重要。我个人习惯用右乘的方式理解:先应用的变换放在右边,后应用的放在左边。比如先旋转再平移,变换矩阵就是 T * R。

1.5 实战:用Python实现二维变换

光说不练假把式。咱们写个简单的Python代码,把三种变换串起来:

import numpy as np

def translation(tx, ty):
    return np.array([
        [1, 0, tx],
        [0, 1, ty],
        [0, 0, 1]
    ])

def rotation(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([
        [c, -s, 0],
        [s,  c, 0],
        [0,  0, 1]
    ])

def scaling(sx, sy):
    return np.array([
        [sx, 0,  0],
        [0,  sy, 0],
        [0,  0,  1]
    ])

# 组合变换:先旋转45度,再平移(2,3)
theta = np.pi / 4
T = translation(2, 3)
R = rotation(theta)
M = T @ R  # 注意顺序:先R后T

# 对点(1,0)应用变换
p = np.array([1, 0, 1])
p_new = M @ p
print(f"变换后坐标: ({p_new[0]:.2f}, {p_new[1]:.2f})")
我建议:写仿真代码时,把变换矩阵的生成封装成函数。这样代码可读性高,也方便调试。我早期做项目时喜欢把所有矩阵运算写在一起,结果出bug找半天。后来改成函数式写法,清爽多了。

1.6 知识体系总览

为了让大家对本章内容有个整体认识,我画了张图:

二维坐标变换知识体系 平移变换 旋转变换 缩放变换 齐次坐标 + 3×3矩阵统一表示 组合变换:矩阵乘法(注意顺序!) 应用:机器人仿真 · 图像处理 · 游戏开发 · CAD系统

从这张图可以看得很清楚:平移、旋转、缩放是三种基本操作,通过齐次坐标统一成矩阵形式,然后通过矩阵乘法组合出任意复杂的变换。这就是整个二维坐标变换的核心逻辑。

1.7 避坑指南

最后,我把自己这些年踩过的坑总结一下:

  • 矩阵乘法顺序:先旋转再平移 和 先平移再旋转,结果完全不同。我建议用右乘顺序理解,代码里也按这个顺序写。
  • 角度单位:Python的math库用弧度,但很多工程图纸用角度。我习惯在函数入口统一转成弧度,避免混淆。
  • 浮点误差:多次变换累积后,坐标可能会有微小误差。做仿真时,我一般会定期对坐标做归一化处理。
  • 坐标系方向:屏幕坐标系y轴向下,数学坐标系y轴向上。做图像相关的仿真时,这个坑我至少踩过三次。

好了,二维坐标变换就讲到这里。内容不多,但都是干货。你把这些吃透了,后面学三维变换就会轻松很多。

可可精品资料库