저렴한 USB 카메라는 이미지에 왜곡을 일으키는 경우가 많지만 요즘 카메라에는 기본적으로 렌즈의 왜곡을 보정(Calibration)해서 나온다. 다만, 카메라 보정이 되어 있지 않은 경우, Software(opencv)를 활용하여 조정할 수 있다. 카메라 왜곡에는 주로 방사형 왜곡과 접선 왜곡이 있다. 방사형 왜곡은 렌즈의 굴절률에 의해 직선이 곡선으로 보이는 현상이며 이미지 중심에서 멀어질 수록 커진다. 그림1. 카메라 왜곡 예시
그림 1과 같이 이미지의 빨간 직선과 체커보드의 직선이 왜곡으로 인해 불일치함을 알 수 있다. 접선 왜곡은 카메라 렌즈와 이미지 센서의 수평이 맞지 않거나 렌즈 자체의 중심이 맞지 않아서 발생하는 왜곡이다.
왜곡의 수학 모델
그림 2. 공간상의 한점은 평면으로 투영됨
렌즈의 왜곡이 없다고 정의할 경우 공간상의 한점(Xc, Yc, Zc)은 central projection에 의해 normalized image plane 상의 한점으로 투영된다. 하지만 실제로는 카메라 렌즈로 인해 왜곡되며 방사 왜곡과 접선 왜곡의 수식 아래와 같이 표현된다.
수식 1. 방사 왜곡수식 2. 접선 왜곡
방사 왜곡과 접선 왜곡 수식에서 5가지 파리미터인 왜곡 계수를 구해야 카메라 왜곡을 보정할 수 있다.
수식 3. 왜곡 계수
그리고 카메라 intrinsic 파라미터와 extrinsic 파라미터 정보가 필요하다. Intrinsic 파라미터는 카메라에 고유하다. Intrinsic 파라미터는 focal length(카메라 초점 거리)나 optical centers에 대한 정보가 포함된다.
그림 3. 카메라 초점거리(focal length)
focal length 나 optical centers는 아래와 같이 3x3 행렬로 나타낸다.
수식 4. 카메라 메트리스
Extrinsic 파라미터는 3D 점의 좌표를 좌표계로 변화하는 rotation and translation vectors에 해당한다
카메라 캘리브레이션(Calibration) 적용(opencv)
opencv에서는 10번의 테스트 패턴을 통해 왜곡 계수를 구하여 보정하는 방법을 제시한다.
체스보드의 패턴을 찾기 위해, cv2.findChessboardCorners() 함수를 사용한다. 패턴이 8×8 격자인지 5×5격자인지 찾으며 이 예제에서는 7×6 격자이다. 이 함수는 코너 지점과 패턴이 발견되었는지의 여부를 반환한다. 이들 코너 지점들은 위치상 왼쪽에서 오른쪽으로, 위에서 아래로 정렬되어 있다.(원형 이미지를 사용할 경우, cv2.findCirclesGrid()를 사용한다.)
일단 코너를 발견하면, cv2.cornerSubPix() 함수를 사용하여 정확도를 높일 수 있다. 또한 cv2.drawChessboardCorners() 함수를 사용해 코너 결과를 그릴 수 있다.
import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32) # 6 x 7 grid 점이 생성될 것이다.
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('./data/chess/*.jpg') # 데이터의 경로를 설정해준다. 이미지 파일 형식은 .jpg
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (7,6),None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners2)
# Draw and display the corners
img = cv2.drawChessboardCorners(img, (7,6), corners2,ret)
cv2.imshow('img',img)
cv2.waitKey(500)
cv2.destroyAllWindows()
캘리브레이션
우리의 객체 포인트들과 이미지의 포인트들을 가지고 카메라 캘리브레이션을 준비했다. cv.calibrationCamera() 함수를 사용하여 카메라 매트릭스, 왜곡 계수, 회전/이동 벡터 등을 반환한다.