주점
과 이미지센서
간 거리를 의미하는 듯하다.카메라 unit image plane
에 매칭됨카메라 unit image plane
이란, 카메라 주점
과의 거리가 1인 image 평면을 의미카메라 unit image plane
)과, 카메라 image plane
간의 관계를 수학적으로 나타낸 것이 intrinsic matrix
(빨간색) 카메라 주점을 원점으로 하는 3D 절대좌표계의 좌표값
표현에서 -> image plane 좌표계
로 변환하기 위한 matrix!(빨간색) 카메라 주점을 원점으로 하는 3D 절대좌표계의 좌표값
표현에서 -> image plane 좌표계
로 변환이 될까?카메라의 원리 1
에서 말한 것처럼, 볼록렌즈의 문제로 인해,(빨간색) 카메라 주점을 원점으로 하는 3D 절대좌표계의 좌표값
표현에서 -> image plane 좌표계
로 변환이 정확히 되지 않는다.체스보드 패턴 판 (하얀 벽에 부착하라)
OpenCV 라이브러리 (python)
조명이 일정하고 움직임이 없는 환경
# 체스보드 패턴 생성
# 체스보드 크기
chessboard_size = (9, 6) # column 수, row 수
# objp: (9 * 6, 3)
objp = np.zeros((np.prod(chessboard_size), 3), np.float32)
"""
- np.mgrid:
- (2, column 수, row 수): (column 수, row 수)행렬이 2개인데,
- 각각 height 좌표, width 좌표를 의미
- (2, column 수, row 수).T = (row 수, column 수, 2)
- reshape: (column 수 * row 수, 2)
- 이 뜻은,
- ( column 수, row 수) array를,
- height 순으로 읽어내려가겟다는 뜻이다. (세로 줄 하나씩 위에서 아래로 읽어가겠다는 뜻)
- objp[:, 0]: height index
- objp[:, 1]: width index
"""
objp[:, :2] = np.mgrid[0:chessboard_size[0],
0:chessboard_size[1]].T.reshape(-1, 2)
cv2.findChessboardCorners()
이용criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
imgpoints = []
# 이미지 로드
images = ["IMG_3323.jpg"] # 캘리브레이션 이미지 파일 리스트
for image in images:
img = cv2.imread(image)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# corners: np.array (N, 1, 2)
ret, corners = cv2.findChessboardCorners(image=gray,
patternSize=chessboard_size,
corners=None)
if ret:
objpoints.append(objp)
# corners2: cv2.typing.MatLike # cv2.mat_wrapper.Mat, NumPyArrayNumeric
# corners2: np.array (N, 1, 2)
corners2 = cv2.cornerSubPix(image=gray,
corners=corners,
winSize=(11, 11),
zeroZone=(-1, -1),
criteria=criteria)
imgpoints.append(corners2)
# 코너를 이미지에 그립니다.
cv2.drawChessboardCorners(img,
patternSize=chessboard_size,
corners=corners,
patternWasFound=ret)
# 결과 이미지를 저장합니다.
output_image_path = f"output_{image}"
cv2.imwrite(output_image_path, img)
cv2.drawChessboardCorners()
cv2.cornerSubPix
objpoints
)를 구했고,imgpoints
)을 구했습니다.cv2.calibrateCamera
메서드를 이용해, intrinsic / extrinsic / distortion parameter을 구할 수 있습니다.(ret, mtx, dist, rvecs, tvecs) = cv2.calibrateCamera(objpoints,
imgpoints,
imageSize=gray.shape[::-1],
cameraMatrix=None,
distCoeffs=None)
"""
ret: float
- 전체 Root Mean Square re-projection error
- 이 값은 캘리브레이션 과정의 품질을 나타내며, 낮을수록 더 정확한 캘리브레이션 결과를 의미
- 0.4043353
mtx: np.ndarray (3, 3)
- intrinsic matrix
- [[2.82941040e+03 0.00000000e+00 1.97697417e+03]
[0.00000000e+00 2.82939480e+03 1.46900315e+03]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist: np.ndarray (1, 5)
- `[k1, k2, p1, p2, k3]`을 포함하며,
- 이는 각각 반경 왜곡 계수(k1, k2, k3)와 접선 왜곡 계수(p1, p2)
[[ 0.09491546 -0.22681414 0.00043416 0.00034345 0.18057507]]
rvecs: Tuple[np.ndarray]
- (np.ndarray (3, 1), ...)
- 각 패턴 뷰에 대한 회전 벡터의 튜플.
- 각 뷰에 대한 3x1 회전 벡터로, 월드 좌표계를 카메라 좌표계로 변환하는 회전을 나타냅니다.
- 이 벡터는 Rodrigues 변환을 통해 회전 행렬로 변환할 수 있습니다.
- (array([[ 0.01413114],
[ 0.01593625],
[-0.00571477]]),)
tvecs: Tuple[np.ndarray]
- 설명: 각 패턴 뷰에 대한 변환 벡터의 튜플.
- 값의 의미:
- 각 뷰에 대한 3x1 변환 벡터로, 월드 좌표계를 카메라 좌표계로 변환하는 평행 이동을 나타냄
- `(array([[-3.89025738],
[-2.26881858],
[ 7.43518093]]),)`
"""
cv2.calibrateCamera
알고리즘은 다음 단계를 수행합니다:imagePoints
와 objectPoints
간의 projectPoints
를 참조하십시오.cv2.getOptimalNewCameraMatrix()
함수를 사용하여 카메라 메트릭스(intrinsic)
를 개선할 수 있음촬영된 이미지 plane
<-> undistored된 normalized image plane
간 변환을 가능하게함왜곡 현상을 제거할 하나를 사용해 이미지의 크기를 얻고
, 카메라 메트릭스(intrinsic)를 얻는 코드
는 다음과 같습니다.# img = cv2.imread('./data/chess/left12.jpg')
h, w = img.shape[:2]
(newcameramtx, roi) = cv2.getOptimalNewCameraMatrix(cameraMatrix=mtx,
distCoeffs=dist,
imageSize=(w, h),
alpha=1,
newImgSize=(w, h))
"""
newcameramtx: np.ndarray (3, 3)
- `출력되는 새로운 카메라 intrinsic 행렬.`
- [[2.86704074e+03 0.00000000e+00 1.97858396e+03]
[0.00000000e+00 2.86554634e+03 1.47026838e+03]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
roi: Tuple[int]
- 선택적 출력 사각형으로, `왜곡 보정된 이미지에서 모든 유효 픽셀 영역을 둘러싼 사각형`
- (x, y, w, h)
- ROI 영역
- (14, 12, 3997, 2995)
"""
cv::getOptimalNewCameraMatrix
함수원본 이미지의 모든 픽셀
이 왜곡 보정된 이미지
에 유지)출력되는 새로운 카메라 intrinsic 행렬.
왜곡 보정된 이미지에서 모든 유효 픽셀 영역을 둘러싼 사각형
# dst: 보정된 이미지
dst = cv2.undistort(src=img,
cameraMatrix=mtx,
distCoeffs=dist,
dst=None,
newCameraMatrix=newcameramtx)
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.png', dst)
fx
와 fy
, cx
와 cy
는 각각 스케일링되어야 하며, 왜곡 계수는 동일하게 유지됨왜곡 제거: 이미지의 프로젝션
cv2.projectPoints()
함수가 사용
# 에러 계산
tot_error = 0
for camera_idx in range(len(objpoints)):
# objpoint: 실제 세계의 3D 점 (H*W, 3)
# rvec: 각 패턴 뷰에 대한 회전 벡터의 튜플.
# tvec: 각 패턴 뷰에 대한 변환 벡터의 튜플.
# mtx: intrinsic matrix (3, 3) # optimal 은 아님
# dist: distortion coefficients (1, 5)
# object point를 이미지 point로 변환
imgpoints2, _ = cv2.projectPoints(objpoints[camera_idx], rvecs[camera_idx],
tvecs[camera_idx], mtx, dist)
# cv2.findChessboardCorners + cv2.cornerSubPix로 얻은 이밎 point와,
# 변환된 이미지 point와 거리 계산
error = cv2.norm(imgpoints[camera_idx], imgpoints2,
cv2.NORM_L2) / len(imgpoints2)
tot_error += error
print("total error: ", tot_error / len(objpoints))
cv2.destroyAllWindows()
예전에 intrinsic matrix를 구하려고 시도했었던 여러 방법을 공유드렸었지만 리마인드차 남겨봅니다.
opencv를 이용했던 방법중 아쉬웠던 부분을 적자면
cv2.cornerSubPix
함수는 체커보드의 꼭짓점을 pixel 단위보다 fine한 subpixel 단위로 보정해주는 함수인데요, 시각화해보니 오히려 꼭짓점의 위치를 왜곡시켜버리는 효과가 있었습니다. 사용 후 제대로 적용되었는지 확인이 필요합니다.