カメラ行列

Camera Matrix (Projection Matrix)

カメラモデルの全体像

3D空間の点を2D画像に投影するプロセスは、複数の座標変換の連鎖で表される。

ワールド座標 カメラ座標 正規化座標 ピクセル カメラ投影モデル
$\mathbf{P}_w$
$[R\,|\,\mathbf{t}]$
$\mathbf{P}_c$
$(x, y)$
$\div Z_c$
$K$
$(u, v)$
\[\begin{gathered}\mathbf{P}_w=(X,Y,Z,1)^{\top}\\ \mathbf{P}_c = [R\,|\,\mathbf{t}]\,\mathbf{P}_w=(X_c,Y_c,Z_c)^{\top}\\ (x,y)=(X_c/Z_c,\ Y_c/Z_c),\quad (u,v,1)^{\top}=K\,(x,y,1)^{\top}\\ \lambda\,(u,v,1)^{\top} = K[R\,|\,\mathbf{t}]\,\mathbf{P}_w = P\,\mathbf{P}_w\quad(\lambda=Z_c)\end{gathered}\]
$\lambda$: スケール係数(=深度 $Z_c$)
$K$: 内部パラメータ行列 (3×3) − カメラ固有
$[R\,|\,\mathbf{t}]$: 外部パラメータ (3×4) − カメラの位置・姿勢
$P = K[R\,|\,\mathbf{t}]$: カメラ行列 (3×4)
図1. カメラモデルの座標変換パイプライン

座標系の約束(本記事を通じて)

  • ワールド座標系:$Y$ 上向きの右手系(絶対的な3D空間の基準)。
  • カメラ座標系:OpenCV 慣習の右手系で、$X_c$ 右・$Y_c$ 下・$Z_c$ 前方(=光軸=視線方向・深度方向)

$Y_c$ を下向きに取るのは、画像のピクセル座標 $(u, v)$ が左上原点・$v$ 下向きだから。こうすると内部行列 $K$ による $(x, y) \to (u, v)$ の対応が符号反転なしで素直につながる。カメラ前方の点は深度 $Z_c > 0$。

内部パラメータ行列 K

内部パラメータ(intrinsic parameters)はカメラ固有の特性を表す。

内部パラメータ行列

$$K = \begin{pmatrix} f_x & s & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{pmatrix}$$
  • $f_x, f_y$: 焦点距離(ピクセル単位)。$f_x = f/p_x$, $f_y = f/p_y$($p_x, p_y$はピクセルサイズ)
  • $(c_x, c_y)$: 主点(光軸と像平面の交点)
  • $s$: スキュー係数(画素の $u$ 軸と $v$ 軸の非直交性を表す。ほとんどのカメラでは $s=0$ とみなせ、稀に一部のスキャナ画像などで $s \neq 0$ になる)
イメージセンサー パラメータの意味 (正方ピクセルの場合) 典型値: 500〜2000 pixel
$(c_x, c_y)$
$p_x$
$p_y$
$u$
$v$
$f_x = \dfrac{f}{p_x}$ : 水平焦点距離
$f_y = \dfrac{f}{p_y}$ : 垂直焦点距離
通常 $f_x \approx f_y$
図2. 内部パラメータ行列Kとイメージセンサー

例: 一般的なWebカメラ

解像度 640×480、センサーサイズ 3.6mm×2.7mm、焦点距離 f=4mm の場合:

  • $p_x = 3.6/640 = 0.005625$ mm/pixel
  • $f_x = 4/0.005625 \approx 711$ pixel
  • 主点: $(c_x, c_y) \approx (320, 240)$(理想的には画像中心)

外部パラメータ [R|t]

外部パラメータ(extrinsic parameters)はワールド座標系に対するカメラの位置と姿勢を表す。

ワールド原点 カメラ
$X_w$
$Y_w$
$Z_w$
$X_c$
$Y_c$
$Z_c$(光軸)
座標変換 $[R\,|\,\mathbf{t}]$
$\mathbf{P}_c = R\,\mathbf{P}_w + \mathbf{t}$
$R$: 回転行列 (3×3)
$\mathbf{t}$: 並進ベクトル (3×1)
図3. 外部パラメータ $[R\,|\,\mathbf{t}]$ によるワールド→カメラ座標変換。カメラ座標系はワールド座標系に対して回転・並進した姿勢にある(軸が傾いているのが回転の効果。$[R\,|\,\mathbf{t}]$ はワールド座標→カメラ座標の写像で、カメラ自身のワールドでの姿勢はその逆変換=回転 $R^{\top}$・位置 $\mathbf{C}=-R^{\top}\mathbf{t}$)。カメラはワールド原点から離れた位置にあり、原点を背にして前方=光軸 $Z_c$ 方向を向く。$Z_c$ はカメラの視線方向(深度方向)で、$X_c$ 右・$Y_c$ 下の右手系(OpenCV 慣習)。オレンジの破線矢印は、ワールド座標からカメラ座標への座標変換 $[R\,|\,\mathbf{t}]$ を概念的に表す(空間中の移動ベクトルではない)。なおカメラ原点のワールド座標(=カメラ位置)は $\mathbf{C}=-R^{\top}\mathbf{t}$ で、並進 $\mathbf{t}$ 自体は「カメラ座標系でのワールド原点」であり $\mathbf{C}$ とは別物。

なぜ $Z_w$ と $Z_c$ は逆を向く? カメラの $Y_c$ を下向きに取ったことと表裏一体。右手系のまま $Y$ を上(ワールド)から下(カメラ)へ反転するには、$Z$ も一緒に反転させるしかない($Y$ だけ反転すると左手系になってしまう)。つまり $Y_c = -Y_w$ と $Z_c = -Z_w$ は、$X$ 軸まわりの 180° 回転として対で起こる。別の言い方をすれば、「$Z_c$ が前方(シーンの奥)を向く」と「$Y_c$ が下」は OpenCV 慣習のセット。OpenGL 慣習($Y$ 上・カメラは $-Z$ を見る)なら $+Z_c$ は視点側=$Z_w$ と同じ向きになる。

座標系の主な流儀(OpenCV 系・OpenGL 系・DirectX/Unity 系)

本記事の $X_c$ 右・$Y_c$ 下・$Z_c$ 前方は OpenCV 系の流儀。ほかにも主要な流儀があり、分野によって多数派・少数派が入れ替わる。手系(右手系/左手系)も流儀で異なる。

流儀$X$$Y$$+Z$ の向き手系多数派の分野代表例
OpenCV 系前方=光軸(奥。前方の点は $Z_c > 0$)右手系コンピュータビジョン / SLAM / SfM / ロボット視覚OpenCV, COLMAP, 多くの SLAM/SfM, ROS の camera optical frame
OpenGL 系後方(視点側。光軸は $-Z$)右手系CG / レンダリングOpenGL / WebGL, Three.js, 多くのレンダラ
DirectX / Unity 系前方=光軸(奥)左手系一部の CG / ゲームエンジンDirect3D, Unity

$X$ 右・$Y$ 上・$Z$ 奥(前方)は左手系で、DirectX / Unity 系がこれ。OpenCV 系($Y$ 下)から $Y$ だけを上に反転すると、1本反転なので右手系→左手系に変わってこの流儀になる(Unreal も左手系だが $X$ 前方・$Z$ 上と軸の割り当てが異なる)。一方、OpenCV 系と OpenGL 系は $Y,Z$ を同時に反転した関係($X$ 軸まわり 180°)で、ともに右手系。本記事はコンピュータビジョンの文脈なので OpenCV 系を採用している。

外部パラメータ行列

$$[R | \mathbf{t}] = \begin{pmatrix} r_{11} & r_{12} & r_{13} & t_x \\ r_{21} & r_{22} & r_{23} & t_y \\ r_{31} & r_{32} & r_{33} & t_z \end{pmatrix}$$

ワールド座標の点 $\mathbf{P}_w = (X, Y, Z, 1)^T$ をカメラ座標に変換:

$$\mathbf{P}_c = R \mathbf{P}_w + \mathbf{t}$$

注意: $\mathbf{t}$ はワールド座標系でのカメラ位置ではなく、カメラ座標系でのワールド原点の位置。カメラのワールド座標は $\mathbf{C} = -R^T \mathbf{t}$

同じ $[R|\mathbf{t}]$ でも、どちらの座標系を固定して眺めるかで見え方が変わる。図3はワールド基準(ワールドを固定し、その中でのカメラの姿勢を描く。カメラの姿勢・軌跡を扱う用途はこちら)だった。逆にカメラを原点に固定すると、ワールド座標系のほうが $[R|\mathbf{t}]$ で回転・並進して配置される。これは投影式 $\mathbf{P}_c = R\,\mathbf{P}_w + \mathbf{t}$(world→camera)そのもので、レンダリングのビュー行列に対応する。

カメラ(固定) ワールド座標系
$\mathbf{P}_c = R\,\mathbf{P}_w + \mathbf{t}$
カメラを固定 → ワールドが $[R\,|\,\mathbf{t}]$ で配置
座標変換 $[R\,|\,\mathbf{t}]$
$X_c$
$Y_c$
$Z_c$(光軸)
$X_w$
$Y_w$
$Z_w$
図4. 同じ $[R\,|\,\mathbf{t}]$ を「カメラ基準」で見た図(図3とは逆に、カメラの側を固定して眺めたもの)。カメラを原点に固定すると、ワールド座標系が回転 $R$・並進 $\mathbf{t}$ で配置される(ワールド原点はカメラ座標系で位置 $\mathbf{t}$)。これは投影式 $\mathbf{P}_c = R\,\mathbf{P}_w + \mathbf{t}$(world→camera)そのもので、レンダリングのビュー行列に対応する。カメラの姿勢(camera→world)は逆変換 $R^{\top},\ \mathbf{C}=-R^{\top}\mathbf{t}$ で得られる。

カメラ行列 P

内部パラメータと外部パラメータを合わせたものがカメラ行列(射影行列)である。

カメラ行列(Projection Matrix)

$$P = K [R | \mathbf{t}]$$

$P$ は $3 \times 4$ 行列で、11自由度(内部5 + 外部6)を持つ。

投影式

$$\lambda \begin{pmatrix} u \\ v \\ 1 \end{pmatrix} = P \begin{pmatrix} X \\ Y \\ Z \\ 1 \end{pmatrix}$$

$\lambda = Z_c$(カメラ座標系での深度)はスケール係数

展開すると:

$$u = \dfrac{p_{11}X + p_{12}Y + p_{13}Z + p_{14}}{p_{31}X + p_{32}Y + p_{33}Z + p_{34}}$$ $$v = \dfrac{p_{21}X + p_{22}Y + p_{23}Z + p_{24}}{p_{31}X + p_{32}Y + p_{33}Z + p_{34}}$$

よくある混乱ポイント(落とし穴)

同じ記号や名前が資料ごとに別物を指すことが多く、これがコンピュータビジョン学習を難しくする最大の要因の一つ。ネットの資料が矛盾して見えるのは、多くが自分の採用した規約を明記していないため。まず、名前そのものが分野で衝突する

呼び名ある文脈での意味別の文脈での意味
カメラ行列(camera matrix)OpenCV:内部行列 $K$(3×3)。関数引数も cameraMatrixHartley–Zisserman(MVG):射影行列 $P = K[R\,|\,\mathbf{t}]$(3×4)
射影行列(projection matrix)CV:$P = K[R\,|\,\mathbf{t}]$(3×4、3D→2D)OpenGL:透視投影行列(4×4、視錐台→クリップ空間)で別物
外部パラメータ / ビュー行列world→camera の $[R\,|\,\mathbf{t}]$「カメラ姿勢」= camera→world(逆変換)を指すことも

名前が同じでも指す行列が違うので、まず「その資料での定義」を確認するのが安全。次に、式の書き方・規約の食い違い:

  • 行ベクトル vs 列ベクトル:列ベクトル規約 $\mathbf{p}' = R\,\mathbf{p}$(本記事・数学・OpenCV)か、行ベクトル規約 $\mathbf{p}' = \mathbf{p}\,R$(DirectX 等)か。転置で式が丸ごと入れ替わる。さらに行列のメモリ配置が row-major(C / NumPy)か column-major(OpenGL / Eigen 既定)かも別問題。
  • 変換の向き:world→camera($R,\ \mathbf{t}$)か camera→world($R^{\top},\ \mathbf{C} = -R^{\top}\mathbf{t}$)か。同じ「外部パラメータ」でも逆を指すことがある(図3・図4)。
  • 回転の能動 / 受動:点を回す(active)か座標系を回す(passive)か。$R$ と $R^{\top}$ が入れ替わる。
  • 座標系の手系・軸:右手 / 左手・$Y$ 上 / 下・$Z$ 前 / 後(上の比較表)。
  • 画像座標:原点が左上(CV)か左下(OpenGL)か。ピクセル中心が整数座標か $+0.5$ か。
  • 回転の表し方:オイラー角の回転順序、クォータニオンの $w$ 先頭 / 末尾・Hamilton / JPL 規約。

座標系のまとめ

ワールド座標系 絶対的な3D空間 カメラ座標系 カメラ中心が原点 正規化画像座標 焦点距離1の仮想平面 ピクセル座標 変換の順序: 自由度: 外部: 6 (3回転+3並進) 合計: 11
$(X, Y, Z)$
$[R\,|\,\mathbf{t}]$
$(X_c, Y_c, Z_c)$
$\div Z_c$
$(x, y) = (X_c/Z_c,\; Y_c/Z_c)$
$K$
$(u, v)$
1. 剛体変換 $[R\,|\,\mathbf{t}]$
2. 透視投影 $(\div Z)$
3. 内部行列 $K$
内部: 5 $(f_x, f_y, c_x, c_y, s)$
図5. ワールド・カメラ・画像・ピクセル座標系の関係

コード例(Python)

import numpy as np

# 内部パラメータ
fx, fy = 800, 800
cx, cy = 320, 240
K = np.array([
    [fx,  0, cx],
    [ 0, fy, cy],
    [ 0,  0,  1]
])

# 外部パラメータ(例: Z軸周りに45度回転、(1,0,5)に移動)
theta = np.radians(45)
R = np.array([
    [np.cos(theta), -np.sin(theta), 0],
    [np.sin(theta),  np.cos(theta), 0],
    [0,              0,             1]
])
t = np.array([[1], [0], [5]])

# 外部パラメータ行列 [R|t]
Rt = np.hstack([R, t])

# カメラ行列 P = K[R|t]
P = K @ Rt
print("Camera Matrix P:")
print(P)

# 3D点の投影
P_world = np.array([0, 0, 0, 1])  # ワールド原点
p_homogeneous = P @ P_world
p = p_homogeneous[:2] / p_homogeneous[2]
print(f"Projected point: ({p[0]:.1f}, {p[1]:.1f})")

# カメラ位置(ワールド座標)
camera_position = -R.T @ t.flatten()
print(f"Camera position: {camera_position}")

# OpenCVでの使用
import cv2
# cv2.projectPoints() を使用する場合
rvec, _ = cv2.Rodrigues(R)  # 回転行列 → 回転ベクトル
tvec = t  # tvec はカメラ位置ではなく、カメラ座標系でのワールド原点の位置(= t)
points_3d = np.array([[0, 0, 0]], dtype=np.float32)
points_2d, _ = cv2.projectPoints(points_3d, rvec, tvec, K, None)

まとめ

  • カメラ行列 $P = K[R|\mathbf{t}]$ は3D→2D投影を表す $3 \times 4$ 行列
  • 内部パラメータ $K$: 焦点距離、主点(カメラ固有)
  • 外部パラメータ $[R|\mathbf{t}]$: カメラの位置・姿勢
  • 投影は同次座標で線形に表現できる
  • 内部パラメータ $K$ はカメラキャリブレーションで求める(詳細は後続の章)。まずは次章の練習問題で理解を確認しよう