Epipolar Geometry
에피폴라 기하학은 장면의 두 시점 간 관계를 설명하며, 한 점의 대응점을 다른 이미지의 한 직선으로 제한합니다. 이는 스테레오 깊이 추정과 3D 매핑의 기반입니다.
컴퓨터 비전에서 에피폴라 기하학은 동일한 장면을 촬영한 두 카메라 뷰 사이의 기하학적 관계를 설명합니다. 한 이미지의 픽셀은 가능한 3D 점들의 광선에 대응하고, 해당 광선이 두 번째 이미지에 투영되면 에피폴라선이라는 직선이 되며, 픽셀의 대응점은 이 선 위에 있어야 합니다. 이 제약 조건은 장면 구조와 무관하게 카메라의 내부 파라미터와 상대 포즈에만 좌우되며, 대응점을 찾는 검색 범위를 이미지 전체에서 하나의 선으로 좁힙니다. 따라서 에피폴라 기하학은 스테레오 비전, 구조 복원, 그리고 로봇과 헤드셋을 안내하는 시각 SLAM의 기반입니다. Richard Hartley와 Andrew Zisserman의 저서 컴퓨터 비전의 다중 뷰 기하학은 표준 참고 문헌입니다.
에피폴, 에피폴라선 및 에피폴라 평면#
두 카메라 중심과 장면의 임의의 한 점은 에피폴라 평면을 정의합니다. 두 카메라 중심을 잇는 선은 기준선이며, 이 선은 각 이미지 평면을 관통하는데, 이 지점이 에피폴라점, 즉 다른 카메라 중심의 이미지입니다. 에피폴라점은 프레임 밖이나 무한대에 있을 수 있습니다. 각 에피폴라 평면은 각 이미지를 절단해 에피폴라선, 즉 에피라인을 만들므로 한 이미지의 모든 에피폴라선은 해당 이미지의 에피폴라점을 통과합니다. 장면의 한 점이 첫 번째 카메라의 시선 광선을 따라 이동하면 두 번째 이미지에 투영된 점은 에피폴라선을 따라 이동합니다.
기본 행렬과 필수 행렬#
랭크가 2인 3 × 3 행렬인 기본 행렬 F는 픽셀 좌표에서 전체 관계를 나타냅니다. 동차 좌표 형식 (x, y, 1)으로 표현된 대응점 x와 x′에 대해 에피폴라 제약 조건은 x′ᵀ F x = 0이며, 곱 F x는 두 번째 이미지에서 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의 방향만 결정하므로 두 뷰만으로는 장면의 형태를 미지의 스케일 범위에서만 구할 수 있으며, 알려진 기준선이나 객체 크기로 스케일을 정합니다.
두 행렬은 키포인트 및 SIFT 특징과 같은 대응점으로 추정합니다. 이때 RANSAC과 같은 강건한 추정기는 최소 개수의 대응점 표본으로 행렬을 반복해서 적합하고, 가장 많은 대응점이 일치하는 모델을 선택합니다. 다음 예제는 100개 점의 두 뷰를 만들고, 20개 대응점을 무작위 오점으로 대체한 뒤, OpenCV의 RANSAC MAGSAC 변형을 사용해 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픽셀입니다. 살아남은 잘못된 대응점 하나는 우연히 해당 선에서 0.61픽셀 떨어져 있습니다. 제약 조건은 대응점을 선 위에 한정할 뿐이고 선을 따라 올바른 점을 선택하는 것은 외관 정보만 가능하기 때문입니다.
순수 회전에는 기준선이 없으며, 단일 평면에는 여러 기본 행렬이 동일하게 들어맞으므로 두 경우 모두 뷰 간 평면의 3 × 3 매핑인 호모그래피를 사용합니다. ORB-SLAM은 호모그래피와 기본 행렬을 동시에 적합해 맵을 초기화하고, 대응점을 더 잘 설명하는 행렬을 선택합니다.
정류, 시차 및 깊이#
정류는 스테레오 이미지 쌍을 워핑해 에피폴라선이 수평으로 정렬되도록 하며, 모든 대응점을 동일한 이미지 행에 배치합니다. 그런 다음 스테레오 매처는 각 행을 검색하며, 대응점 사이의 열 방향 오프셋인 시차 d를 사용해 초점 거리 f와 기준선 B에 대해 깊이 Z = f × B / d를 구합니다. 이 방식으로 스테레오 장비가 깊이 추정 맵을 생성합니다. KITTI 스테레오 벤치마크는 자율주행 장면을 정류한 데이터에서 매처 성능을 평가합니다.
스테레오 이외의 활용 분야#
3D 재구성에서 COLMAP은 기본 행렬, 필수 행렬 또는 호모그래피 행렬을 적합해 모든 이미지 쌍을 검증하고, 기하학적 근거가 충분하지 않은 쌍과 대응점을 제외합니다. 이미지 매칭 파이프라인은 같은 검증으로 잘못된 대응 관계를 거부하며, 이미지 정합은 장면이 평면이거나 카메라가 회전만 하는 경우, 예를 들어 파노라마 스티칭에서 위의 호모그래피 방식을 사용합니다. 움직이는 카메라의 두 프레임 사이에서 에피폴라 제약 조건은 정적 표면에만 적용되므로, 이 조건을 위반하는 광학 흐름 벡터는 독립적으로 움직이는 객체를 드러냅니다. 사람 자세의 멀티카메라 촬영에서는 삼각 측량으로 3D 스켈레톤을 계산하기 전에 각 관절이 서로의 에피폴라선에 얼마나 가까운지를 기준으로 뷰 간 동일 인물을 대응시킵니다. Ultralytics YOLO26 포즈 추정 모델은 각 뷰에서 사람마다 COCO 신체 키포인트 17개를 제공합니다.










