준비물

Python을 사용한 카메라 캘리브레이션을 구현하기 위해서는 몇 가지 필수적인 라이브러리와 도구가 필요하다.

  1. 필수 라이브러리
  2. opencv-python: OpenCV는 컴퓨터 비전 관련 작업을 위한 강력한 라이브러리로, 카메라 캘리브레이션에 필요한 함수들을 제공한다.
  3. numpy: 수치 계산을 위한 라이브러리로, 행렬 연산 및 벡터 연산에 필수적이다.
  4. matplotlib: 결과를 시각적으로 표현하기 위한 도구로, 보정 후 이미지를 비교하는 데 유용하다.
pip install opencv-python numpy matplotlib
  1. 캘리브레이션을 위한 이미지 캘리브레이션을 위해서는 여러 장의 캘리브레이션 패턴 이미지가 필요하다. 일반적으로 체스보드 패턴이 많이 사용되며, 이를 OpenCV의 findChessboardCorners() 함수를 사용해 처리할 수 있다.

체스보드 패턴 인식

카메라 보정을 시작하기 위해 첫 번째 단계는 체스보드 패턴을 인식하는 것이다. 체스보드의 내부 코너들은 카메라 캘리브레이션의 기준점 역할을 한다. OpenCV에서는 cv2.findChessboardCorners()를 사용해 이미지에서 체스보드 코너를 찾을 수 있다.

import cv2
import numpy as np

chessboard_size = (9, 6)  # 9x6 내부 코너

image = cv2.imread('chessboard_image.jpg')

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)

if ret:
    cv2.drawChessboardCorners(image, chessboard_size, corners, ret)
    cv2.imshow('Corners', image)
    cv2.waitKey(0)
cv2.destroyAllWindows()

위 코드는 체스보드 패턴이 포함된 이미지를 읽고, 내부 코너를 찾아 화면에 표시한다.


카메라 캘리브레이션을 위한 수학적 모델

카메라 캘리브레이션의 목표는 카메라의 내부 파라미터와 외부 파라미터를 추정하는 것이다. 이를 위해 우리는 다음과 같은 수학적 모델을 사용한다:

투영 매트릭스

3차원 공간의 점 \mathbf{P} = [X, Y, Z, 1]^T은 2차원 이미지 평면의 점 \mathbf{p} = [u, v, 1]^T로 투영된다. 이를 나타내는 투영 매트릭스 \mathbf{P}_{\text{proj}}는 다음과 같다:

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

여기서: - \mathbf{K}는 카메라의 내부 파라미터 매트릭스로, 초점 거리 f_x, f_y와 주점 c_x, c_y를 포함한다:

\mathbf{K} = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}

왜곡 보정 모델

실제 카메라는 이상적인 핀홀 모델과 다르게 왜곡이 발생한다. 이를 보정하기 위해 RadialTangential 왜곡을 고려한 모델이 필요하다.

  1. Radial 왜곡은 다음과 같이 표현된다:
x_{\text{distorted}} = x(1 + k_1 r^2 + k_2 r^4 + k_3 r^6)
y_{\text{distorted}} = y(1 + k_1 r^2 + k_2 r^4 + k_3 r^6)

여기서 r은 원점에서의 거리로, r^2 = x^2 + y^2, k_1, k_2, k_3는 Radial 왜곡 계수이다.

  1. Tangential 왜곡은 카메라 렌즈와 센서가 완벽하게 평행하지 않을 때 발생하며, 다음 식으로 보정된다:
x_{\text{corrected}} = x + [2p_1 xy + p_2(r^2 + 2x^2)]
y_{\text{corrected}} = y + [p_1(r^2 + 2y^2) + 2p_2 xy]

여기서 p_1, p_2는 Tangential 왜곡 계수이다.


카메라 보정 함수 구현

체스보드 코너를 찾은 후, OpenCV의 cv2.calibrateCamera() 함수를 사용해 카메라 파라미터를 추정할 수 있다. 이 함수는 여러 이미지에서 얻은 코너 정보를 입력받아 카메라의 내부 및 외부 파라미터, 왜곡 계수를 반환한다.

objp = np.zeros((chessboard_size[0]*chessboard_size[1], 3), np.float32)
objp[:,:2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)

objpoints = [] # 실제 세계의 3D 좌표
imgpoints = [] # 이미지의 2D 좌표

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)

    if ret:
        objpoints.append(objp)
        imgpoints.append(corners)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

print("Camera matrix:\n", mtx)
print("Distortion coefficients:\n", dist)

mtx는 카메라 매트릭스 \mathbf{K}를 나타내며, dist는 왜곡 계수이다.


캘리브레이션 결과 확인 및 보정 적용

카메라 캘리브레이션이 완료되면, 추정된 내부 파라미터와 왜곡 계수를 사용하여 이미지를 보정할 수 있다. 이를 통해 왜곡된 이미지에서 왜곡을 제거하고, 보정된 이미지를 얻을 수 있다.

보정 함수

OpenCV에서는 cv2.undistort() 함수를 사용하여 왜곡을 제거할 수 있다. 이 함수는 보정된 카메라 매트릭스와 왜곡 계수를 입력받아 이미지를 보정한다.

img = cv2.imread('distorted_image.jpg')
h, w = img.shape[:2]

newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

cv2.imshow('Undistorted', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

위 코드는 보정된 카메라 매트릭스를 사용해 왜곡된 이미지를 보정하고, 결과 이미지를 화면에 출력한다.

ROI (Region of Interest) 적용

보정된 이미지는 왜곡이 제거된 상태로 출력되지만, 이미지의 가장자리에 검정색 영역이 남을 수 있다. 이를 제거하기 위해 ROI(관심 영역)를 적용할 수 있다.

x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv2.imshow('Cropped', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

ROI는 보정된 이미지에서 실제로 사용 가능한 부분만을 잘라내는 과정이다. 이로 인해 최종적으로 사용 가능한 이미지 영역만 남게 된다.


재투영 오류 계산

카메라 캘리브레이션의 품질을 평가하는 방법 중 하나는 재투영 오류를 계산하는 것이다. 재투영 오류는 실제 3D 좌표를 카메라 모델을 통해 다시 2D 이미지 평면으로 투영했을 때의 오차를 나타낸다. 이 값이 작을수록 캘리브레이션이 잘된 것으로 평가할 수 있다.

재투영 오류는 다음과 같이 계산된다:

\text{error} = \frac{1}{N} \sum_{i=1}^{N} \left\| \mathbf{p}_i - \mathbf{\hat{p}}_i \right\|^2

여기서: - \mathbf{p}_i는 실제 이미지에서 관찰된 코너의 위치 - \mathbf{\hat{p}}_i는 캘리브레이션된 카메라 모델을 사용하여 투영된 코너의 위치 - N은 코너의 개수

OpenCV에서는 cv2.projectPoints() 함수를 사용해 재투영된 코너 위치를 얻고, 이를 통해 재투영 오류를 계산할 수 있다.

total_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
    total_error += error

print("Total reprojection error: ", total_error / len(objpoints))

재투영 오류 값이 작을수록 보정이 더 정확하게 이루어진 것이다. 일반적으로 재투영 오류가 1픽셀 이하일 경우 캘리브레이션의 정확도가 높다고 판단할 수 있다.


Python에서의 파이프라인 구축

카메라 캘리브레이션 파이프라인은 다음과 같은 순서로 진행된다:

  1. 체스보드 이미지 로드 및 코너 탐지: 여러 이미지에서 체스보드 패턴을 찾고, 해당 코너 정보를 수집한다.
  2. 캘리브레이션 수행: 수집된 2D 이미지 좌표와 3D 객체 좌표를 사용하여 카메라의 내부 파라미터, 외부 파라미터, 왜곡 계수를 추정한다.
  3. 왜곡 보정: 추정된 파라미터를 사용해 왜곡된 이미지를 보정하고, 최종 보정된 이미지를 출력한다.
  4. 재투영 오류 계산: 캘리브레이션 결과를 평가하여 재투영 오류를 계산하고, 보정의 품질을 확인한다.

이 과정은 실시간 비전 시스템, 로봇 비전, AR/VR 응용 등 다양한 분야에서 활용할 수 있다.