OpenCV 설치 및 기본 설정

OpenCV는 컴퓨터 비전 라이브러리로, 카메라 캘리브레이션을 비롯한 다양한 기능을 제공한다. 먼저 OpenCV를 설치하고, 이를 사용하여 이미지를 처리할 수 있는 환경을 설정해야 한다. Python 환경에서 OpenCV를 사용하려면 다음과 같이 OpenCV 패키지를 설치할 수 있다.

pip install opencv-python

이후, 필요한 라이브러리를 임포트하고, 카메라 이미지를 처리할 기본적인 코드를 작성한다.

import cv2
import numpy as np

카메라 캘리브레이션의 기본 개념

카메라 캘리브레이션은 카메라가 촬영한 이미지에서 실제 공간 좌표를 추정하는 과정이다. 이는 카메라의 내부 파라미터와 외부 파라미터를 구하는 작업을 포함한다. OpenCV에서는 Chessboard 패턴을 사용하여 캘리브레이션을 수행한다.

내부 파라미터

카메라 내부 파라미터는 카메라 렌즈와 센서의 특성을 나타내며, 다음과 같이 정의된다.

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

여기서 f_x, f_y는 카메라의 초점 거리, c_x, c_y는 이미지의 중심 좌표이다.

외부 파라미터

외부 파라미터는 월드 좌표계를 카메라 좌표계로 변환하는 매트릭스로, 회전 행렬 \mathbf{R}과 이동 벡터 \mathbf{t}로 구성된다.

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

\mathbf{P}는 프로젝션 매트릭스이며, 카메라 좌표계를 이미지 좌표계로 변환하는 역할을 한다.

Chessboard 패턴을 이용한 이미지 수집

OpenCV의 카메라 캘리브레이션 함수는 주로 체스보드 패턴을 사용하여 캘리브레이션을 수행한다. 체스보드 패턴은 카메라가 인식할 수 있는 일련의 교차점(코너)을 제공한다. 각 코너의 좌표를 추출하고, 이를 통해 카메라 매트릭스 및 왜곡 계수를 추정한다.

다음과 같이 Chessboard 패턴을 사용하는 코드를 작성할 수 있다.

chessboard_size = (9, 6)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

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 좌표

images = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(images, cv2.COLOR_BGR2GRAY)

여기에서 chessboard_size는 체스보드 패턴의 내부 코너 개수를 의미하며, objp는 실제 월드 좌표를 나타낸다. 이미지를 불러와 그레이스케일로 변환한 후, 체스보드 코너를 찾는다.

코너 탐지

체스보드 패턴에서 코너를 탐지하는 것은 OpenCV의 findChessboardCorners() 함수를 통해 가능한다.

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

if ret:
    objpoints.append(objp)
    corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    imgpoints.append(corners2)

    # 코너를 이미지에 그리기
    cv2.drawChessboardCorners(images, chessboard_size, corners2, ret)
    cv2.imshow('Chessboard', images)
    cv2.waitKey(500)

cv2.destroyAllWindows()

여기서 findChessboardCorners()는 이미지에서 체스보드의 코너를 감지하고, cornerSubPix()는 서브픽셀 정확도로 코너 좌표를 개선한다. 이를 통해 높은 정확도의 코너 좌표를 얻을 수 있다.

카메라 캘리브레이션 실행

체스보드 이미지에서 코너를 검출한 후, 이를 기반으로 카메라의 내부 및 외부 파라미터를 추정할 수 있다. OpenCV에서 이를 수행하는 함수는 calibrateCamera()이다. 이 함수는 이미지에서 추출된 2D 코너 좌표와 3D 월드 좌표를 사용하여 카메라의 매트릭스와 왜곡 계수를 추정한다.

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

여기서 반환되는 값들은 다음과 같다:

내부 파라미터 행렬

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

이 행렬은 카메라의 초점 거리와 이미지 중심을 나타낸다. 캘리브레이션을 통해 이 값을 정확하게 추정할 수 있다.

왜곡 계수

카메라 렌즈는 종종 이미지에 왜곡을 발생시킨다. OpenCV는 왜곡을 나타내는 5개의 계수를 제공한다: k_1, k_2, p_1, p_2, k_3. 이는 방사형 및 접선 왜곡을 보정하는 데 사용된다.

왜곡 계수는 다음과 같은 형태로 표현된다:

\text{dist} = \begin{bmatrix} k_1 & k_2 & p_1 & p_2 & k_3 \end{bmatrix}

여기서:

왜곡 보정

캘리브레이션 과정에서 얻은 왜곡 계수와 카메라 매트릭스를 사용하여 왜곡된 이미지를 보정할 수 있다. OpenCV에서는 undistort() 함수를 통해 이미지를 보정한다.

h, w = images.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

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

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

이 코드는 원본 이미지에서 왜곡을 제거하고, 보정된 이미지를 생성하는 과정을 보여준다. getOptimalNewCameraMatrix() 함수는 새로운 카메라 매트릭스를 계산하여, 왜곡을 최대한 줄이면서 원본 이미지의 손실을 최소화한다.

재투영 오류 분석

카메라 캘리브레이션의 정확성을 평가하는 방법 중 하나는 재투영 오류(reprojection error)를 분석하는 것이다. 이는 실제 3D 점이 이미지 평면에 투영된 위치와, 캘리브레이션된 매트릭스를 사용하여 예측된 2D 점 사이의 차이를 의미한다. 재투영 오류가 작을수록 캘리브레이션 결과가 정확한다.

재투영 오류는 다음과 같이 계산할 수 있다:

mean_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)
    mean_error += error

print(f"Total reprojection error: {mean_error/len(objpoints)}")

여기서 cv2.projectPoints()는 3D 월드 좌표를 이미지 평면에 투영하여 예상 2D 좌표를 생성한다. cv2.norm()은 실제 이미지 좌표와 예상 좌표 간의 차이를 계산하여 재투영 오류를 평가한다.