OpenCV로 체커보드 카메라 캘리브레이션 따라 하기

대상: 로봇 비전, SLAM, AprilTag, ArUco 등에서 정확한 카메라 좌표를 얻고 싶은 개발자
환경: Ubuntu 20.04 + Python 3 + OpenCV 4.x
준비물: 인쇄한 체커보드 패턴 (흑백 격자), 카메라 (USB/CSI/웹캠 등)


1. 문제/주제 요약

카메라로 3D 공간을 인식하려면 왜곡 없는 정확한 이미지 좌표 → 3D 변환이 필수입니다.
하지만 대부분의 렌즈는 다음과 같은 왜곡(distortion) 을 가지고 있습니다 👇

  • 직선이 휘어보이는 배럴 왜곡(barrel distortion)
  • 중심부와 가장자리의 초점 차이

이를 보정하기 위한 과정이 카메라 캘리브레이션(Camera Calibration) 입니다.
즉, OpenCV로 K (Intrinsic)과 D (왜곡 계수)를 추정하는 단계입니다.


2. 원리 요약

OpenCV는 체커보드(Checkerboard) 이미지 여러 장을 이용해
3D-2D 점 대응 관계를 계산하고, 다음을 추정합니다:

  1. 내부 파라미터 (Intrinsic)
    $$
    K =
    \begin{bmatrix}
    f_x & 0 & c_x \
    0 & f_y & c_y \
    0 & 0 & 1
    \end{bmatrix}
    $$
  2. 왜곡 계수 (Distortion Coefficients)
    $$
    D = [k_1, k_2, p_1, p_2, k_3]
    $$
  3. 외부 파라미터 (Extrinsic)
    각 체커보드 사진에서의 회전($R$)과 이동($t$)

💡 이 과정을 통해 “이 카메라가 3D 세상에서 어떻게 이미지를 보는가”를 수학적으로 표현할 수 있습니다.


3. 준비 단계

(1) 체커보드 인쇄

  • 일반적으로 9×6 코너(즉, 10×7 칸짜리 보드)가 많이 사용됩니다.
  • 흑백 패턴을 A4 용지에 인쇄하고, 평평한 판자에 부착합니다.
  • 패턴 PDF 예시: OpenCV 공식 패턴

⚙️ 코너 수 설정 시 주의:
9x6 체커보드는 “코너 점이 9×6개”지, 사각형이 9×6개가 아닙니다.
즉, OpenCV에 전달할 코너 수는 (9, 6) 입니다.


4. 캘리브레이션 코드 예제

📂 camera_calibration.py

import cv2
import numpy as np
import glob

# 1️⃣ 체커보드 내부 코너 개수
CHECKERBOARD = (9, 6)

# 2️⃣ 체커보드의 실제 각 사각형 한 변의 길이 (예: 25mm)
square_size = 0.025  # 단위: m

# 3️⃣ 체커보드 3D 좌표 준비
objp = np.zeros((CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
objp *= square_size

# 4️⃣ 저장용 배열
objpoints = []  # 3D 점 (실세계)
imgpoints = []  # 2D 점 (이미지)

# 5️⃣ 캘리브레이션용 이미지 불러오기
images = glob.glob('./images/*.jpg')

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

    # 6️⃣ 체커보드 코너 찾기
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)

    if ret:
        objpoints.append(objp)
        corners_sub = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1),
                                       criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
        imgpoints.append(corners_sub)
        cv2.drawChessboardCorners(img, CHECKERBOARD, corners_sub, ret)
        cv2.imshow('Corners', img)
        cv2.waitKey(200)

cv2.destroyAllWindows()

# 7️⃣ 카메라 캘리브레이션 수행
ret, K, D, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

print("\n✅ Calibration complete!")
print("Intrinsic matrix (K):\n", K)
print("Distortion coefficients (D):\n", D)

# 8️⃣ 예시: 첫 이미지 undistort 테스트
img = cv2.imread(images[0])
h, w = img.shape[:2]
new_K, roi = cv2.getOptimalNewCameraMatrix(K, D, (w, h), 1, (w, h))
undistorted = cv2.undistort(img, K, D, None, new_K)

cv2.imshow('Original', img)
cv2.imshow('Undistorted', undistorted)
cv2.waitKey(0)
cv2.destroyAllWindows()

5. 촬영 가이드

✅ 캘리브레이션 시 주의사항

항목설명
각도 다양성체커보드를 다양한 각도·위치에서 촬영 (평면, 비스듬히 등)
조명 균일반사나 그림자 최소화
프레임 수최소 10장 이상 (보통 15~20장 권장)
해상도가능한 한 고해상도 유지 (너무 낮으면 코너 검출 실패)

6. 결과 예시

출력 예시:

Intrinsic matrix (K):
[[625.4   0.   315.2]
 [  0.  624.9  240.3]
 [  0.    0.    1.  ]]

Distortion coefficients (D):
[[-0.332, 0.124, 0.002, -0.001, 0.0]]

보정된 이미지:

  • 왼쪽: 원본 (직선이 휘어 있음)
  • 오른쪽: 보정 후 (직선 유지)

7. ROS와 연동하기

OpenCV에서 구한 값을 ROS 카메라 파라미터로 변환할 수 있습니다.

camera_info.yaml 예시:

image_width: 640
image_height: 480
camera_name: usb_cam
camera_matrix:
  rows: 3
  cols: 3
  data: [625.4, 0.0, 315.2, 0.0, 624.9, 240.3, 0.0, 0.0, 1.0]
distortion_model: plumb_bob
distortion_coefficients:
  rows: 1
  cols: 5
  data: [-0.332, 0.124, 0.002, -0.001, 0.0]
rectification_matrix:
  rows: 3
  cols: 3
  data: [1, 0, 0, 0, 1, 0, 0, 0, 1]
projection_matrix:
  rows: 3
  cols: 4
  data: [625.4, 0.0, 315.2, 0.0, 0.0, 624.9, 240.3, 0.0, 0.0, 0.0, 1.0, 0.0]

💡 camera_info_publisher 노드나 image_proc에서 이 YAML을 불러오면
RViz나 SLAM 노드가 자동으로 보정된 카메라 모델을 사용합니다.


8. 자주 하는 실수 & 해결 팁

증상원인해결
“체커보드 코너 찾기 실패”조명 불균일, 각도 부족여러 각도, 거리에서 재촬영
보정 후 이미지가 더 왜곡됨코너 수 잘못 지정(9,6) 확인 (코너 개수 기준)
Undistort 결과가 잘림alpha=1getOptimalNewCameraMatrix 사용
3D 포인트 스케일이 이상함square_size 단위 오류실제 mm→m 단위 일치 필요
ROS에서 좌표가 뒤집힘frame_id 불일치TF(camera_link) 확인

9. 정리

항목의미예시
Intrinsic (K)카메라 내부 파라미터초점 거리, 중심 좌표
Distortion (D)렌즈 왜곡 계수$$k_1, k_2, p_1, p_2, k_3$$
Extrinsic (R, t)카메라 위치/자세TF(base_link → camera_link)
Calibration체커보드로 K, D 추정cv2.calibrateCamera()
Undistortion보정 영상 출력cv2.undistort()

📌 요약

  • 체커보드 코너를 여러 장에서 검출 → K, D 계산
  • 왜곡 없는 이미지로 보정 → SLAM, ArUco, Depth 정밀도 향상
  • 결과는 ROS camera_info.yaml에 그대로 활용 가능

댓글 남기기