Epipolar Geometry
エピポーラ幾何はシーンの2つの視点の関係を表し、一方の画像にある各点の対応点をもう一方の画像上の直線に限定します。これはステレオ深度推定と3Dマッピングの基礎です。
コンピュータービジョンでは、エピポーラ幾何学は同じシーンを捉えた2つのカメラビュー間の幾何学的な関係を表します。一方の画像内のピクセルは、3D空間における点の候補を結ぶ光線に対応し、その光線をもう一方の画像に投影すると、エピポーラ線と呼ばれる線になります。対応するピクセルはこの線上に存在します。この制約はカメラの内部パラメーターと相対姿勢だけで決まり、シーンの構造には依存しません。そのため、対応点の探索範囲を画像全体から一本の線上に絞り込めます。エピポーラ幾何学は、ステレオビジョン、Structure from Motion、そしてロボットやヘッドセットを制御するVisual SLAMの基盤です。Richard HartleyとAndrew Zissermanによるコンピュータービジョンにおける多視点幾何学は、標準的な参考書です。
エピポール、エピポーラ線、エピポーラ平面#
2つのカメラ中心とシーン内の任意の点が、エピポーラ平面を定義します。カメラ中心を結ぶ線がベースラインで、この線が各画像平面と交わる点がエピポールです。エピポールはもう一方のカメラ中心の画像であり、フレーム外や無限遠に位置することもあります。各エピポーラ平面は各画像をエピポーラ線(エピラインとも呼ばれます)で切るため、画像内のすべてのエピポーラ線はその画像のエピポールを通ります。シーン内の点が最初のカメラの視線上を移動すると、その点の2番目の画像への投影はエピポーラ線上を移動します。
基本行列とエッセンシャル行列#
階数2の3 × 3行列である基本行列Fは、ピクセル座標上の関係全体を表します。同次座標(x, y, 1)で表した対応点xとx′について、エピポーラ制約はx′ᵀ F x = 0です。また、F xの積から、2番目の画像におけるxのエピポーラ線の係数が得られます。カメラキャリブレーションによってカメラが校正されている場合、エッセンシャル行列E = K′ᵀ F Kは、正規化座標で同じ制約を表し、相対回転Rと並進tに分解されてE = [t]ₓ Rとなります。Christopher Longuet-Higginsは1981年にエッセンシャル行列と8点アルゴリズムを導入し、Olivier Faugeras、Quang-Tuan Luong、Richard Hartleyは1990年代初頭に未校正の基本行列を発展させました。Eが定めるのはtの方向だけなので、2つのビューだけではシーンの形状は未知のスケールを除いてしか得られません。スケールは、既知のベースラインまたは物体サイズによって決まります。
どちらの行列も、キーポイント(SIFT特徴など)の対応付けから推定します。RANSACなどのロバスト推定器は、対応点の最小サンプルに行列を繰り返しフィッティングし、最も多くの対応点が支持するモデルを選びます。この例では、100点の2つのビューを作り、20個の対応をランダムな誤対応に置き換え、OpenCVのMAGSAC版RANSACでFを推定します。
import cv2
import numpy as np
rng = np.random.default_rng(0)
K = np.array([[800.0, 0, 320], [0, 800.0, 240], [0, 0, 1]]) # intrinsics shared by both cameras
# 100 scene points seen by two cameras; the second sits 0.5 m to the right, turned 5 degrees
points = rng.uniform([-2, -1, 4], [2, 1, 8], (100, 3))
rvec = np.array([0.0, np.radians(5), 0.0])
tvec = -cv2.Rodrigues(rvec)[0] @ np.array([0.5, 0.0, 0.0])
x1 = cv2.projectPoints(points, np.zeros(3), np.zeros(3), K, None)[0].reshape(-1, 2) + rng.normal(0, 0.5, (100, 2))
x2 = cv2.projectPoints(points, rvec, tvec, K, None)[0].reshape(-1, 2) + rng.normal(0, 0.5, (100, 2))
x2[:20] = rng.uniform([0, 0], [640, 480], (20, 2)) # replace 20 matches with wrong ones
# Robust estimation keeps the matches that agree with a single fundamental matrix
F, inliers = cv2.findFundamentalMat(x1, x2, cv2.USAC_MAGSAC, 1.0)
kept = inliers.ravel() == 1
print(f"Kept {kept[20:].sum()} of 80 true matches and {kept[:20].sum()} of 20 wrong ones")
# Each point in image 1 defines a line a*x + b*y + c = 0 in image 2 that its match must lie on
lines = cv2.computeCorrespondEpilines(x1, 1, F).reshape(-1, 3)
dist = np.abs((lines[:, :2] * x2).sum(axis=1) + lines[:, 2])
print(f"Median distance to epipolar line: {np.median(dist[20:]):.2f} px (true), {np.median(dist[:20]):.0f} px (wrong)")
print(f"Wrong match that survived: {dist[:20][kept[:20]].round(2)} px from its line")正しい対応点80個のうち75個を保持し、誤対応20個のうち19個を除外します。正しい対応点とエピポーラ線との距離の中央値は0.43ピクセルで、これは0.5ピクセルのノイズによるものです。一方、誤対応では144ピクセルです。残った誤対応1個は、たまたま線から0.61ピクセルの位置にあります。制約によって対応点は線上に絞られますが、線上の正しい点を選ぶには見た目の情報が必要なためです。
純粋な回転にはベースラインがなく、単一平面には同等に適合する基本行列が多数存在するため、どちらの場合もホモグラフィーを使います。ホモグラフィーは、平面のビュー間を対応付ける3 × 3行列です。ORB-SLAMは、ホモグラフィーと基本行列を並行してフィッティングし、対応点をよりよく説明する方を選んでマップを初期化します。
整列、視差、深度#
整列では、ステレオ画像ペアをワープし、エピポーラ線を水平にそろえます。これにより、すべての対応点が同じ画像行に配置されます。ステレオマッチャーは各行を探索し、対応点間の列方向のずれである視差dから、焦点距離fとベースラインBを使って深度Z = f × B / dを求めます。これがステレオカメラで深度推定マップを生成する仕組みです。KITTIステレオベンチマークは、自動運転の整列済みシーンを使って、こうしたマッチャーを評価します。
ステレオ以外の用途#
3D再構築では、COLMAPが基本行列、エッセンシャル行列、またはホモグラフィーをフィッティングして画像ペアを検証し、幾何学的な裏付けが不十分なペアや対応点を破棄します。画像マッチングのパイプラインも同じ検証を使って誤対応を除外します。また、画像レジストレーションでは、パノラマ合成のようにシーンが平面の場合やカメラが回転するだけの場合に、前述のホモグラフィーの処理を使います。移動するカメラの2つのフレーム間では、エピポーラ制約は静止した表面にのみ成り立つため、制約に違反するオプティカルフローベクトルから独立して動く物体を検出できます。人間の姿勢を複数のカメラで撮影する場合、三角測量で3D骨格を構築する前に、各ビューの関節が互いのエピポーラ線からどれだけ近いかを使って同一人物を対応付けます。Ultralytics YOLO26の姿勢推定モデルは、各ビューの人物ごとにCOCOの人体キーポイント17個を検出します。










