同次座標と変換行列

Homogeneous Coordinates and Transformation Matrices

同次座標とは

同次座標(Homogeneous Coordinates)は、射影幾何学で使われる座標系で、通常の座標に成分を 1 つ追加したものである(幾何の次元が増えるわけではない)。厳密には $(kx, ky, kw) \sim (x, y, w)$($k \neq 0$)のように比例関係にある座標を同じ点とみなす同値類であり(零ベクトルは点を表さない)、これにより平行移動を含むアフィン変換や射影変換を行列の積で表現できる(レンズ歪みのような非線形の変換は表せない)。

2D点 (x, y) 通常座標 → 同次座標 (x, y, 1) 3要素ベクトル 3D: (X, Y, Z) → 同次: (X, Y, Z, 1) 同じ点の別の表し方: (kx, ky, k) ∼ (x, y, 1)
図1. 通常座標と同次座標の対応

同次座標の定義

  • 2D点 $(x, y)$ の同次座標: $(x, y, 1)$ または一般に $(kx, ky, k)$ for $k \neq 0$
  • 3D点 $(X, Y, Z)$ の同次座標: $(X, Y, Z, 1)$ または $(kX, kY, kZ, k)$($k \neq 0$)
  • 通常座標への変換: $(x, y, w) \to (x/w, y/w)$($w \neq 0$ のとき。$w = 0$ は無限遠点を表す)

なぜ同次座標を使うのか?

  • 平行移動を行列の積で表現できる
  • 複数の変換を1つの行列に合成できる
  • 射影変換(透視投影)を行列の積で扱える(通常座標に戻すときに最後の成分で割る)
  • 無限遠点を有限の座標で表現できる($w = 0$)

平行移動はなぜ特別か

通常座標では平行移動は加算 $(x', y') = (x, y) + (t_x, t_y)$ であり、原点を固定する線形変換(1つの行列の積)では書けない。同次座標で次元を1つ増やすと、平行移動も $(x', y', 1) = T\,(x, y, 1)$ という単一の行列積で表せる。これにより回転・スケール・平行移動を同じ枠組みで合成できるのが最大の利点である。

2D変換行列

ベクトルの向き: 本記事ではベクトルを列ベクトルとして扱い、$p' = T\,p$ と行列を左から掛ける。$(x, y, 1)$ とカンマ区切りで横書きするのは、列ベクトル $(x, y, 1)^\top$ を縦に積む代わりに省スペースで書いた省略表記である。

平行移動(Translation)

元の位置 移動後 (tx, ty) 平行移動行列: T = 10tx 01ty 001 p′ = T p (x′, y′, 1) = (x+tx, y+ty, 1)
図2. 平行移動の変換行列

回転(Rotation)

θ 原点中心で回転 回転行列: R = cos θ−sin θ0 sin θcos θ0 001 反時計回りを正とする
図3. 原点中心の回転変換(反時計回りを正とするのは $x$ 右・$y$ 上の座標のとき。$y$ を下向きに取る画像座標では、同じ行列の正の角は見た目で時計回りになる)

スケーリング(Scaling)

sx = 2, sy = 3 スケール行列: S = sx00 0sy0 001 sx ≠ sy: 異方スケール
図4. スケーリング変換の効果(行列は原点を中心とする拡大。図では左下の角を原点としている)

変換の合成

複数の変換は行列の積で合成できる。右から順に適用される点に注意。

変換の合成

スケール → 回転 → 平行移動 の順に適用する場合:

$$M = T \cdot R \cdot S$$ $$\mathbf{p}' = M \mathbf{p} = T(R(S \mathbf{p}))$$
元 → S 拡大 → R 回転 → T 移動
図5. 幾何変換の合成(スケール→回転→平行移動)

重要: 行列の積は一般には可換でない($AB \neq BA$ となりうる)。変換の順序を変えると結果が変わることがある(平行移動どうしのように可換な組もある)。

3D変換行列

3D空間での変換は $4 \times 4$ 行列で表現される。

3D平行移動

$$T = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix}$$

3D回転行列

X軸周り(ロール):

$$R_x(\theta) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}$$

Y軸周り(ピッチ):

$$R_y(\theta) = \begin{pmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}$$

Z軸周り(ヨー):

$$R_z(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}$$
X・Y・Z 各軸まわりの回転を1枚にまとめた3D図。各軸の周りに右ねじ方向の回転矢印を描く Y軸 X軸 Z軸
図6. X・Y・Z 軸まわりの回転 — 各オレンジ矢印は右ねじの向き(軸の正の向きの先から原点を見ると反時計回り)

注意(オイラー角の限界): Roll・Pitch・Yaw は航空機や船舶の姿勢で使う呼び方で、上の X=ロール・Y=ピッチ・Z=ヨーの対応は、機体座標(X 前方・Y 右・Z 下=鉛直)での約束。座標の取り方が変わると対応も変わる(例:Y を上に取る CG では、鉛直の Y 軸まわりがヨー)。CV/CG では単に X 軸・Y 軸・Z 軸回りの回転と呼ぶことも多い。3つの回転を合成する順序(例: $R = R_z(\text{yaw})\,R_y(\text{pitch})\,R_x(\text{roll})$)は慣例により異なる。またオイラー角による表現にはジンバルロック(上の回転順序ではピッチが ±90° のとき、ロールとヨーの効果が区別できなくなる。失われるのは角度による表し方の自由度で、姿勢そのものは 3 自由度のまま)があり、滑らかな補間や特異点回避が必要な場面ではクォータニオンや回転行列を直接扱うことが多い。

剛体変換と相似変換

剛体変換(Rigid Transformation / Euclidean)

回転+平行移動。形状と大きさと向き(鏡映しない)を保存(6自由度: 3回転 + 3並進)。カメラの外部パラメータ $\mathbf{X}_c = R\,\mathbf{X}_w + \mathbf{t}$ もこの形(次章「カメラ行列」)。

$$\begin{pmatrix} \mathbf{R} & \mathbf{t} \\ \mathbf{0}^T & 1 \end{pmatrix}, \quad \mathbf{R} \in SO(3), \quad \mathbf{t} \in \mathbb{R}^3$$

相似変換(Similarity Transformation)

剛体変換+等方スケール。角度を保存(3Dで7自由度: 剛体6 + 等方スケール1)

$$\begin{pmatrix} s\mathbf{R} & \mathbf{t} \\ \mathbf{0}^T & 1 \end{pmatrix}, \quad s > 0$$

アフィン変換(Affine Transformation)

平行性を保存(3Dで12自由度: 線形部 $\mathbf{A}$ の9 + 並進 $\mathbf{t}$ の3)

$$\begin{pmatrix} \mathbf{A} & \mathbf{t} \\ \mathbf{0}^T & 1 \end{pmatrix}, \quad \mathbf{A} \in \mathbb{R}^{3 \times 3},\ \det\mathbf{A} \neq 0$$

コード例(Python)

import numpy as np

def translation_matrix_2d(tx, ty):
    """2D平行移動行列"""
    return np.array([
        [1, 0, tx],
        [0, 1, ty],
        [0, 0, 1]
    ])

def rotation_matrix_2d(theta):
    """2D回転行列(ラジアン)"""
    c, s = np.cos(theta), np.sin(theta)
    return np.array([
        [c, -s, 0],
        [s,  c, 0],
        [0,  0, 1]
    ])

def scale_matrix_2d(sx, sy):
    """2Dスケール行列"""
    return np.array([
        [sx, 0, 0],
        [0, sy, 0],
        [0,  0, 1]
    ])

# 変換の合成: スケール → 回転 → 平行移動
S = scale_matrix_2d(2, 2)
R = rotation_matrix_2d(np.radians(45))
T = translation_matrix_2d(100, 50)
M = T @ R @ S  # 右から順に適用

# 点の変換
p = np.array([10, 20, 1])  # 同次座標
p_transformed = M @ p  # アフィン変換なので変換後も第 3 成分は 1
print(f"変換後: ({p_transformed[0]:.1f}, {p_transformed[1]:.1f})")  # 変換後: (85.9, 92.4)

# OpenCVでの使用
import cv2
img = cv2.imread("input.png")  # 入力画像(ファイル名は例)
height, width = img.shape[:2]
# 最終行が (0, 0, 1) のアフィン行列なので、先頭 2 行の 2x3 行列で表せる
M_cv = M[:2, :]
img_transformed = cv2.warpAffine(img, M_cv, (width, height))

まとめ

  • 同次座標により、平行移動を含む変換を行列の積で表現
  • 2D変換は $3 \times 3$ 行列、3D変換は $4 \times 4$ 行列
  • 変換の合成は行列の積(右から順に適用)
  • 剛体変換は回転+平行移動(形状保存)
  • カメラの外部パラメータは剛体変換で表される

よくある質問

同次座標で $w = 0$ になるとどういう意味か?

$w = 0$ のときは通常座標 $(x/w, y/w)$ は定義されない(無限大になるのではない)。$(x, y) \neq (0, 0)$ なら、同次座標 $(x, y, 0)$ は「無限遠点」を表し、射影幾何学では平行線がこの点で交わる。たとえば $(1, 0, 0)$ は $x$ 軸に平行な直線たちに共通の無限遠点で、射影変換では有限の計算で扱える。

2D変換行列と3D変換行列でサイズが違うのはなぜか?

2D変換は3成分の同次ベクトル $(x, y, 1)$ を扱うため $3 \times 3$ 行列、3D変換は4成分の同次ベクトル $(X, Y, Z, 1)$ を扱うため $4 \times 4$ 行列となる。一般に $n$ 次元空間のアフィン変換・射影変換は $(n+1) \times (n+1)$ 行列で表現される。