跳轉到

2.3 特殊矩陣:單位、旋轉、鏡射、剪切、縮放#

本節目標#

  • 認識五個「基本積木」矩陣:單位、旋轉、鏡射、剪切、縮放
  • 用「追蹤 \(\mathbf{e_1}, \mathbf{e_2}\) 的下場」自己推導 旋轉矩陣(附三角函數複習框)
  • 每種變換都用 linalg_viz 畫網格圖,建立視覺記憶
  • 用「原點不動、直線仍直」的檢驗法確認線性
  • 把積木 組合 出一個複雜變換(體會 2.2 的複合威力)

2.3.1 單位矩陣:什麼都不做#

\[ I = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}, \qquad I\mathbf{e_1} = \mathbf{e_1},\ I\mathbf{e_2} = \mathbf{e_2} \]

兩行就是 \(\mathbf{e_1}, \mathbf{e_2}\) 本身 —— 恆等變換,數字世界的「1」。np.eye(2)。任何矩陣 \(AI = IA = A\) ,它是矩陣乘法的單位元素(練習 1 驗證)。

2.3.2 縮放矩陣:對角線上的伸縮#

\[ S = \begin{bmatrix} 3 & 0 \\ 0 & 2 \end{bmatrix} \ \Rightarrow\ S\mathbf{e_1} = \begin{bmatrix} 3 \\ 0 \end{bmatrix},\ S\mathbf{e_2} = \begin{bmatrix} 0 \\ 2 \end{bmatrix} \]

水平拉 3 倍、垂直拉 2 倍。對角矩陣 = 各座標軸獨立伸縮,是所有矩陣裡最「乖」的一種 —— 第 5 章會看到,旋轉到特徵向量座標系後,任何對稱矩陣都變成這種乖矩陣。

import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform

matrices = {
    "縮放 (3,2)": np.diag([3., 2.]),
}

fig, ax = plt.subplots(figsize=(6, 6))
setup_axes(ax, xlim=(-4, 4), ylim=(-4, 4))
draw_grid_transform(ax, matrices["縮放 (3,2)"], xlim=(-4, 4), ylim=(-4, 4))
ax.set_title("Scaling: diag(3, 2)")
plt.show()

若對角線元素是負的,例如 \(\mathrm{diag}(-1, 1)\) —— 這其實就是鏡射,見 2.3.4。

2.3.3 旋轉矩陣:三角函數複習 + 推導#

三角函數複習框

單位圓上角度 \(\theta\) 的點是 \((\cos\theta, \sin\theta)\) 。

  • \(\cos\) 是 \(x\) 座標(cos → 鄰邊/斜邊)、 \(\sin\) 是 \(y\) 座標
  • \(\cos 90° = 0,\ \sin 90° = 1\) ; \(\cos 0° = 1,\ \sin 0° = 0\)
  • 旋轉不改變長度,所以矩陣的每一行都該是 單位向量,且兩行互相垂直

推導(不用背,跟著 \(\mathbf{e_1}, \mathbf{e_2}\) 走):

  • \(\mathbf{e_1} = (1, 0)\) 逆時針轉 \(\theta\) 後落在單位圓上角度 \(\theta\) 處: \((\cos\theta, \sin\theta)\) → 這是第一行
  • \(\mathbf{e_2} = (0, 1)\) 轉 \(\theta\) 後落在角度 \(\theta + 90°\) 處: \((-\sin\theta, \cos\theta)\) → 這是第二行
\[ R_\theta = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \]
import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform

theta = np.deg2rad(30)                       # 30 度轉弧度
R = np.array([[np.cos(theta), -np.sin(theta)],
              [np.sin(theta),  np.cos(theta)]])

fig, ax = plt.subplots(figsize=(6, 6))
setup_axes(ax, xlim=(-2, 2), ylim=(-2, 2))
draw_grid_transform(ax, R, xlim=(-2, 2), ylim=(-2, 2))
ax.set_title(r"Rotation $R_{30^\circ}$")
plt.show()

# 驗證:長度不變
v = np.array([2., 1.])
print(np.linalg.norm(v), np.linalg.norm(R @ v))   # 2.236... 2.236...
print(np.allclose(R.T @ R, np.eye(2)))            # True:行的長度為1、彼此垂直

R.T @ R = I 這條性質叫 正交矩陣(第 6 章主角):旋轉與鏡射都屬於這一族,「只轉方向、不拉長度」。

2.3.4 鏡射矩陣:把世界翻面#

對 \(x\) 軸鏡射: \(\mathbf{e_1}\) 不動、 \(\mathbf{e_2} \to -\mathbf{e_2}\) :

\[ F_x = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \]

對 \(y\) 軸鏡射: \(F_y = \begin{bmatrix} -1 & 0 \\ 0 & 1 \end{bmatrix}\) 。 對直線 \(y = x\) 鏡射: \(\mathbf{e_1} \leftrightarrow \mathbf{e_2}\) 互換:

\[ F_{y=x} = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \]
import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform

mirrors = {
    "對 x 軸": np.diag([1., -1.]),
    "對 y 軸": np.diag([-1., 1.]),
    "對 y=x":  np.array([[0., 1.],
                         [1., 0.]]),
}

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, (name, M) in zip(axes, mirrors.items()):
    setup_axes(ax, xlim=(-2, 2), ylim=(-2, 2))
    draw_grid_transform(ax, M, xlim=(-2, 2), ylim=(-2, 2))
    ax.set_title(f"Mirror {name}")
plt.show()

F = np.diag([1., -1.])
print(np.linalg.det(F))     # -1.0:翻轉了定向(順時針↔逆時針),見 2.4 節

鏡射做兩次回到原狀: \(F^2 = I\) 。畫圖或用 NumPy 驗證 —— 這是「翻面再翻面」的直覺。

2.3.5 剪切矩陣:推倒撲克牌#

2.1 已見過水平剪切 \(S_x = \begin{bmatrix} 1 & k \\ 0 & 1 \end{bmatrix}\) ,垂直版是 \(S_y = \begin{bmatrix} 1 & 0 \\ k & 1 \end{bmatrix}\) 。 \(k\) 越大、推得越歪; \(k = 0\) 就退回單位矩陣。

import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform

k = 1.0
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
for ax, M, name in [(axes[0], np.array([[1., k], [0., 1.]]), "水平剪切 k=1"),
                    (axes[1], np.array([[1., 0.], [k, 1.]]), "垂直剪切 k=1")]:
    setup_axes(ax, xlim=(-3, 3), ylim=(-3, 3))
    draw_grid_transform(ax, M, xlim=(-3, 3), ylim=(-3, 3))
    ax.set_title(name)
plt.show()

2.3.6 線性檢驗:原點不動、直線仍直#

每個新矩陣都可以做「體檢」:

  1. 原點檢驗: \(M\mathbf{0} = \mathbf{0}\) ?—— 任何矩陣都通過(乘矩陣永遠得零向量),這就是為什麼矩陣表達不了平移
  2. 直線檢驗:把一條直線上的取樣點全變換後,仍應共線
M = np.array([[0., 1.],
              [1., 0.]])          # 對 y=x 鏡射
t = np.linspace(-2, 2, 20)
b = np.array([0., 1.])
line = np.outer([1., 2.], t) + b[:, None]    # 直線 y = 2x + 1 上取 20 點
Mline = M @ line
# 直線的「方向向量」(1,2) 應變成 M @ (1,2),截距的像應是 M @ b
d = M @ np.array([1., 2.])
resid = Mline - np.outer(d, t) - (M @ b)[:, None]
print(np.allclose(resid, 0))      # True:像仍是一條直線(平行 y=x 鏡射後的方向)
print(np.linalg.norm(resid))      # 0.0(或浮點極小值)

注意: \(y = 2x + 1\) 的像還是一條直線:方向向量 \((1,2)\) 被 \(M\) 推去新的方向 \(M(1,2)\) ,截距的像正好是 \(M\mathbf{b}\) 。線性變換 保直線,但不保原本的原點不成原點 —— 整條線像剛體一樣「平著搬、不彎折」,這正是 \(T(\text{點} + t\mathbf{d}) = T(\text{點}) + t\,T(\mathbf{d})\) 的展現。

2.3.7 組合積木:拼出一個複雜變換#

目標變換:「先對 \(x\) 軸鏡射,再旋轉 \(90°\) ,最後水平剪切」。複合矩陣 \(M = S R F_x\) (右邊先做):

import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform

F  = np.diag([1., -1.])                      # 鏡射(先做)
th = np.pi / 2
R  = np.array([[np.cos(th), -np.sin(th)],
               [np.sin(th),  np.cos(th)]])   # 旋轉 90°
Sx = np.array([[1., 1.],
               [0., 1.]])                    # 剪切(最後做)

M = Sx @ R @ F
print(M)                                      # 一個矩陣,三個動作!

fig, ax = plt.subplots(figsize=(6, 6))
setup_axes(ax, xlim=(-3, 3), ylim=(-3, 3))
draw_grid_transform(ax, M, xlim=(-3, 3), ylim=(-3, 3))
ax.set_title(r"Compose: $M = S\,R\,F_x$")
plt.show()

一個 \(2\times2\) 矩陣就完整封裝了三個動作 —— 這正是電腦遊戲引擎表示角色姿態的方式:不管動作多複雜,都是矩陣相乘(8.4 章的齊次座標會把平移也納進來)。

flowchart LR
    F["鏡射 Fx"] --> R["旋轉 R90"] --> S["剪切 Sx"] --> M["複合矩陣 M"]

小結與練習#

本節重點

  • 五種積木:單位 \(I\) 、縮放 \(\mathrm{diag}(s_x, s_y)\) 、旋轉 \(R_\theta\) 、鏡射( \(\det = -1\) )、剪切
  • 旋轉矩陣 推得出來:兩行就是 \(\mathbf{e_1}, \mathbf{e_2}\) 轉完後的位置(單位圓上的點)
  • 旋轉/鏡射都是正交矩陣: \(M^T M = I\) ,不改變長度
  • 線性檢驗:原點不動 + 直線仍直(矩陣天生通過,但這解釋了為何平移不是線性)
  • 複雜變換 = 積木矩陣的乘積,右邊先做

練習

  1. 用隨機 \(2\times2\) 矩陣 \(A\) 驗證 \(AI = IA = A\) 與 \(A I \mathbf{v} = A\mathbf{v}\) 。
  2. 推導並畫出旋轉 \(-45°\) 的矩陣( \(\theta\) 取負值即可),與旋轉 \(+45°\) 的圖比較。
  3. 驗證 \(F_{y=x}^2 = I\) 與 \(R_{\theta}^{-1} = R_{-\theta}\) (提示: \(R_{-\theta} = R_\theta^T\) ,畫圖解釋為什麼)。
  4. 對「旋轉 \(45°\) 後再縮放 \((2, 2)\) 」畫網格圖;再對「縮放後旋轉」畫一次。這兩者可交換嗎?為什麼(提示:等比例縮放像「放大整張照片」)?
  5. 挑戰:組合出「把平面對直線 \(y = 2x\) 鏡射」的矩陣。提示: \(M = R_{-\alpha}\, F_x\, R_{\alpha}\) ,其中 \(\alpha\) 是直線與 \(x\) 軸的夾角 —— 先把直線轉到 \(x\) 軸、鏡射、再轉回來。