Part I · 第 1 章 线性代数 · 1.4

矩阵乘法即复合

机器人是一串坐标系:世界、底盘、云台、相机。从一个坐标系走到下一个,是一次变换;走完整条链,是把这些变换一个接一个做下去。矩阵乘法 \(AB\) 的本意正是这个:先做 B,再做 A,合成一个新的变换。"行乘列"只是算它的记账法。顺序一换,结果就变。

约 25 分钟 前置:1.3 矩阵即变换 7 个交互

    1为什么机器人需要它

    想象一台移动机器人,底盘上立着一个云台,云台上装着相机。相机看到一个杯子,报告:"杯子在我正前方 2.4 米、偏左 0.8 米。"可机械臂要的是杯子在世界坐标系里的位置。怎么换算?

    先用云台相对相机的姿态,把读数换到云台坐标系;再用云台相对底盘的姿态,换到底盘坐标系;最后用底盘相对世界的姿态,换到世界坐标系。每一步是一个矩阵,一共三步。三步不必每次分开做,把它们连乘成一个矩阵,一次就够:

    $$ {}^{W}R_{C} \;=\; {}^{W}R_{B}\;\;{}^{B}R_{H}\;\;{}^{H}R_{C} $$

    注意下标像多米诺骨牌一样"接龙":相邻的 B 对 B、H 对 H 约掉,剩下最外面的 W 和 C。机械臂也是同一个套路:底座 → 肩 → 肘 → 腕 → 夹爪,一路连乘。

    这条链里最容易出事的是顺序。写反了,程序不会崩溃,只会给出一个看起来合理、其实错了的位置,机器人则会伸手去抓空气。本节只讲旋转,平移要到 5.3 节才加进来。

    2直觉:先 B,再 A,合成一个变换

    上一节学到:矩阵是一个动作,它由 î 和 ĵ 的落点决定。现在做两个动作:先用 \(B\) 把空间变形,再用 \(A\) 把变形后的空间再变形一次。两步合起来仍然是一个线性变换(直线仍是直线,原点不动),所以它也有一个矩阵。这个矩阵怎么找?跟上一节一样:追踪 î 和 ĵ 的去向。

    下图中,\(B\) 是逆时针转 90°,\(A\) 是水平剪切。用下面的切换键一步步看。拖动黄色向量,也可以看它被先后推到哪里。

    FIG 1两步合成一步
    基向量的旅程
    黄色向量
    淡色网格是原始网格,亮色网格是做完当前步骤后的样子。红、绿箭头是 î、ĵ 当前的位置;虚线箭头是黄色向量在前几步的位置。到最后一步,两个箭头的坐标并排写下来,就是 \(AB\)。

    看右边的记录。î 先被 \(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\)。点 ▶ 播放:网格先从单位网格变成第一步的样子,再变成最终的样子。

    FIG 2复合实验室
    矩阵 A
    矩阵 B
    A 预设
    B 预设
    淡色网格是原始网格;青色亮网格是做完当前步骤后的网格,红、绿箭头是 î、ĵ 此刻的位置。进度条拖到最右端时,紫色虚线是另一种顺序的结果。把鼠标悬停在"再看数学"一节的符号 A、B、AB 上,这里会单独画出对应的网格。
    试一试
    • 改动至少一个矩阵,让 \(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 题。

    FIG 3AB 还是 BA?
    • 连续答对 4 题。

    4第二种视角:"行乘列"从哪里来

    课本上的乘法口诀是"第 \(i\) 行乘第 \(j\) 列,对应相乘再相加"。有了上面的图景,这条口诀不再神秘:它只是把"\(A\) 作用在 \(B\) 的第 \(j\) 列上"这件事,一个数一个数地算出来。

    在下面的表里,把鼠标移到(或点击)乘积 \(AB\) 的任意一格。左边会高亮 \(A\) 的那一行,上边高亮 \(B\) 的那一列,右边列出它们的"对应相乘再相加"。同一格也可以按列视角读:乘积的整个第 \(j\) 列,是 \(A\) 的各列按 \(B\) 的第 \(j\) 列的数配比后的组合。两种读法,同一个数。

    FIG 4乘积表
    行乘列:对应相乘再相加
    列视角:A 作用在 B 的这一列上
    A
    B
    和 FIG 2 共用同一对矩阵,这里改了那里也变。
    青色是 \(A\) 的第 \(i\) 行,紫色是 \(B\) 的第 \(j\) 列,黄色是它们算出的那一格。表的摆法让每个乘积格的"行"在它左边,"列"在它上边,一眼就能对上。
    • 把乘积 \(AB\) 的四个格子都看一遍。
    实际计算时怎么选

    手算或写循环时,用"行乘列"最顺手:每个数独立,互不依赖。想弄明白一个乘积做了什么时,用"列视角":每一列是一个基向量的落点。机器人里要排查坐标系链的时候,也是追着一个向量一步步看它落在哪个坐标系。

    5第三种视角:到了三维,顺序更要紧

    拿一本书放在桌上。先绕 x 轴转 90°,再绕 z 轴转 90°,书最后朝哪边?反过来,先绕 z 再绕 x,会一样吗?下图把两种顺序并排放在一起。书的六个面按"垂直于哪根轴"着色:红色垂直于 x 轴(短的两端),绿色垂直于 y 轴(长边),蓝色垂直于 z 轴(封面和封底)。琥珀色边框是封面。

    两次旋转都是绕固定的世界坐标轴转的,所以每做一步,就把新矩阵乘在左边:先绕 x 再绕 z,矩阵是 \(R_z R_x\)。

    FIG 5同样两次转动,不同顺序
    先绕 x,再绕 z:RzRx
    先绕 z,再绕 x:RxRz
    拖动画面可以转动视角(两张图同步)。进度条 0 到 1 是第一次转动,1 到 2 是第二次。两个角度默认都是 90°,也可以改。数学上两张图的最终姿态由下方两个矩阵给出。
    • 找一对角度(两个都至少 20°),让两种顺序的最终姿态完全一样。有且只有一种选法,想想为什么。
    核心洞见

    平面里的旋转可交换:转 30° 再转 40°,和转 40° 再转 30°,都是转 70°。三维里的旋转一般不可交换:转轴不同,顺序就是内容的一部分。角度很小的时候两种顺序几乎一样(把两个角都拖到 5° 试试),角度大了差别才明显。机器人姿态的所有 bug,几乎都来自把这条忘了。

    6再看数学

    提示:把鼠标悬停在公式中带底色的 A、B、AB 上,FIG 2 会单独画出对应的网格。

    定义:先 B,后 A

    乘积 \(AB\) 是这样一个矩阵:对每个向量 \(\mathbf v\),

    $$ \hl{AB}{(AB)}\,\mathbf v \;=\; \hl{A}{A}\,\bigl(\hl{B}{B}\,\mathbf v\bigr). $$

    它存在而且唯一:两个线性变换复合后仍是线性变换,而线性变换由 \(\hat\imath,\hat\jmath\) 的落点唯一决定。

    一列一列算,以及单个数的公式

    把 \(\hat\imath\) 代入定义:\((AB)\hat\imath = A(B\hat\imath) = A\mathbf b_1\),其中 \(\mathbf b_1\) 是 \(B\) 的第 1 列。对 \(\hat\jmath\) 同理。所以

    $$ AB = A\,[\,\ci{\mathbf b_1}\;\;\cj{\mathbf b_2}\,] = [\,\ci{A\mathbf b_1}\;\;\cj{A\mathbf b_2}\,]. $$

    再把 \(A\mathbf b_j\) 按"行视角"展开(1.3 节),第 \(i\) 个分量是 \(A\) 的第 \(i\) 行与 \(\mathbf b_j\) 对应相乘再相加:

    $$ (AB)_{ij} \;=\; \sum_{k=1}^{n} a_{ik}\,b_{kj} \;=\; a_{i1}b_{1j} + a_{i2}b_{2j} + \dots + a_{in}b_{nj}. $$

    形状:内维必须相同

    $$ \underbrace{A}_{m\times n}\;\underbrace{B}_{n\times p} \;=\; \underbrace{AB}_{m\times p}. $$

    \(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\) 没有意义,因为投影的输出是二维,而旋转要吃三维。

    结合律:怎么加括号都一样

    $$ A(BC) = (AB)C. $$

    两边都表示"先 C,再 B,最后 A"。不需要逐项验算,只需看懂两边在做同一件事。机器人里它很有用:可以提前把不变的那段链连乘好存起来,比如相机装在云台上,\({}^{H}R_{C}\) 是固定的,每帧只需要更新变化的部分。

    不可交换,以及单位矩阵

    $$ AB \ne BA \;\text{(in general)}, \qquad AI = IA = A. $$

    "先穿袜子再穿鞋"和"先穿鞋再穿袜子"不是一回事,这就是 \(AB \ne BA\)。少数情况下可以交换:\(A\) 或 \(B\) 是单位矩阵("什么也不做",对角线全是 1),或其中一个是单位矩阵的倍数,或两个都是对角矩阵,或两个都是同一平面内的旋转。

    转置:穿袜子与穿鞋

    $$ (AB)^{\T} = B^{\T}A^{\T}. $$

    验证只要一行:\(\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)R(\beta) = \begin{bmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{bmatrix}\begin{bmatrix} \cos\beta & -\sin\beta \\ \sin\beta & \cos\beta \end{bmatrix} = \begin{bmatrix} \ci{\cos\alpha\cos\beta - \sin\alpha\sin\beta} & \cdots \\ \ci{\sin\alpha\cos\beta + \cos\alpha\sin\beta} & \cdots \end{bmatrix}. $$

    它必须等于 \(R(\alpha+\beta)\),其第 1 列是 \((\cos(\alpha+\beta),\,\sin(\alpha+\beta))\)。比较第 1 列,两角和公式就出来了:

    $$ \cos(\alpha+\beta) = \cos\alpha\cos\beta - \sin\alpha\sin\beta, \qquad \sin(\alpha+\beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta. $$

    因为 \(\alpha+\beta = \beta+\alpha\),所以 \(R(\alpha)R(\beta) = R(\beta)R(\alpha)\):平面旋转可交换。下面的图让你亲手拨一拨。

    FIG 6转两次,角度相加
    紫色是先转 \(\beta\) 的位置,黄色是再转 \(\alpha\) 后的位置;灰色虚线是反过来先转 \(\alpha\) 的路线。两条路终点相同。可以拖动紫色或黄色箭头尖,也可以用滑块。
    • 让乘积正好是 90° 旋转 \(\begin{bmatrix}0&-1\\1&0\end{bmatrix}\),且两个角都不是 0。

    7机器人中的它:云台相机的坐标系链

    云台由两个关节组成:下面一个关节绕竖直的 z 轴水平转(pan,偏航 \(\psi\)),上面一个关节让相机俯仰(tilt,\(\theta\)),它绕的是云台自己的 y 轴,这根轴会随着水平转动而转走。相机坐标系在世界里的姿态是

    $$ {}^{W}R_{C} = R_z(\psi)\,R_y(\theta), \qquad R_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta \\ 0 & 1 & 0 \\ -\sin\theta & 0 & \cos\theta \end{bmatrix}, \qquad \mathbf p_W = {}^{W}R_{C}\,\mathbf p_C. $$

    \(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)\) 把两个矩阵的次序对调了。(为了专注旋转,所有坐标系都放在同一个原点。)

    FIG 7云台相机看到的杯子
    淡色的 x、y、z 箭头是世界坐标系;虚线箭头是只做了水平转动之后的云台坐标系(注意它的 y 轴,就是俯仰轴);粗箭头是相机坐标系。黄点是正确算出的杯子位置,虚线折线是从相机原点出发沿 \(x_c\)、\(y_c\) 走到杯子的路。在"错误顺序"下,紫色的相机轴和紫点显示把矩阵乘反后的结果。
    • 切到"错误顺序",找一组 \(\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常见误区

    误区 1:顺序写反(AB 当成 BA)

    \(AB\) 是先做 B 再做 A:最后一个作用的矩阵写在最左边。很多人按"时间顺序"从左往右写,于是整条链反了。检查办法:看下标能不能接龙,\({}^{W}R_{B}\,{}^{B}R_{H}\) 对,\({}^{B}R_{H}\,{}^{W}R_{B}\) 错。

    误区 2:NumPy 里用 * 做矩阵乘法

    在 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 里 * 才是矩阵乘法,换语言时要留神。)

    误区 3:把形状不匹配的报错当成小问题

    看到 matmul: Input operand 1 has a mismatch in its core dimension,别急着 .T 或 reshape 把它压下去。内维对不上,往往说明你把坐标系接错了:这个矩阵的输出空间,并不是下一个矩阵的输入空间。先搞清每个矩阵"从哪来到哪去",再动手。

    误区 4:混淆绕移动轴转和绕固定轴转

    "先绕 z 转 30°,再绕 y 转 20°"有两种合理的意思:y 轴是世界的 y 轴,还是转完之后云台自己的 y 轴?前者是 \(R_y(20°)R_z(30°)\),后者是 \(R_z(30°)R_y(20°)\),结果不同。写代码前先说清每个关节转的是哪根轴。

    误区 5:\((AB)^{\T} = A^{\T}B^{\T}\)

    转置和求逆都会把顺序反过来:\((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小练习

    1. 设 \(A = \begin{bmatrix}2&0\\1&1\end{bmatrix}\),\(B = \begin{bmatrix}1&3\\0&1\end{bmatrix}\)。\(AB\) 是?
    按列算:第 1 列是 \(A\) 乘 \(B\) 的第 1 列 \((1,0)\),得 \((2,1)\);第 2 列是 \(A\) 乘 \((3,1)\),即 \(3\,(2,1) + 1\,(0,1) = (6,4)\)。所以 \(AB = \begin{bmatrix}2&6\\1&4\end{bmatrix}\)。选项 A 是 \(BA\),选项 B 是 \(A+B\),选项 D 是逐元素相乘(NumPy 的 *)。
    2. \(B\) 是"沿 x 方向拉伸 2 倍"(\(B=\begin{bmatrix}2&0\\0&1\end{bmatrix}\)),\(A\) 是"逆时针转 90°"。在 \(AB\)(先 B 后 A)下,î 和 ĵ 各落在哪里?
    追踪:î 先被拉成 \((2,0)\),再转 90° 成 \((0,2)\);ĵ 先保持 \((0,1)\),再转成 \((-1,0)\)。所以 \(AB = \begin{bmatrix}0&-1\\2&0\end{bmatrix}\)。选项 B 是反过来的 \(BA\)(先转再拉:\((0,1)\) 被拉后不变,\((-1,0)\) 被拉成 \((-2,0)\))。
    3. 已知 \({}^{W}R_{B}\)、\({}^{B}R_{H}\)、\({}^{H}R_{C}\),要把相机坐标换算到世界坐标,应该用哪个乘积?
    相邻下标要能约掉:\({}^{W}R_{B}\,{}^{B}R_{H}\,{}^{H}R_{C} = {}^{W}R_{C}\),把 C 坐标送到 W 坐标。选项 B 是从世界到相机的方向(\({}^{C}R_{W}\),即上式的逆),选项 A、C的下标接不上。
    4. 相机投影矩阵 \(A\) 是 \(2\times 3\)(三维点到二维像素),\(B\) 是 \(3\times 3\) 的旋转矩阵。下面哪个说法对?
    \(AB\):\((2\times 3)(3\times 3)\),内维都是 3,结果 \(2\times 3\),含义是"先旋转三维点,再投影到图像"。\(BA\):\((3\times 3)(2\times 3)\),内维是 3 和 2,对不上:投影输出的是二维,旋转却要吃三维。
    5. 云台先水平转 \(\psi\)(绕竖直 z 轴),再让相机绕云台自己的 y 轴俯仰 \(\theta\)。相机在世界中的姿态是?
    绕"自身当前轴"转,新旋转写在右边:\(R_z(\psi)R_y(\theta)\)。两个顺序一般不同(FIG 7 里切到"错误顺序"可以看到),只有 \(\psi = 0\) 或 \(\theta = 0\) 这类特殊情形才一样。矩阵相加没有"先后"的含义。

    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 — 矩阵乘法一章,带可拖动的交互插图