矩阵乘法即复合
机器人是一串坐标系:世界、底盘、云台、相机。从一个坐标系走到下一个,是一次变换;走完整条链,是把这些变换一个接一个做下去。矩阵乘法 \(AB\) 的本意正是这个:先做 B,再做 A,合成一个新的变换。"行乘列"只是算它的记账法。顺序一换,结果就变。
1为什么机器人需要它
想象一台移动机器人,底盘上立着一个云台,云台上装着相机。相机看到一个杯子,报告:"杯子在我正前方 2.4 米、偏左 0.8 米。"可机械臂要的是杯子在世界坐标系里的位置。怎么换算?
先用云台相对相机的姿态,把读数换到云台坐标系;再用云台相对底盘的姿态,换到底盘坐标系;最后用底盘相对世界的姿态,换到世界坐标系。每一步是一个矩阵,一共三步。三步不必每次分开做,把它们连乘成一个矩阵,一次就够:
注意下标像多米诺骨牌一样"接龙":相邻的 B 对 B、H 对 H 约掉,剩下最外面的 W 和 C。机械臂也是同一个套路:底座 → 肩 → 肘 → 腕 → 夹爪,一路连乘。
这条链里最容易出事的是顺序。写反了,程序不会崩溃,只会给出一个看起来合理、其实错了的位置,机器人则会伸手去抓空气。本节只讲旋转,平移要到 5.3 节才加进来。
2直觉:先 B,再 A,合成一个变换
上一节学到:矩阵是一个动作,它由 î 和 ĵ 的落点决定。现在做两个动作:先用 \(B\) 把空间变形,再用 \(A\) 把变形后的空间再变形一次。两步合起来仍然是一个线性变换(直线仍是直线,原点不动),所以它也有一个矩阵。这个矩阵怎么找?跟上一节一样:追踪 î 和 ĵ 的去向。
下图中,\(B\) 是逆时针转 90°,\(A\) 是水平剪切。用下面的切换键一步步看。拖动黄色向量,也可以看它被先后推到哪里。
看右边的记录。î 先被 \(B\) 送到 \(B\hat\imath\)(就是 \(B\) 的第 1 列),再被 \(A\) 送到 \(A(B\hat\imath)\)。ĵ 同理。所以:
\(AB\) 是"先做 B,再做 A"的矩阵。它的第 \(j\) 列,是 \(A\) 作用在 \(B\) 的第 \(j\) 列上的结果。从右往左读:和函数复合 \(f(g(x))\) 一样,离向量最近的那个最先作用。
3动手玩:复合实验室
现在你来选 \(A\) 和 \(B\)。改矩阵里的数(左右拖动数字可以连续调节),或点下面的预设。"先 B 后 A" 给出 \(AB\),"先 A 后 B" 给出 \(BA\)。点 ▶ 播放:网格先从单位网格变成第一步的样子,再变成最终的样子。
- 改动至少一个矩阵,让 \(AB \ne BA\)(很容易),然后切换"先 B 后 A / 先 A 后 B",看两个最终网格不一样。
- 找两个可交换的矩阵(\(AB = BA\)):两个都不是单位矩阵,且 \(A \ne B\)。提示:两个旋转?两个只沿坐标轴拉伸的矩阵?
- 让 \(AB = I\),而 A、B 都不是单位矩阵。这时 B 恰好把 A 撤销了(1.6 节的主题)。提示:拉伸用反向拉伸撤销,旋转用转回去撤销。
- 让 \(AB = 0\)(四个数全为 0),而 A、B 都不是零矩阵。提示:先把平面压成一条线,再把剩下的那条线压成一个点。
小游戏:这是 AB 还是 BA?
给你两个矩阵 \(A\) 和 \(B\),以及其中一种顺序的最终网格(紫色虚线)。判断它是 \(AB\) 还是 \(BA\)。诀窍:只看 î 去了哪里,也就是乘积的第 1 列。连续答对 4 题。
- 连续答对 4 题。
4第二种视角:"行乘列"从哪里来
课本上的乘法口诀是"第 \(i\) 行乘第 \(j\) 列,对应相乘再相加"。有了上面的图景,这条口诀不再神秘:它只是把"\(A\) 作用在 \(B\) 的第 \(j\) 列上"这件事,一个数一个数地算出来。
在下面的表里,把鼠标移到(或点击)乘积 \(AB\) 的任意一格。左边会高亮 \(A\) 的那一行,上边高亮 \(B\) 的那一列,右边列出它们的"对应相乘再相加"。同一格也可以按列视角读:乘积的整个第 \(j\) 列,是 \(A\) 的各列按 \(B\) 的第 \(j\) 列的数配比后的组合。两种读法,同一个数。
- 把乘积 \(AB\) 的四个格子都看一遍。
手算或写循环时,用"行乘列"最顺手:每个数独立,互不依赖。想弄明白一个乘积做了什么时,用"列视角":每一列是一个基向量的落点。机器人里要排查坐标系链的时候,也是追着一个向量一步步看它落在哪个坐标系。
5第三种视角:到了三维,顺序更要紧
拿一本书放在桌上。先绕 x 轴转 90°,再绕 z 轴转 90°,书最后朝哪边?反过来,先绕 z 再绕 x,会一样吗?下图把两种顺序并排放在一起。书的六个面按"垂直于哪根轴"着色:红色垂直于 x 轴(短的两端),绿色垂直于 y 轴(长边),蓝色垂直于 z 轴(封面和封底)。琥珀色边框是封面。
两次旋转都是绕固定的世界坐标轴转的,所以每做一步,就把新矩阵乘在左边:先绕 x 再绕 z,矩阵是 \(R_z R_x\)。
- 找一对角度(两个都至少 20°),让两种顺序的最终姿态完全一样。有且只有一种选法,想想为什么。
平面里的旋转可交换:转 30° 再转 40°,和转 40° 再转 30°,都是转 70°。三维里的旋转一般不可交换:转轴不同,顺序就是内容的一部分。角度很小的时候两种顺序几乎一样(把两个角都拖到 5° 试试),角度大了差别才明显。机器人姿态的所有 bug,几乎都来自把这条忘了。
6再看数学
提示:把鼠标悬停在公式中带底色的 A、B、AB 上,FIG 2 会单独画出对应的网格。
定义:先 B,后 A
乘积 \(AB\) 是这样一个矩阵:对每个向量 \(\mathbf v\),
它存在而且唯一:两个线性变换复合后仍是线性变换,而线性变换由 \(\hat\imath,\hat\jmath\) 的落点唯一决定。
一列一列算,以及单个数的公式
把 \(\hat\imath\) 代入定义:\((AB)\hat\imath = A(B\hat\imath) = A\mathbf b_1\),其中 \(\mathbf b_1\) 是 \(B\) 的第 1 列。对 \(\hat\jmath\) 同理。所以
再把 \(A\mathbf b_j\) 按"行视角"展开(1.3 节),第 \(i\) 个分量是 \(A\) 的第 \(i\) 行与 \(\mathbf b_j\) 对应相乘再相加:
形状:内维必须相同
\(B\) 把 \(\R^p\) 里的向量送到 \(\R^n\),\(A\) 再把 \(\R^n\) 里的向量送到 \(\R^m\)。能接起来的前提是:\(B\) 的输出空间必须正好是 \(A\) 的输入空间,也就是两个 \(n\) 要相等。例如相机投影 \(A\)(\(2\times 3\):三维点到二维像素)乘一个 \(3\times 3\) 的旋转 \(B\),得到 \(2\times 3\) 的"先转再投影";反过来 \(BA\) 没有意义,因为投影的输出是二维,而旋转要吃三维。
结合律:怎么加括号都一样
两边都表示"先 C,再 B,最后 A"。不需要逐项验算,只需看懂两边在做同一件事。机器人里它很有用:可以提前把不变的那段链连乘好存起来,比如相机装在云台上,\({}^{H}R_{C}\) 是固定的,每帧只需要更新变化的部分。
不可交换,以及单位矩阵
"先穿袜子再穿鞋"和"先穿鞋再穿袜子"不是一回事,这就是 \(AB \ne BA\)。少数情况下可以交换:\(A\) 或 \(B\) 是单位矩阵("什么也不做",对角线全是 1),或其中一个是单位矩阵的倍数,或两个都是对角矩阵,或两个都是同一平面内的旋转。
转置:穿袜子与穿鞋
验证只要一行:\(\big((AB)^{\T}\big)_{ij} = (AB)_{ji} = \sum_k a_{jk}b_{ki} = \sum_k (B^{\T})_{ik}(A^{\T})_{kj}\)。直觉是"撤销":穿的时候先袜后鞋,脱的时候先鞋后袜,顺序必须反过来。同一条规则也管逆矩阵:\((AB)^{-1} = B^{-1}A^{-1}\)(1.6 节)。对旋转矩阵 \(R^{-1} = R^{\T}\)(1.10 节),所以反过来换算坐标系时,整条链的顺序也会反过来。
二维旋转:角度加法白送
旋转 \(R(\theta)\) 的两列是转过 \(\theta\) 后的 \(\hat\imath,\hat\jmath\)。先转 \(\beta\) 再转 \(\alpha\),就是转 \(\alpha+\beta\)。把乘积算出来:
它必须等于 \(R(\alpha+\beta)\),其第 1 列是 \((\cos(\alpha+\beta),\,\sin(\alpha+\beta))\)。比较第 1 列,两角和公式就出来了:
因为 \(\alpha+\beta = \beta+\alpha\),所以 \(R(\alpha)R(\beta) = R(\beta)R(\alpha)\):平面旋转可交换。下面的图让你亲手拨一拨。
- 让乘积正好是 90° 旋转 \(\begin{bmatrix}0&-1\\1&0\end{bmatrix}\),且两个角都不是 0。
7机器人中的它:云台相机的坐标系链
云台由两个关节组成:下面一个关节绕竖直的 z 轴水平转(pan,偏航 \(\psi\)),上面一个关节让相机俯仰(tilt,\(\theta\)),它绕的是云台自己的 y 轴,这根轴会随着水平转动而转走。相机坐标系在世界里的姿态是
\(R_y(\theta)\) 在 \(\theta>0\) 时把相机的前方(x 轴)向下压,所以正的 \(\theta\) 是"向下看"。杯子在相机坐标里固定为 \(\mathbf p_C = (2.4,\,0.8,\,0)\)(正前方 2.4 米,偏左 0.8 米)。调整 \(\psi\) 和 \(\theta\),看它在世界里的位置如何变化。再切到"错误顺序":\(R_y(\theta)R_z(\psi)\) 把两个矩阵的次序对调了。(为了专注旋转,所有坐标系都放在同一个原点。)
- 切到"错误顺序",找一组 \(\psi\)、\(\theta\),让错算的位置偏离真实位置超过 1.5 米。
- 切到"错误顺序",让两种顺序给出相同的位置,同时俯仰角 \(|\theta|\ge 15°\)。什么时候这种错误不会被发现?
\(R_z(\psi)R_y(\theta)\) 可以这样读:从右往左,先绕世界 y 轴转 \(\theta\),再绕世界 z 轴转 \(\psi\),每一步都绕固定的世界轴;也可以从左往右,先转 \(\psi\),再绕云台"现在的" y 轴转 \(\theta\),每一步都绕当前的轴。口诀:右乘(把新旋转写在右边)是绕自身的移动轴转,左乘(写在左边)是绕固定的世界轴转。FIG 5 的书每步都是左乘;云台的俯仰是右乘。这两种说法的完整关系在 5.2 节讲。
ROS 的 tf2 把整个机器人表示成一棵坐标系树,查询"相机在地图里的位姿",就是沿树把一串变换连乘(并在需要反向走时取逆)。机械臂的正运动学也是同一件事:每个关节一个矩阵,从底座乘到末端(5.3 节、5.10 节)。把关节角写进变量名,再把链写成 R_world_cam = R_world_base @ R_base_head @ R_head_cam,下标能接龙就基本不会错。
8常见误区
\(AB\) 是先做 B 再做 A:最后一个作用的矩阵写在最左边。很多人按"时间顺序"从左往右写,于是整条链反了。检查办法:看下标能不能接龙,\({}^{W}R_{B}\,{}^{B}R_{H}\) 对,\({}^{B}R_{H}\,{}^{W}R_{B}\) 错。
在 NumPy 里,对数组 A * B 是逐元素相乘,矩阵乘法要写 A @ B(或 np.matmul)。对小测验第 1 题的两个矩阵,A * B 给出 \(\begin{bmatrix}2&0\\0&1\end{bmatrix}\),A @ B 才是 \(\begin{bmatrix}2&6\\1&4\end{bmatrix}\)。两个都是 2×2,程序不会报错,只会悄悄算错。(C++ 的 Eigen 里 * 才是矩阵乘法,换语言时要留神。)
看到 matmul: Input operand 1 has a mismatch in its core dimension,别急着 .T 或 reshape 把它压下去。内维对不上,往往说明你把坐标系接错了:这个矩阵的输出空间,并不是下一个矩阵的输入空间。先搞清每个矩阵"从哪来到哪去",再动手。
"先绕 z 转 30°,再绕 y 转 20°"有两种合理的意思:y 轴是世界的 y 轴,还是转完之后云台自己的 y 轴?前者是 \(R_y(20°)R_z(30°)\),后者是 \(R_z(30°)R_y(20°)\),结果不同。写代码前先说清每个关节转的是哪根轴。
转置和求逆都会把顺序反过来:\((AB)^{\T} = B^{\T}A^{\T}\)。如果 \(A\) 是 \(2\times 3\)、\(B\) 是 \(3\times 4\),错误写法 \(A^{\T}B^{\T}\) 连形状都对不上(\(3\times 2\) 乘 \(4\times 3\)),这也是形状检查能救你的一个例子。
9小练习
*)。10延伸阅读
- 3Blue1Brown, Essence of Linear Algebra — 第 4 章(矩阵乘法即复合)与第 5 章(三维线性变换)
- G. Strang, Introduction to Linear Algebra, Ch. 2 — 矩阵乘法的几种读法(2.4 节 矩阵运算规则)
- K. Lynch & F. Park, Modern Robotics, Sec. 3.2 — 旋转矩阵,以及绕固定轴与绕自身轴的复合
- J. Ström et al., Immersive Linear Algebra — 矩阵乘法一章,带可拖动的交互插图