AI/vision

camera calibration using OpenCV

민사민서 2024. 8. 29. 16:36

2D image points are OK which we can easily find from the image. (These image points are locations where two black squares touch each other in chess boards)

 

we need to know 3D points(X,Y,Z) values. But for simplicity, we can say chess board was kept stationary at XY plane, (so Z=0 always) and camera was moved accordingly. ⇒ Now for X,Y values, we can simply pass points as (0,0), (1,0), (2,0) … (square size 알면 그 배수로)

 

3D points are called object points and 2D image points are called image points.

캘리브레이션 과정

1. 준비

먼저, 체스보드 패턴이 있는 이미지를 사용하여 3D 실제 좌표와 해당하는 2D 이미지 좌표를 찾습니다. 체스보드의 코너점을 사용합니다. (이 예시에서는 7*6 grid를 사용)

import numpy as np
import cv2 as cv
import glob

# 종료 기준
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# 체스보드의 3D 점 준비
objp = np.zeros((6*7, 3), np.float32)
objp[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)

# 3D 점과 2D 점을 저장할 배열
objpoints = [] # 3D 점
imgpoints = [] # 2D 점

images = glob.glob('*.jpg')

for fname in images:
    img = cv.imread(fname)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

    # 체스보드 코너 찾기
    ret, corners = cv.findChessboardCorners(gray, (7, 6), None)

    # 코너 점을 찾으면 배열에 추가
    if ret:
        objpoints.append(objp)
        # 코너의 정확도 증가
        corners2 = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners2)

        # 코너 그리기
        cv.drawChessboardCorners(img, (7, 6), corners2, ret)
        cv.imshow('img', img)
        cv.waitKey(500)

cv.destroyAllWindows()

2. 캘리브레이션

체스보드 패턴 이미지에서 얻은 객체 포인트와 이미지 포인트를 사용하여 카메라 매트릭스와 왜곡 계수를 계산합니다. ⇒ 순서대로 camera matrix, distortion 계수, rotation vec, translation vec

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

3. 이미지 왜곡 제거

계산된 카메라 매트릭스와 왜곡 계수를 사용하여 왜곡된 이미지를 보정합니다.

  • cv.undistort() 사용:
img = cv.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))

# 왜곡 제거
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)
  • remapping 사용:
# 왜곡 제거를 위한 매핑 함수 찾기
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)

⇒ can store the camera matrix and distortion coefficients using write functions in NumPy (np.savez, np.savetxt etc) for future uses

4. 재투영 오류 계산

재투영 오류는 캘리브레이션의 정확성을 평가하는 데 사용됩니다. 이는 실제 코너 점과 캘리브레이션된 매트릭스를 사용하여 예측한 코너 점 사이의 차이를 나타냅니다.

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

print("total error: {}".format(mean_error / len(objpoints)))

 

참고로 cv2.findCorners 는 코너 격자 개수를 정확히 주어야 detect 하더라 (9*6 격자인데 7*6 이라 하면 못찾음 ㅋㅋㅋ)

 

포즈 추정 과정 (좌표계 변환)

포즈 추정은 카메라 캘리브레이션 결과를 사용하여 이미지에서 객체의 위치와 회전을 추정하는 과정

체스보드 패턴을 사용하여 3D 좌표축을 이미지에 그리는 방법과 간단한 큐브를 그리는 방법 ㄱㄱ

기본 개념

  • 내부 매개변수 (Intrinsic Parameters): 카메라의 고유 특성으로, 초점 거리(focal length), 광학 중심(optical center) 등이 포함됩니다.
  • 왜곡 계수 (Distortion Coefficients): 렌즈 왜곡을 보정하는 데 사용되는 파라미터입니다.
  • 외부 매개변수 (Extrinsic Parameters): 카메라의 위치와 회전을 나타내는 회전 벡터와 평행 이동 벡터입니다.

포즈 추정을 통해 객체가 공간에서 어떻게 배치되었는지 (예: 회전, 평행 이동) 계산

⇒ 2D 이미지에서 3D 효과를 시뮬레이션할 수 있습니다.

3D 좌표축 그리기

먼저, 이전 캘리브레이션 결과에서 카메라 매트릭스와 왜곡 계수를 불러옵니다.

import numpy as np
import cv2 as cv
import glob

# 이전에 저장한 데이터 불러오기
with np.load('B.npz') as X:
    mtx, dist, _, _ = [X[i] for i in ('mtx', 'dist', 'rvecs', 'tvecs')]

체스보드 코너와 축 점을 받아 이미지를 그리는 함수를 정의합니다.

def draw(img, corners, imgpts):
    corner = tuple(corners[0].ravel())
    img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255, 0, 0), 5)
    img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0, 255, 0), 5)
    img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0, 0, 255), 5)
    return img

기준 종료 조건과 체스보드의 객체 포인트를 설정하고, 축 점을 정의합니다.

criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*7, 3), np.float32)
objp[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)

axis = np.float32([[3, 0, 0], [0, 3, 0], [0, 0, -3]]).reshape(-1, 3)

이미지를 불러오고 체스보드 코너를 찾은 후, 회전 벡터와 평행 이동 벡터를 계산합니다. 그런 다음, 3D 축 점을 이미지 평면에 투영하고, 그 결과를 이미지에 그립니다.

for fname in glob.glob('left*.jpg'):
    img = cv.imread(fname)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret, corners = cv.findChessboardCorners(gray, (7, 6), None)

    if ret:
        corners2 = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        
        # 회전 및 평행 이동 벡터 계산
        ret, rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)

        # 3D 점을 이미지 평면에 투영
        imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)

        img = draw(img, corners2, imgpts)
        cv.imshow('img', img)
        k = cv.waitKey(0) & 0xFF
        if k == ord('s'):
            cv.imwrite(fname[:6] + '.png', img)

cv.destroyAllWindows()

큐브 그리기

큐브를 그리기 위해서는 축 점과 그리는 함수를 수정해야 합니다.

def draw(img, corners, imgpts):
    imgpts = np.int32(imgpts).reshape(-1, 2)

    # 녹색으로 바닥 그리기
    img = cv.drawContours(img, [imgpts[:4]], -1, (0, 255, 0), -3)

    # 파란색으로 기둥 그리기
    for i, j in zip(range(4), range(4, 8)):
        img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]), (255), 3)

    # 빨간색으로 상단 레이어 그리기
    img = cv.drawContours(img, [imgpts[4:]], -1, (0, 0, 255), 3)

    return img

큐브의 8개 모서리를 나타내는 축 점을 정의합니다.

axis = np.float32([[0, 0, 0], [0, 3, 0], [3, 3, 0], [3, 0, 0],
                   [0, 0, -3], [0, 3, -3], [3, 3, -3], [3, 0, -3]])

나머지 코드는 이전과 동일하게 처리하여 이미지를 그립니다.