본문 바로가기

ImageProcessing

카메라 캘리브레이션

카메라 캘리브레이션(Camera Calibration)

저렴한 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() 함수를 사용하여 카메라 매트릭스, 왜곡 계수, 회전/이동 벡터 등을 반환한다.
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

왜곡 없앰

  • opencv에는 왜곡을 제거할 수 있는 방법이 두가지 있다.
  • 먼저 cv.getOptimalNewCameraMatrix() 를 사용하여 프리 스케일링 매개변수를 기반으로 카메라 매트릭스를 개선할 수 있다 . 스케일링 매개변수 alpha=0이면 불필요한 픽셀이 최소화된 왜곡되지 않은 이미지를 반환한다.
  • 따라서 이미지 모서리에서 일부 픽셀을 제거할 수도 있다. alpha=1이면 모든 픽셀이 일부 추가 검은색 이미지와 함께 유지된다. 이 기능은 또한 결과를 자르는 데 사용할 수 있는 이미지 ROI를 반환한다.
img = cv.imread ( 'left12.jpg' )
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix (mtx, dist, (w,h), 1, (w,h))

  1. cv.undistort()
  • 함수를 호출하고 위에서 얻은 ROI를 사용하여 결과를 자르면 된다.
   # 왜곡 해제
   dst = cv.undistort (img, mtx, dist, None , newcameramtx)
   # 이미지 자르기
   x, y, w, h = roi
   dst = dst[y:y+h, x:x+w]
   cv.imwrite ( 'calibresult.png' , dst)

  1. cv.remap()
  • 왜곡된 이미지에서 왜곡되지 않은 이미지로의 매핑 함수를 찾는다. 그런 다음 다시 매핑 기능을 사용한다.
   # 왜곡 해제
   mapx, mapy = cv.initUndistortRectifyMap (mtx, dist, None , newcameramtx, (w,h), 5)
   dst = cv.remap (img, mapx, mapy, cv.INTER_LINEAR)
   # 이미지 자르기
   x, y, w, h = roi
   dst = dst[y:y+h, x:x+w]
   cv.imwrite ( 'calibresult.png' , dst)

  • 찾아낸 파라미터를 저장하는 pickle 코드이다. cam_calib.pkl 이름으로 파라미터가 저장된다.
with open('cam_calib.pkl', 'wb') as f:
    pickle.dump([mtx, dist, rvecs, tvecs], f)

  • 저장한 카메라 내부 파라미터를 불러와서 웹캠으로 캘리브레이션한 결과를 실행시켜보는 코드이다.
import numpy as np
import cv2
import glob
import pickle
def get_cameramat_dist(filename):
    f = open(filename, 'rb')
    mat, dist, rvecs, tvecs = pickle.load(f)
    f.close()
    print("camera matrix")
    print(mat)
    print("distortion coeff")
    print(dist)
    return mat,dist
def main():
    mat, dist = get_cameramat_dist("cam_calib.pkl")
    cap = cv2.VideoCapture(0)
    ret, frame = cap.read()
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    print("width:",width,"height:",height)
    fourcc = cv2.VideoWriter_fourcc(*'DIVX')
    fps = cap.get(cv2.CAP_PROP_FPS)
    #frame = cv2.flip(frame, -1)
    rsz = cv2.resize(frame, dsize=(640,640))
    gray = cv2.cvtColor(rsz, cv2.COLOR_BGR2GRAY)
    h, w = gray.shape[:2]
    newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mat,dist,(w,h),0,(w,h))
    out = cv2.VideoWriter('out.avi', fourcc, fps, (int(480), int(360)))
    while(True):
        ret, frame = cap.read()
        #frame = cv2.flip(frame,-1)
        #print(frame.shape)
        rsz = cv2.resize(frame, dsize=(640,640))
        gray = rsz
# undistort
        mapx,mapy = cv2.initUndistortRectifyMap(mat,dist,None,newcameramtx,(w,h),5)
        res = cv2.remap(gray,mapx,mapy,cv2.INTER_LINEAR)
# crop the image
        x,y,w,h = roi
        res = res[y:y+h, x:x+w]
        res = cv2.resize(res,(480,360))
        out.write(res)
        cv2.imshow('res',res)
        if cv2.waitKey(20) & 0xFF == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()
if __name__ == "__main__":
    main()

출처 : [ https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html]

'ImageProcessing' 카테고리의 다른 글

다채널 합성곱 연산  (0) 2023.03.18