18장 카메라 모형과 보정
시각(vision)은 실세계에서 빛을 탐지하는 것으로 시작한다. 빛은 광원(예: 전구 또는 태양)에서 퍼져 나와 물체에 부딪힐 때까지 공간을 여행한다. 빛이 물체에 부딪칠 때 많은 빛이 흡수되는데, 물체의 색으로 인식되지 않는 것은 흡수되지 않는다. 우리의 눈 또는 카메라에 도달한 반사된 빛은 망막(또는 영상)에 수집된다. 실제 전산시각에서 물체로부터 눈이나 카메라의 렌즈를 거쳐 망막이나 영상까지 전달되는 빛의 이동에 대한 배치의 기하학은 매우 중요하다.
이 과정을 설명하기 위한 간단하지만 유용한 모형으로 핀홀 카메라 모형(pinhole camera model) 이 있다. 핀홀은 중심에 작은 구멍이 있는 가상의 벽으로 중앙의 작은 구멍을 통과하는 빛을 제외한 모든 빛을 차단한다. 이 장에서는 빛 투영의 기초 기하학을 이해하기 위한 핀홀 카메라 모형에 대해 다룰 것이다. 아쉽게도 실제 핀홀은 빠른 노출을 위해 충분한 빛을 모으지 않기 때문에 영상을 만드는 좋은 방법은 아니다. 그래서 눈과 카메라는 더 많은 빛을 모으기 위해 렌즈를 사용한다. 그러나 렌즈로 더 많은 빛을 모으는 방법은 핀홀 모형의 단순한 기하학으로는 설명이 불가능할 뿐만 아니라 렌즈 자체에서 왜곡이 발생한다는 단점이 있다.
먼저 카메라 모형과 렌즈 왜곡의 원인을 살펴보자. 여기에서는 카메라의 기본 동작과 다양한 왜곡 및 수정에 의한 효과를 찾아낼 수 있는 수학적 도구인 호모그래피 변환(homography transform) 을 살펴본다.
18.1 카메라 모형
먼저 카메라의 가장 단순한 모형인 핀홀을 살펴보자. 이 모형에서는 빛이 단순히 장면이나 먼 물체에서 들어오되, 그 장면의 특정 지점에서 단 하나의 광선만 핀홀에 들어오는 것으로 가정한다. 물리적인 핀홀 카메라에서는 이 지점이 촬영 영상 위에 '투영'된다. 결과적으로 이 영상 평면(투영평면이라고도 함)의 영상이 항상 또렷하다(초점이 맞추어 있다). 영상의 크기는 떨어져 있는 대상의 크기를 기준으로 카메라의 단일 변수인 초점 거리에 의해 정해진다. 이상적인 핀홀 카메라의 경우, 핀홀 구멍에서 영상 평면까지의 거리가 정확한 초점거리다.
아래 그림(Fig 1)에서 각 기호의 의미는 다음과 같다.
| 기호 | 의미 |
| $f$ | 초점 거리 (focal length) |
| $Z$ | 카메라(핀홀)에서 대상까지의 거리 |
| $X$ | 대상의 실제 크기 |
| $x$ | 영상 평면에 맺힌 상의 크기 (도립) |
[Fig 1] 핀홀 카메라 모형 — 전통적 표현 (영상이 핀홀 뒤에 맺힘)
물체 평면 핀홀 영상 평면
│ │ │
│ │ │ ← 광학축
──────┤ │ ─────┤
A │ │ │
┊ X │ ────────────────● │
┊ │ ╲ │
──────┤ ╲ ─────┤
│ ╲─────────── A' │
│ │ ┊ x │ (도립상)
│ │ │
│◄──────── Z ────────►◄───── f ────────►│
닮은꼴 삼각형 관계: -x/f = X/Z
$$ -\frac{x}{f} = \frac{X}{Z} $$
그림 Fig 2처럼 핀홀 평면과 영상 평면을 맞교환한다. 주요 차이점은 대상이 이제 오른쪽에 나타난다는 점이다. 핀홀의 점(구멍)은 투영의 중심(projection center) 으로 재해석된다. 이렇게 사물을 보면 모든 광선이 멀리 떨어진 대상의 한 점에서부터 출발해 투영의 중심을 향해 나아간다. 영상면과 광학축의 교차점을 주점(principal point) 이라고 한다. 이 새로운 정면 영상평면은 이전의 투영 평면 또는 영상평면과 동일하다. 따라서 이 새로운 평면에 나타나는 대상의 영상은 Fig 1의 영상 평면에 나타나는 것과 정확히 같은 크기가 된다. 이 광선이 영상 평면과 교차되어 영상이 생성된다. 이 영상은 투영 중심으로부터 정확히 거리 $f$만큼 떨어진 곳에 생성된다. 이것은 닮은꼴 삼각형 관계 $x/f = X/Z$를 전보다 더 직접적으로 나타낸다.
[Fig 2] 핀홀 카메라 모형 — 정면 영상 평면 표현 (수학적 등가 모형)
영상 평면 투영 중심 물체 평면
│ (핀홀) │
│ │ │
──────┤ │ ──────┤
A' │ │ A │
┊ x ├─────────────────●──────────────────┊ X │
│ │ │
──────┤ │ ──────┤
│ │ │
│◄───── f ────────►◄──────── Z ────────────►│
주점(Principal Point): 광학축과 영상 평면의 교점
닮은꼴 삼각형 관계: x/f = X/Z (영상이 뒤집히지 않음)
Fig 1과 달리 영상이 정립(正立)으로 나타나며, 동일한 닮음비 $x/f = X/Z$ 가 성립한다.
주점이 영상면의 중심과 같으려면, 사람이 핀셋과 접착제로 카메라에 영상센서(imager)를 미크론 단위의 정확도로 부착할 수 있어야 한다. 사실, 일반적으로 영상센서 칩의 중심은 광학축 위에 있지 않다. 따라서 투영 화면에 있는 중심 좌표의 편위(광학축에서부터 멀어지는 방향)를 모형화하기 위해 두 개의 새로운 변수 $c_x, c_y$를 도입한다. 그 결과 $(X,Y,Z)$에 위치한 물리적 세계의 점 $\vec{Q}$는 $(x_s, y_s)$에 위치한 촬영 장치의 화소에 다음과 같이 투영되는 비교적 단순한 모형을 얻을 수 있다.
$$ x_s = f_x \frac{X}{Z} + c_x \qquad y_s = f_y \frac{Y}{Z} + c_y $$
아랫첨자 s는 sensor(센서) 의 약자로, 이 좌표가 카메라 이미지 센서 위의 픽셀 위치임을 의미한다.
쉽게 말하면, 3D 세계의 점 $(X, Y, Z)$을 카메라로 찍었을 때 실제 이미지 센서(촬영 소자) 위의 어느 픽셀에 찍히는지 를 나타내는 좌표다.
비유로 이해하기
| 좌표 | 의미 | 단위 |
| $(X, Y, Z)$ | 현실 세계의 점 위치 | m, cm 등 |
| $(x_s, y_s)$ | 카메라 사진 위의 픽셀 좌표 | pixel |
예: 눈앞에 사과가 있다면
- 사과의 실제 위치 → $(X, Y, Z)$
- 카메라로 찍었을 때 사진의 300번째 열, 200번째 행 픽셀 → $(x_s, y_s) = (300, 200)$
s첨자의 핵심 의미: "이건 렌즈를 통해 센서판 위에 맺힌 좌표야" 를 강조하는 표시
수식 $x_s = f_x \dfrac{X}{Z} + c_x$ 는 "현실의 점을 픽셀 좌표로 변환하는 공식" 이다.
카메라 내부 변수 직관적 설명
상황 설정
눈앞 1m 거리에 친구가 서 있고, 그걸 카메라로 찍는다고 상상해보자.
$f_x$, $f_y$ — 확대 배율 ("줌 배율")
현실 물체가 카메라 픽셀 좌표에 얼마나 크게 찍히는지를 결정하는 값.
친구가 현실에서 1m 옆으로 이동했을 때 사진에서 몇 픽셀 움직여 보이냐를 결정한다.
| $f_x$ 값 | 현실 1m 이동 시 사진에서 이동량 |
| 500 | 500픽셀 이동 |
| 1000 | 1000픽셀 이동 (더 확대된 느낌) |
$f_x$와 $f_y$가 따로 존재하는 이유: 저가 카메라는 픽셀이 정사각형이 아니라 직사각형이라서 가로/세로 방향 확대 비율이 미묘하게 다를 수 있기 때문이다.
실제로는 $f_x = f \cdot \dfrac{1}{s_x}$, $f_y = f \cdot \dfrac{1}{s_y}$ 형태이지만, 카메라를 분해하지 않고서는 $f$와 $s_x$를 개별적으로 측정할 수 없으므로 두 값의 곱인 $f_x$, $f_y$ 만을 보정(calibration)으로 구한다.
$c_x$, $c_y$ — 주점 위치 ("사진의 광학 중심")
카메라 렌즈의 광학적 중심이 사진의 정중앙에 딱 맞지 않을 수 있다. 공장에서 렌즈와 센서를 조립할 때 미세하게 어긋나기 때문이다.
이상적인 경우: 실제:
┌──────────┐ ┌──────────┐
│ │ │ │
│ ✕ │ │ ✕ │ ← 중심이 살짝 오른쪽으로 밀림
│ (중심) │ │ (중심) │
└──────────┘ └──────────┘
c_x = 사진너비/2 c_x = 사진너비/2 + 살짝
$c_x, c_y$는 렌즈 광학 중심이 픽셀 기준으로 어느 위치에 있는지 를 나타내는 값
카메라 내부 파라미터(Intrinsic Parameters) 요약
| 변수 | 직관적 의미 |
| $f_x$ | 가로 방향 줌 배율 (픽셀 단위) |
| $f_y$ | 세로 방향 줌 배율 (픽셀 단위) |
| $c_x$ | 렌즈 광학 중심의 가로 픽셀 위치 |
| $c_y$ | 렌즈 광학 중심의 세로 픽셀 위치 |
이 4개를 묶어서 카메라 내부 파라미터(intrinsic parameters) 라고 부르며, 카메라마다 고유한 값이다. 카메라 보정(calibration) 을 통해 이 값들을 측정한다.
18.1.1 투영기하학의 기초
실제 세계의 좌표 $(X_i, Y_i, Z_i)$에 있는 점들의 그룹 $\vec{Q}$를 투영화면의 좌표 $(x_i, y_i)$에 있는 점들로 사상(mapping)하는 과정을 투영변환(projective transform) 이라고 한다. 이런 변환 작업을 할 때는 동차좌표(homogeneous coordinate) 를 사용하는 것이 편리하다.
n차원의 투영공간에 있는 한 점에 대한 동차좌표는 일반적으로 n+1차원의 벡터로 표현된다. (예: $(x,y,z)$는 $(x,y,z,w)$가 된다.) 여기서는 영상평면이 투영공간이고 2차원을 가지므로, 그 평면상의 점을 3차원 벡터 $\vec{q} = (q_1, q_2, q_3)$로 나타낸다. 투영 공간에서 비례하는 값을 갖는 모든 점들이 동일하다는 것을 생각하면 $q_3$로 나눠 실제 화소좌표를 얻을 수 있다. 이렇게 하면 카메라를 정의하는 변수(즉, $f_x, f_y, c_x, c_y$)를 하나의 3×3 행렬로 나타낼 수 있다. 이를 카메라 내부변수 행렬 이라고 부른다. 실제 세계의 점을 카메라에 투영한 결과는 다음과 같은 간단한 형태로 요약된다.
$$ \vec{q} = M\vec{Q} $$
$$ \vec{q} = \begin{bmatrix} x \\ y \\ z \end{bmatrix}, \quad M = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}, \quad \vec{Q} = \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} $$
행렬 $M$을 카메라 내부변수 행렬(Camera Intrinsic Matrix) 이라고 한다.
여기서 $q=Z$이고 점 $\vec{q}$는 동차좌표이기 때문에 이전의 정의처럼 $w$(또는 $Z$)로 나누면 실제 좌표를 얻을 수 있다. 이제 핀홀 뒤에 있는 투사화면상 거꾸로 된 영상이 아니라 핀홀 앞에 있는 투영평면상 반전되지 않은 영상을 보고 있기 때문에 음수 부호는 사라진다.
🤔 왜 동차좌표를 쓰는가?
핵심 문제는 이거예요. 3D 점 $(X, Y, Z)$를 2D 픽셀 $(x_s, y_s)$로 변환하는 공식이:
$x_s = f_x \frac{X}{Z} + c_x$
이렇게 나눗셈이 포함되어 있어요. 나눗셈은 행렬 곱셈으로 표현이 안 돼요. 그래서 수학자들이 꼼수를 고안했는데, 그게 바로 동차좌표예요.
💡 동차좌표의 아이디어
나눗셈을 나중으로 미루는 거예요.
일단 나누지 말고 행렬 곱셈을 먼저 한 다음, 마지막에 한 번만 나누자!
관련해서 OpenCV 라이브러리 함수인
cv::convertPointsToHomogeneous()와cv::convertPoints FromHomogeneous()함수를 사용하면 동차좌표로 변환하거나, 동차좌표에서 변환할 수 있다.다음은 이 함수들의 원형(prototype)이다.
void cv::convertPointsToHomogeneous(
cv::InputArray src, // N 차원의 점에 대한 입력 벡터
cv::OutputArray dst // N+l 차원의 점에 대한 결과 벡터
);
void cv::convertPointsFromHomogeneous(
cv::InputArray src, // N 차원의 점에 대한 입력 벡터
cv::OutputArray dst // N-l 차원의 점에 대한 결과 벡터
);
첫번째 함수는 (일반적인 표현에서) $N$ 차원 점들의 벡터를 입력받아서 그 벡터로 부터 $N+1$ 차원의 벡터를 만들어낸다. 추가된 차원과 연관된 새로 구성된 벡터의 모든 요소는 1로 설정된다. 그 결과는 다음과 같다.
$\vec{Di} = (s{i,0}, s{i, 1}, ..., s{i, N-1}, 1)$
두번째 함수는 동차좌표로부터 다시 변환한다. $N$ 차원 점들의 입력벡터가 주어지면 먼저 각 점의 모든 구성요소를 마지막 구성요소값으로 나눈후, 1이 된 그 마지막 구성요소를 버리고 $N-1$차원 점들의 벡터를 만든다. 그 결과는 다음과 같다.
$\vec{Di} = (\frac{s{i,0}}{s{i, N-1}}, \frac{s{i,1}}{s{i, N-1}}, ..., \frac{s{i,N-2}}{s_{i, N-1}})$
이상적인 핀홀을 사용하면 비전 분야에서는 일부 3차원 기하학을 위한 유용한 모형을 만들 수 있다. 그러나 실제로는 핀홀을 통과하는 빛이 거의 없어 어떤 센서를 사용하든 충분한 빛이 축적될 때까지 기다려야 하기 때문에 그러한 배치로는 촬영이 매우 오래 걸린다. 카메라가 더 빠른 속도로 영상을 획득하려면 더 넓은 영역에 서 많은 빛을 모으고 빛이 투사 지점에서 수렴되도록 빛을 굴절시켜야 한다(즉, 빛 을 투사지점에 집중시켜야 한다.) 이러한 용도로 렌즈가 사용된다. 렌즈는 한 지점에 많은 양의 빛을 집중시켜 빠르게 촬영할 수 있지만, 그로 인해 발생하는 왜곡은 감수해야 한다.
18.1.2 로드리게스 변환
3차원 공간을 다룰 때는 공간에서 3x3 행렬로 표현한 회전을 주로 사용한다. 벡터를 이 행렬에 곱하는 것과 벡터를 특정한 방향으로 회전시키는 것이 서로 같기 때문에 보통 이 표현 방식이 가장 편리하다. 단점은 어느 3x3 행렬이 어떤 회전을 하는지 직관적으로 알기 어렵다는 것이다. 따라서 이 장에서는 일부 OpenCV 함수에서 사용되는 회전에 대한 대체 표현과 이 대체 표현으로 변환하는 유용한 함수를 소개한다.
축-각도 표현 (Axis-Angle Representation) 직관
3D 공간에서 어떤 회전이든 하나의 축(axis)과 그 축 주위의 회전 각도(angle) 로 표현할 수 있다.
z
│ ← 회전축 방향 (단위벡터 r_hat)
│ ↗
│ / θ = 회전 각도 (벡터의 크기)
│/
└──────── x
/
y
로드리게스 벡터 $\vec{r}$은 이 두 정보를 하나의 벡터에 압축한다.
$$ \vec{r} = \theta \cdot \hat{r} $$
| 의미 | 수식 | 설명 | ||
| 회전 각도 | $\theta = \ | \vec{r}\ | $ | 벡터의 크기(길이) |
| 회전 축 방향 | $\hat{r} = \dfrac{\vec{r}}{\ | \vec{r}\ | }$ | 벡터를 정규화한 단위벡터 |
예: $\vec{r} = \begin{bmatrix} 0 \ 0 \ \pi/2 \end{bmatrix}$ 이면, z축 주위로 90° 회전을 의미한다.
두 가지 회전 표현 비교
| 표현 방식 | 형태 | 장점 | 단점 |
| 회전 행렬 $R$ | 3×3 행렬 (9개 원소) | 계산이 빠름, 벡터에 바로 곱할 수 있음 | 직관적으로 이해하기 어려움 |
| 로드리게스 벡터 $\vec{r}$ | 3×1 벡터 (3개 원소) | 직관적, 저장 공간 작음 | 행렬 연산 불가, 변환 필요 |
벡터 → 회전 행렬 변환 공식
$\vec{r} = \begin{bmatrix} r_x & r_y & r_z \end{bmatrix}^T$, $\theta = |\vec{r}|$, $\hat{r} = \vec{r}/\theta$ 로 정의한다.
$$ R = \cos\theta \, I_3 + (1-\cos\theta)\,\hat{r}\hat{r}^T + \sin\theta \begin{bmatrix} 0 & -\hat{r}_z & \hat{r}_y \\ \hat{r}_z & 0 & -\hat{r}_x \\ -\hat{r}_y & \hat{r}_x & 0 \end{bmatrix} $$
수식 3항 분해:
| 항 | 역할 |
| $\cos\theta \, I_3$ | 회전 전 기존 성분 유지 (cosine 비율) |
| $(1-\cos\theta)\,\hat{r}\hat{r}^T$ | 회전 축 방향 성분 보정 |
| $\sin\theta [\hat{r}]_\times$ | 축 주위로 회전시키는 성분 (반대칭 행렬) |
마지막 항의 $\begin{bmatrix} 0 & -\hat{r}_z & \hat{r}_y \ \hat{r}_z & 0 & -\hat{r}_x \ -\hat{r}_y & \hat{r}_x & 0 \end{bmatrix}$ 은 반대칭 행렬(skew-symmetric matrix) 로, 외적(cross product) 연산을 행렬로 표현한 것이다.
회전 행렬 → 벡터 역변환 공식
회전 행렬 $R$에서 축-크기 표현으로 되돌리려면 $R$의 반대칭 부분을 이용한다.
$$ \sin\theta \begin{bmatrix} 0 & -\hat{r}_z & \hat{r}_y \\ \hat{r}_z & 0 & -\hat{r}_x \\ -\hat{r}_y & \hat{r}_x & 0 \end{bmatrix} = \frac{R - R^T}{2} $$
$$ \theta = \arccos\left(\frac{\mathrm{trace}(R) - 1}{2}\right) $$
trace$(R)$는 행렬의 대각 원소의 합, 즉 $R{00} + R{11} + R_{22}$이다.
주요 성질
- 회전 행렬 $R$은 항상 직교 행렬(orthogonal matrix): $R^T R = I$
- $\det(R) = +1$ (반사 없이 순수 회전)
- $\vec{r} = \vec{0}$ (영벡터)이면 $R = I$ (항등 변환, 회전 없음)
- $\vec{r}$과 $-\vec{r}$은 반대 방향 회전 (반시계 ↔ 시계)
OpenCV 함수: cv::Rodrigues()
따라서 계산에 가장 편리한 표현(행렬 표현)과 사람의 뇌가 이해하기에 좀 더 쉬운 표현(로드리게스 표현)을 사용할 수 있다. OpenCV는 표현끼리 서로 변환할 수 있는 함수를 제공한다.
void cv::Rodrigues(
cv::InputArray src, // 입력: 회전 벡터(3×1) 또는 회전 행렬(3×3)
cv::OutputArray dst, // 출력: 회전 행렬(3×3) 또는 회전 벡터(3×1)
cv::OutputArray jacobian = cv::noArray() // (선택) 자코비안 행렬 (3×9 또는 9×3)
);
입출력 방향:
src | dst | 변환 방향 |
| 3×1 회전 벡터 $\vec{r}$ | 3×3 회전 행렬 $R$ | 벡터 → 행렬 |
| 3×3 회전 행렬 $R$ | 3×1 회전 벡터 $\vec{r}$ | 행렬 → 벡터 |
jacobian인수가cv::noArray()가 아니면, 입력 배열의 각 원소에 대한 출력 배열의 편미분으로 채워진 3×9 또는 9×3 자코비안 행렬을 반환한다. 이는 최적화 알고리즘(예: 카메라 보정)에서 활용된다.
Python 예제
import cv2
import numpy as np
# 1) 로드리게스 벡터 → 회전 행렬
rvec = np.array([[0.0], [0.0], [np.pi / 2]]) # z축 주위로 90° 회전
R, jacobian = cv2.Rodrigues(rvec)
print("회전 행렬 R:")
print(np.round(R, 4))
# [[ 0. -1. 0.]
# [ 1. 0. 0.]
# [ 0. 0. 1.]]
# 2) 회전 행렬 → 로드리게스 벡터 (역변환)
rvec_back, _ = cv2.Rodrigues(R)
print("\n역변환 결과 rvec:")
print(np.round(rvec_back, 4))
# [[0. ]
# [0. ]
# [1.5708]] ← π/2 ≈ 1.5708
실제 활용: 카메라 보정의 외부 파라미터
카메라 보정(cv2.calibrateCamera())의 결과로 반환되는 rvecs가 바로 로드리게스 벡터다.
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
# rvecs[i]: i번째 보정 영상에서 카메라와 체스보드 간의 상대 회전 (로드리게스 벡터)
# tvecs[i]: i번째 보정 영상에서 카메라와 체스보드 간의 상대 이동 (평행이동 벡터)
# 회전 행렬로 변환
R, _ = cv2.Rodrigues(rvecs[0])
로드리게스 벡터는 저장 및 최적화 단계에서 3개의 값만 다루면 되므로, 9개 원소의 회전 행렬보다 훨씬 효율적이다.
18.1.3 렌즈에 의한 왜곡
방사왜곡(radial distortions)은 렌즈 모양에 의해 발생하는 반면, 접선왜곡(tangential distortions)은 카메라 조립 과정 전체에서 발생한다.
방사왜곡
실제 카메라의 렌즈는 종종 센서의 가장자리 화소의 위치를 크게 왜곡한다. 이 팽창 현상이 '술통' 또는 '어안' 효과의 원인이다. 일부 렌즈의 경우, 렌즈의 중심에서 멀리 떨어진 광선이 중심에 가까이 있는 광선보다 더 많이 구부러진다.
방사왜곡의 경우, 왜곡이 카메라 센서의 (광학적) 중심에서는 없고 외곽으로 이동할수록 증가한다. 실제로 왜곡은 작으며 $r=0$ 부근의 테일러 급수 전개의 처음 몇항으로 이 특징을 표현할 수 있다.
$$ x_c = x(1+k_1 r^2 + k_2 r^4 + k_3 r^6) \\ y_c = y(1+k_1 r^2 + k_2 r^4 + k_3 r^6) $$
여기서 $(x,y)$는 왜곡된 점에 대해 센서에서 획득한 위치이고, $(x_c, y_c)$는 수정한 결과인 새로운 위치다.
접선왜곡
이 왜곡은 제조 결함으로 렌즈가 촬영 영상평면에 정확히 평행하지 않아 생긴다.
접선 왜곡은 다음과 같은 두 가지 추가 인수 $p_1$과 $p_2$로 최소화된다.
$$ x_c = x + [2p_1 xy + p_2(r^2 + 2x^2)] \\ y_c = y + [2p_2 xy + p_1(r^2 + 2y^2)] $$
따라서 총 5개의 왜곡계수가 필요하다. 왜곡계수가 사용되는 대부분의 OpenCV 루틴에서 5개 모두가 필요하기 때문에 일반적으로 이것을 하나의 왜곡벡터로 묶는다. 이 벡터는 $\begin{bmatrix} k_1 & k_2 & p_1 & p_2 & k_3 \end{bmatrix}$ 순서의 5X1 배열이다.
18.2 보정(calibration)
이제 어떻게 OpenCV를 사용해 내부변수행렬과 왜곡벡터를 계산하는지 알아보자. OpenCV는 이 내부 파라미터를 계산하는 데 도움이 되는 몇 가지 알고리즘을 제공한다. 실제 보정은
cv::calibrateCamera()를 통해 수행된다. 이 루틴의 보정 방식은 개별적으로 인식 가능한 점들을 많이 포함하는 잘 알려진 대상을 카메라로 찍는 것이다. 이 형태를 다양한 각도에서 촬영하면 영상마다 변화하는 카메라의 상대적인 위치와 방향 카메라의 내부변수를 계산할 수 있다.(cv::findChessboardCorners()로 체스판 모퉁이 찾기 참고). 여러 각도에서 보려면 대상을 회전하거나 평행 이동해야 하므로 여기서 잠깐 이에 대해 다루겠다.
18.2.1 회전행렬과 평행이동벡터
카메라로 특정 대상을 찍은 각 영상에서 카메라 좌표계를 기준으로 대상의 위치를 회전 및 평행이동으로 나타낼 수 있다.
대상의 점 $P$는 영상평면에서 점 $q$로 보인다. 회전 행렬 $R$과 변환 벡터 $t$를 $P$에 적용해 점 $q$와 점 $P$의 관계를 확인할 수 있다.
일반적으로 어떤 수의 차원에서든 회전은 좌표 벡터와 적절한 크기의 정사각 행렬의 곱으로 표현할 수 있다. 결과적으로 회전은 점의 위치를 다른 좌표계에서 표현하는 것과 같다. 좌표계를 각도 $\theta$만큼 회전시키는 것은 그 좌표계의 원점을 중심으로 대상의 점을 동일한 각도 $\theta$만큼 반대 방향으로 회전시키는 것과 같다. 아래 그림은 행렬의 곱으로 2차원 회전을 표현한 것이다.
y y'
│ θ │╲
│ ↺ │ ╲ (x', y') = R·(x, y)
│ P'=(x',y') │ ╲
│ ╱ │ ╲
│ ╱ r │ P
│╱ θ │
└──────── x └──────── x'
2D 회전행렬 (각도 θ, 반시계방향):
┌ cosθ -sinθ ┐ ┌ x ┐ ┌ x' ┐
│ │·│ │ = │ │
└ sinθ cosθ ┘ └ y ┘ └ y' ┘
3차원에서의 회전은 회전축이 고정돼 있다면 각 축을 중심으로 한 2차원 회전으로 분리할 수 있다. 전체 회전행렬 $R$은 각각 $x, y, z$ 축을 중심으로 회전각도 $\psi, \varphi, \theta$ 만큼 회전한 결과이며, 이는 3개의 행렬 $R_x (\psi), R_y (\varphi), R_z (\theta)$ 의 곱(*)으로 나타낼 수 있다. $$ R_x (\psi) = \begin{bmatrix}1 & 0 & 0 \ 0 & \cos\psi & \sin\psi \ 0 & -\sin\psi & \cos\psi \end{bmatrix} \qquad R_y (\varphi) = \begin{bmatrix}\cos\varphi & 0 & -\sin\varphi \ 0 & 1 & 0 \ \sin\varphi & 0 & \cos\varphi \end{bmatrix} \qquad R_z (\theta) = \begin{bmatrix}\cos\theta & \sin\theta & 0 \ -\sin\theta & \cos\theta & 0 \ 0 & 0 & 1 \end{bmatrix} $$
(*) 처음에는 $z$축을 기준으로 회전하고 새 위치에서 $y$축을 기준으로 회전한 다음, 마지막으로 새로운 위치에서 $x$축을 기준으로 회전한다.
따라서 $R=R_x (\psi) R_y (\varphi) R_z (\theta)$라고 할 수 있다. 회전행렬 $R$은 '회전행렬의 역행렬은 회전행렬의 전치 행렬이다'라는 속성을 가지고 있다. 따라서 $R^TR = RR^T = I_3$임을 알 수 있다. $I_3$는 대각선 방향이 1이고, 그 외의 부분은 0으로 채워진 항등행렬이다.
평행이동벡터는 기존좌표계에서 원점이 다른 위치로 옮겨진 다른 좌표계로의 이동을 나타내는 방법이다. 다르게 표현하자면 평행이동벡터는 그저 처음 좌표계의 원점과 다른 좌표계의 원점 사이의 편위이다. 따라서 물체를 중심에 둔 좌표계에서 카메라를 중심에 둔 좌표계로 이동시키기에 적절한 평행이동벡터는 간단히 $\vec{T} = O_o - O_c$가 된다. 여기서 $O_o$는 물체의 중심, $O_c$는 카메라 중심이다. 물체 (또는 세계) 좌표계에 있는 점 $\vec{P_o}$가 카메라 좌표계에서 $\vec{P_c}$에 위치함을 알 수 있다. $$ \vec{P_c} = R(\vec{P_o} - T) $$
$\vec{P_c}$에 대한 이 방정식을 카메라 내부변수 보정과 결합하면 OpenCV로 풀어야 할 방정식의 기본 체계가 만들어진다. 3개의 각도를 지정하여 3차원 회전을 표현할 수 있고, 3개의 변수 $(x,y,z)$로 3차원 평행이동을 표현할 수 있다는 것을 확인했다. 따라서 지금까지 6개의 변수가 있다. OpenCV에서 카메라 내부변수행렬은 4개의 변수 $(f_x, f_y, c_x, c_y)$로 구성되며, 각도별로 촬영할 때 생성된 총 10개의 변수를 얻어야 한다(그러나 카메라 내부변수는 어느 각도로 촬영하든지 동일하다). 평평한 대상에 대해서는 각도별 촬영 시 8개의 변수면 충분하다는 것을 앞으로 설명할 것이다. 회전과 평행이동에 대한 6개의 변수는 각도별로 촬영할 때마다 변경되기 때문에 촬영마다 카메라 내부변수행렬을 얻는 데 사용할 2개의 추가 변수를 제약 조건으로 갖는다. 따라서 모든 기하학적 변수에 대해 (최소한) 두 번의 각기 다른 각도에서의 촬영이 필요하다.
18.2.2 보정판
기본적으로 적절한 특징을 가진 물체를 보정 대상으로 이용할 수 있다. 실제로 많이 쓰이는 예는 체스판, 원형 격자, 무작위 무늬, ArUco 무늬, ChArUco 무늬와 같이 평평한 표면 위에 그려진 규칙적인 무늬다.
cv::findChessboardCorners()로 체스판 모퉁이 찾기
bool cv::findChessboardCorners( // 모퉁이가 발견되면 true 반환
cv::InputArray image, // 체스판 영상(8UC1 또는 8UC3) 8bit Unsigned Channel
cv::Size pattternSize, // 행과 열당 모퉁이의 수
cv::OutputArray corners, // 검출된 모퉁이를 저장할 배열 출력
int flags = cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE
);
corner는 모퉁이의 위치가 기록되는 출력배열이다. 각각의 값은 화소 좌표계 상에 있는 각 모퉁이의 위치로 설정된다. 마지막의flags인수는 체스판에서 모퉁이를 찾을 때 도움이 되는 하나 이상의 추가 필터링 과정을 구현할 때 사용할 수 있다.부울 OR 연산을 사용해 다음 인수 중 하나 또는 모두를 조합할 수 있다.
cv::CALIB_CB_ADAPTIVE_THRESH:cv::findChessboardCorners()의 기본 동작은 우선 평균 밝기에 따라 영상의 임계값을 지정하는 것이지만, 이 플래그가 설정된 경우에는 적응형 임계값을 대신 사용한다.cv::CALIB_CB_NORMALIZE_IMAGE: 임계값에 도달하기 전에 영상을cv::equalizeHist()를 이용해 정규화한다.cv::CALIB_CB_FILTER_QUADS: 영상이 임계값에 도달하면 이 알고리즘은 체스판에서 검은 사각형의 투영도에서 얻은 사각형을 찾는다. 사각형의 각 모퉁이를 연결한 선들이 직선이라고 가정하기 때문에 이 알고리즘은 근사법이다. 영상에 방사 왜곡이 있을 경우에는 이 방법이 참(true)이 아니다. 이 플래그를 사용하면 거짓된(false) 사각형을 거부하기 위해 다양한 추가 제약조건을 해당 사각형에 적용한다.cv::CALIB_CV_FAST_CHECK: 이 선택을 하면 영상에 대해 빠른 스캔을 수행해 실제로 영상에 모퉁이가 있는지 확인한다. 만약 모퉁이가 존재하지 않으면 그 영상에 대한 작업 전체를 건너뛴다.
체스판 상의 부화소 모퉁이와 cv::cornerSubPix()
cv::findChessboardCorners()가 사용하는 내부 알고리즘으로는 모퉁이의 대략적인 위치만 얻을 수 있다. 따라서cv::findChessboardCorners()는 좀 더 정확한 결과를 얻기 위해cv::cornerSubPix()를 자동으로 호출한다. 실제로 이렇게 되면 상대적으로 위치가 정확해진다. 매우 높은 정밀도로 위치를 얻고자 한다면 출력 자료에cv::cornerSubPix()를 직접 호출(사실상 다시 호출)하면 되지만 검색 종료 기준을 더 엄격하게 정해야 한다.
cv::drawChessboardCorners()로 체스판 모퉁이 그리기
cv::drawChessboardCorners()함수는 입력한 영상 위에cv::findChessboardCorners()로 찾은 모퉁이를 그려준다. 모든 모퉁이가 발견되지 않으면 사용 가능한 모퉁이에 작은 빨간색 원이 표시된다. 전체 무늬가 발견되면 모퉁이들을 각각 다른 색상으로 칠하고(각 행은 고유한 색상을 가짐) 인식된 순서대로 모퉁이를 선으로 연결한다.void cv::drawChessboardCorners( cv::InputOutputArray image, // 입출력할 체스판 영상(8UC3) cv::Size patternSize, // 행당, 열당 모퉁이수 cv::InputArray corners, // findChessboardCorners()로 찾은 모퉁이 bool patternWasFound // findChessboardCorners() 의 반환값 )
cv::drawChessboardCorners()의 첫번째 인수는 찾아낸 모퉁이를 그리기할 영상이다. 모퉁이는 색상이 있는 원으로 표시되기 때문에 8비트 칼라 영상이어야 한다. 대부분의 경우, 이것은cv::findChessboardCorners()에 사용된 영상의 복사본일 것이다(하지만 3채널 영상으로 변환해야 한다). 다음 두 인수patternSize와corners는cv::findChessboardCorners()에서 사용된 인수와 같다. 마지막으로 인수patternWasFound는 체스판 무늬 전체를 성공적으로 발견했는지 아닌지를 나타낸다. 이것은cv::findChessboardCorners()의 반환값으로 설정할 수 있다.이제 보정판과 같은 평평한 물체를 어떻게 사용할지 생각해 보자. 평면상의 점을 핀홀이나 렌즈를 통해 보면 원근 변환이 작용한다. 정사각형 격자를 이용한 다른 무늬에 대해 간략히 설명한 후, 3x3 호모그래피 행렬에 포함된 원근 변환을 위한 변수에 대해 잠깐 설명하겠다.
원형 격자와 cv::findCirclesGrid()
원형 격자로 보정하는 것은 체스판에서
cv::findChessboardCorners()를 이용하는 것과 정확히 같은 방식으로 처리된다. 단, 다른 함수가 호출되고 다른 보정용 영상이 사용된다는 점이 다르다. 그 다른 함수가cv::findCirclesGrid()이며 다음과 같은 함수 원형을 가지고 있다.
bool cv::findCirclesGrid( // 모퉁이를 발견하면 true를 반환한다
cv::InputArray image, // 체스판 영상 입력(8UC1 또는 8UC3)
cv::Size patternSize, // 행당/ 열당 모퉁이 수
cv::OutputArray centers, // 검출된 원의 중심에 대한 배열 출력
int flags = cv::CALIB_CB_SYMMETRIC_GRID,
const cv::Ptr<cv::FeatureDetector>& blobDetector
= SimpleBlobDetector::create()
);
cv::findChessboardCorners()처럼 이 함수는 영상과 원형 형태의 수(및 배치)를 정의하는cv::Size객체를 입력받는다. 그리고 체스판에 있는 모퉁이에 해당하는 중심의 위치를 출력한다.flags인수로 원이 배치된 배열을 어떤 방식으로 정렬할지 정할 수 있다. 기본적으로cv::findCirclesGrid()는 원의 대칭형 격자를 만든다. ‘대칭형’ 격자는 원이 체스판 모퉁이와 같은 방식으로 행과 열로 깔끔하게 정렬되는 격자이다. 또 다른 방법은 비대칭형 격자이다.flag인수를cv::CALIB_CB_ASYMMETRIC_GRID로 설정하면 비대칭형 격자를 사용할 수 있다. ‘비대칭’ 격자에서 각 행의 원은 행을 가로질러 엇갈리게 배치된다.
flag의 마지막 선택은cv::CALIB_CB_CLUSTERING이다. 논리 연산자 OR을 사용하면cv::CALIB_CB_SYMMETRIC_GRID또는cv::CALIB_CB_ASYMMETRIC_GRID와 함께 설정할 수 있다. 이 옵션을 선택하면cv::findCirclesGrid()가 원을 찾는 데 약간 다른 알고리즘을 사용한다. 이 알고리즘은 원근 왜곡에 더 견고(robust)하지만 (그 결과로) 배경 잡음에 훨씬 더 민감하다. 이것은 매우 넓은 시야각으로 카메라를 보정하려고 할 때 좋은 방법이다.일반적으로 비대칭 원형격자가 최종 결과의 질뿐만 아니라 반복 수행시 결과의 안정성 면에서도 체스판보다 우수하다는 것을 잘 알 수 있다. 이러한 이유로 비대칭 원형 격자는 카메라 보정을 위한 표준 도구 모음의 일부가 되었다. 최근에는 ChArUco(라이브러리의 실험적인 코드인 contrib 부분 참고)와 같은 형태가 매우 주목받고 있다.
18.2.3 호모그래피
전산시각에서는 평면 호모그래피를 한 평면에서 다른 평면으로의 투영사상이라고 정의한다. 따라서 2차원 평면상의 점을 카메라의 촬영 센서에 사상하는 것을 평면 호모그래피의 한 예로 볼 수 있다. $\vec{Q}$가 사상된 센서에서 본 점 $\vec{Q}$와 점 $\vec{q}$를 모두 표현하기 위해 동차좌표를 사용하면 행렬의 곱셈으로 사상을 표현할 수 있다. 다음과 같이 정의한다고 하자. $$ \vec{Q} = \begin{bmatrix}X \ Y \ Z \ 1 \end{bmatrix}, \vec{q} = \begin{bmatrix}x\ y \ 1 \end{bmatrix} $$ 그러면 다음과 같이 단순하게 호모그래피의 역할을 표현할 수 있다. $$ \vec{q} = sH\vec{Q} $$ 여기서는 임의의 축척계수인 $s$를 도입했다. 이 변수는 호모그래피가 이 축척변수까지 정의함을 암묵적으로 의미한다. 일반적으로 $H$를 제외하며 여기서는 그 관례를 따를 것이다.
간단한 기하학과 행렬 대수학을 사용하면 이 변환 행렬을 풀 수 있다. 가장 주목할 점은 $H$가 두 부분이라는 점이다. 그것은 기본적으로 보이는 대상 평면의 위치를 찾는 물리적 변환과 카메라 내부함수 행렬을 도입한 투영으로 구성된다.
물리적 변환 부분은 보이는 평면과 영상 평면 사이에 작용한 회전 $R$과 평행이동 $\vec{t}$의 합이다. 동차좌표를 사용하기 때문에 다음과 같이 한 행렬로 이들을 결합할 수 있다. $$ W = \begin{bmatrix} R & \vec{t} \end{bmatrix} $$
그런 다음, 이미 투영좌표로 표현하는 방법을 알고 있는 카메라 행렬 $M$을 $\vec{Q}$에 곱하면 다음과 같이 된다.
$$ M = \begin{bmatrix}f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} 일 때, \vec{q} = sMW\vec{Q} $$
이것으로 끝난 것처럼 보이겠지만, 실제로 관심을 두는 것은 모든 공간에 대해 정의되어 있는 좌표 $\vec{Q}$가 아니라 보이는 평면에만 정의되어 있는 좌표 $\vec{Q}′$이다. 이는 결과를 약간 단순하게 만들어준다.
보편성을 잃지 않고 $Z=0$이 되도록 대상 평면을 정의할 수 있다. 회전 행렬을 세 개의 3×1열(즉, $R = \begin{bmatrix}\vec{r_1} & \vec{r_2} & \vec{r_3} \end{bmatrix}$)로 분리하면 그 열 중 하나가 더 이상 필요하지 않기 때문에 다음과 같이 정의할 수 있다.
$$ \begin{bmatrix}x \\ y \\ 1\end{bmatrix} = sM\begin{bmatrix}\vec{r_1} & \vec{r_2} & \vec{r_3} & \vec{t} \end{bmatrix} \begin{bmatrix}X\\Y\\0\\1 \end{bmatrix} = sM\begin{bmatrix}\vec{r_1} & \vec{r_2} & \vec{t} \end{bmatrix} \begin{bmatrix}X\\Y\\1 \end{bmatrix} $$
평면 대상의 점을 센서에 사상하는 호모그래피 행렬 $H$는 다음과 같이 완전하게 설명된다. $$ \vec{q} = sH\vec{Q}일 때, H = sM\begin{bmatrix}\vec{r_1} & \vec{r_2} & \vec{t} \end{bmatrix} $$ 이제 $H$가 3×3 행렬임을 주목하라.
OpenCV는 이 방정식을 사용해 호모그래피 행렬을 계산한다. 이 함수는 동일한 대상에 대한 여러 매의 영상을 이용해 각 영상에 대한 각각의 평행 이동 및 회전을 계산하고, 아울러(모든 영상에서 동일한) 카메라 내부변수를 계산한다. 앞에서 논의한 바와 같이 회전은 3개의 각도로 표현되고 평행 이동은 3개의 편위로 정의된다. 따라서 각 영상에 대해 6개의 미지수가 있다. 이 미지수는 체스판과 같은 잘 알려진 평평한 물체로부터 8개의 방정식을 얻을 수 있기 때문에 문제가 되지 않는다.
즉, 네 개의 $(x,y)$ 점으로 정사각형을 사변형에 사상하는 것을 설명할 수 있다. 각각의 새로운 영상은 6개의 새로운 미지의 카메라 외부변수를 희생하고 8개의 방정식을 제공한다. 따라서 충분한 수의 영상이 주어지면 미지의 내부변수를 얼마든지 계산할 수 있다.
호모그래피 행렬 $H$는 다음의 간단한 방정식을 이용해서 소스의 영상 평면상에 위치한 점과 목적지의 영상 평면(일반적으로 센서의 평면)상에 위치한 점 사이의 관계를 나타낸다. $$ \vec{P_d} = \begin{bmatrix} x_d \ y_d \ 1 \end{bmatrix} = H\vec{P}_s, \vec{P_s} = \begin{bmatrix} x_s \ y_s \ 1 \end{bmatrix} = H^{-1} \vec{P_d} $$ 여기서 아랫첨자 $d$는 목적 영상평면의 점, 아랫첨자 $s$는 원영상 평면의 점을 의미한다.
카메라 내부변수에 대해 알지 못해도 $H$를 계산할 수 있다. 시실 OpenCV는 여러 매의 영상에서 여러 개의 호모그래피를 계산해 카메라 내부변수를 얻는다.
OpenCV의 편리한 함수인
cv::findHomography()는 카메라 내부변수에 대응하는 호모그래피 행렬의 목록을 만들고 그중 가장 잘 대응하는 호모그래피 행렬을 반환한다. $H$를 얻기 위해 최소 4개의 점이 필요하지만, 3×3보다 큰 체스판을 사용하기 때문에 추가적으로 더 많은 점을 사용할 수 있다. 잡음과 기타 불일치가 항상 존재하고 그 영향을 최소화해야 하기 때문에 더 많은 점을 사용하는 것이 효과적이다.
cv::Mat cv::findHomography(
cv::InputArray srcPoints, // 2차원에 위치한 영상점에 대한 입력 배열
cv::InputArray dstPoints, // 2차원에 위치한 결과점에 대한 입력 배열
int method = 0, // 0, cv::RANSAC, cv::LMEDS, cv::RHO
double ransacReprojThreshold = 3, // 최대 재투영 오차 (RANSAC 사용 시)
cv::OutputArray mask = cv::noArray() // 정상값/이상값 마스크 출력 (선택)
)
srcPoints,dstPoints: 각각 원본/대상 평면의 점 배열.CV_32FC2타입의 $N×2$ 또는 $N×1$ 배열, 또는cv::Point2fSTL 벡터여야 한다.method: 호모그래피를 계산하는 데 사용할 알고리즘. 아래 표 참고.ransacReprojThreshold: RANSAC 사용 시 정상값 판단 기준 거리(픽셀). 대부분 10 미만의 정수값으로 충분하다.
method 옵션 비교:
| 값 | 이름 | 설명 | 특징 |
0 | 최소제곱법 | 모든 점을 사용해 재투영 오차 최소화 | 빠르지만 이상치에 취약 |
cv::RANSAC | RANSAC | 무작위 표본화로 이상치를 제거하며 계산 | 거의 모든 잡음비에서 효과적 |
cv::LMEDS | LMedS | 중앙값 오차를 최소화 | 추가 변수 불필요, 정상치 50% 이상일 때만 유효 |
cv::RHO | RHO | 가중치 RANSAC(PROSAC) 기반 | 이상치가 많을 때 RANSAC보다 빠름 |
OpenCV는 잡음이 있는 실제 보정 환경에서 이상치 처리가 필요하므로, 일반적으로
cv::RANSAC또는cv::RHO사용을 권장한다.마지막 인수인
mask는 견고한 방식에서만 사용되며 출력값이다. 배열을 입력하면cv::findHomography()가 그 배열을 채워서 어떤 점이 실제로 $H$를 계산하는 데 사용됐는지 나타낸다.반환값은 3×3 행렬이다. 호모그래피 행렬에 8개의 자유요소가 있기 때문에 여기서는 $H{33}=1$이 되도록 정규화했다(이것은 매우 드물고 독특한 경우인 $H{33}=0$을 제외하면 일반적으로 가능하다). 호모그래피 크기조정은 9번째 호모그래피 요소로 생각할 수 있지만, 이 장의 앞부분에서 설명한 것처럼 일반적으로 전체 호모그래피 행렬에 축척계수를 곱해 크기조정하는 것을 선호한다.
18.2.4 카메라 보정
이 절에서는
cv::calibrateCamera()를 사용해 이런 값을 계산하는 방식과 모형을 사용해 보정된 카메라로 생성한 영상의 왜곡을 수정하는 방식을 설명할 것이다. 먼저 내부 변수와 왜곡을 해결하기 위해 체스판의 영상이 얼마나 많이 필요한지 조금 더 설명할 것이다. 그런 다음, 쉽게 코드를 작성할 수 있도록 OpenCV가 실제로 어떻게 이 시스템에 대해 풀어내는지에 대해 간략히 설명하겠다.
변수의 수에 따라 필요한 체스판 모퉁이의 수
보정을 통해 해결하려는 변수가 몇 개일까? OpenCV의 경우, 카메라 내부행렬 $(f_x, f_y, c_x, c_y)$와 5개 이상의 왜곡계수에 관련된 4개의 변수가 있다. 왜곡계수는 방사왜곡에 대한 3개 이상의 변수 $(k_1, k_2, k_3[, k_4, k_5, k_6])$와 접선왜곡에 대한 2개 이상의 변수 $(p_1, p_2)$가 있다. 내부변수는 물리적인 대상과 촬영된 영상에 대한 선형 투영변환을 제어한다. 결과적으로 외부변수를 알아내서 그 물체가 실제로 어디에 있는지를 알려준다.
왜곡계수는 최종 영상에서 점으로 된 무늬가 어떻게 왜곡됐는지, 2차원 기하학으로 설명한다. 원리상으로는 알려진 무늬에서 단 3개의 모퉁이 점으로 6개의 정보를 얻어내면 5개의 왜곡계수를 풀 수 있다. 따라서 보정용 체스판을 한 번만 촬영하면 충분하다.
그러나 내부변수와 외부변수의 결합으로 인해 그것만으로는 충분하지 않은 것으로 밝혀졌다. 이 결합을 이해하려면 먼저 외부변수가 다양한 각도에서 본 체스판의 영상당 총 6개, 즉 3개의 회전변수 $(\psi, \varphi, \theta)$와 3개의 이동변수 $(T_x, T_y, T_z)$를 포함 한다는 점을 알아야 한다. 카메라 내부행렬의 변수 4개와 외부변수 6개를 합하면 영상 하나로 풀어야 되는 변수는 10개가 되지만 각각의 추가 영상에 대해서는 6개가 된다(다른 영상으로 내부 변수를 미리 계산해두면 추가 영상에 대해서는 내부 변수 4개를 계산할 필요가 없다. 따라서 추가 영상마다 변하는 것은 외부변수 6개뿐이므로 이것들에 대해서만 풀면 된다. - 옮긴이)
체스판의 모퉁이 $N$개와 영상 $K$개가 각기 다른 위치에서 촬영되었다고 가정해 보자. 이 모든 변수를 풀기에 충분한 제약 조건이 되려면 영상과 모퉁이가 몇 개 있어야 할까?
- 체스판의 영상 $K$개는 $2NK$ 개의 제약조건을 제공한다. (영상의 $N$개의 모퉁이점 각각에 대해 $x$와 $y$ 좌표를 가지기 떄문에 계수 2가 생긴다.)
- 일단 왜곡변수를 무시하면 4개의 내부변수와 $6K$개의 외부변수가 있다.
- 이 문제를 풀기 위해서는 $2NK \geq 6K+4$ 이어야 한다.
따라서 $N=5$이면 $K=1$의 영상만 필요하지만, 여기서 $K$(영상의 수)가 1보다 커야 한다. $K$개의 영상 각각에 대한 호모그래피 행렬을 찾기 위해 체스판을 보정에 사용하고 있기 때문에 $K > 1$을 만족해야 한다. 앞서 논의한 것처럼 호모그래피는 $(x,y)$ 좌표 4쌍에서 최대 8개의 변수를 얻을 수 있다. 평면 투영도로 할 수 있는 모든 것을 표현하기 위해서는 4개의 점만 있으면 되기 때문이다. 사각형을 네 가지 다른 방향으로 한꺼번에 잡아당겨서 늘리면 어떤 모양의 사각형으로도 바꿀 수 있다.