JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

旋转矩阵:把转动写下来

一个 3×3 的数字方阵如何精确地表示朝向,以及为什么它的各列就是旋转后的坐标轴。

为什么朝向需要的是数字,而不是文字

机器人必须对「自己的手指向哪边」有一个前后一致的说法。「稍微往左偏一点」对人来说够用了,但计算机需要一个能存储、能比较、能做乘法的东西。这个东西就是旋转矩阵:一小块大小固定的数字,精确地钉住朝向,既不含糊,也不留下任何猜测的余地。

回想前面几级讲过的设定:每一个测量都活在某个坐标系里,而机器人会同时操控好几个坐标系——一个钉在地面上的世界坐标系,还有一个随着运动部件移动的本体坐标系。旋转矩阵就是对「一个坐标系相对于另一个坐标系转了多少」的精确表述。一旦有了它,你就能回答这样的问题:「如果相机看到杯子在那边,那么对夹爪来说它在哪里?」

诀窍:每一列就是一根转过去的坐标轴

整个思想用一句话就能讲完。拿出本体坐标系的三根单位箭头——也就是画在运动部件上的小小的 x、y、z 轴——问每一根在转动之后指向哪里,并用世界坐标系的数字写出来。把这三个答案并排摆成三列,你就构造出了旋转矩阵。第一列是本体的 x 轴落到的地方,第二列是 y 轴落到的地方,第三列是 z 轴落到的地方。

这正是为什么矩阵是 3×3 的,而不是比如说单单一个角度。在平面 2D 里,一个数字(转角)就够了。在 3D 里你可以绕三个互相独立的方向转动,于是你需要记下这三根轴最终都落到了哪里——三列,每列三个数字。这个方阵并不是随意的记账;它实实在在就是转过去的那几根坐标轴的一张快照。

做乘法:旋转一个点,串接多次转动

回报在于:这个矩阵通过普通的乘法就能干真正的活。把任意一个点写成位置向量,用矩阵去乘它,出来的就是这同一个点旋转之后的样子。你不必每次都搬出三角函数——那九个数字已经把转动编码好了,而「矩阵乘以向量」无非是按合适的比例把各列混合起来。计算机每秒能做几百万次这样的运算,全程根本不用去想角度。

更妙的是,转动可以通过矩阵相乘来串接。先转 A,再转 B,那么合起来的转动就是 B 乘以 A 得到的那一个矩阵。于是一条有许多关节、每个关节各添一份扭转的长机械臂,就坍缩成一个只需算一次的乘积。这正是你将处处依赖的「变换串接」思想——不过要注意顺序很重要:B 乘 A 一般不等于 A 乘 B,因为「先转再倾」并不等于「先倾再转」。

2D rotation by angle t (the simplest case):

    [ cos t   -sin t ]
    [ sin t    cos t ]

Column 1 = where the x-axis lands = (cos t, sin t)
Column 2 = where the y-axis lands = (-sin t, cos t)

Rotate a point p = (px, py):

    new_px = cos t * px  -  sin t * py
    new_py = sin t * px  +  cos t * py
由两根转过去的坐标轴构成的 2D 旋转矩阵;3D 是同一个思想,再加上第三列。

SO(3):所有「干净」旋转构成的家族

并不是每一个 3×3 方阵都是合法的旋转。真正的旋转从不拉伸、压扁或镜像空间——它只把空间转一转。所有通过这道检验的矩阵,有一个名字:特殊正交群 SO(3)。可以把它想成「诚实旋转」的会员俱乐部;前面那张「三列即三轴」的图,正是会员资格的样子。

两条简单的性质定义了这个俱乐部。第一,正交性:三列都是单位长度,且彼此互成直角——这无非是说「转过去的几根轴依然是一套干净、互相垂直的坐标轴」。第二,行列式等于 +1,这就禁止了那种偷偷把右手变成左手的镜像翻转。两者合在一起,保证这个矩阵是一次纯粹的转动,别无其他。

正交性还白送你一个好处:要撤销一次旋转,你只需把矩阵沿对角线翻折(取它的转置),而不必去做昂贵的求逆运算。于是「杯子对夹爪在哪」和「夹爪对杯子在哪」之间,只隔着一步廉价的运算。这正是机器人代码即便要花九个数字、也愿意用旋转矩阵来存储朝向的现实理由。