메뉴 닫기

파이썬 OpenCV fisheye 캘리브레이션과 undistortImage 차이 완전정복

파이썬 OpenCV fisheye 캘리브레이션과 undistortImage 차이 완전정복

🎯 fisheye 모델과 표준 왜곡 모델의 차이부터 보정 실습까지 핵심만 쉽고 정확하게 정리합니다

광각과 액션캠, 로봇 비전에서 물고기눈처럼 가장자리가 강하게 휘는 이미지를 다뤄야 할 때가 많습니다.
일반적인 렌즈 보정 공식을 적용했더니 모서리가 늘어나거나 직선이 덜 펴지는 경험을 했다면, 문제는 보정 절차가 아니라 모델 선택일 가능성이 큽니다.
OpenCV에는 일반 카메라 전용 왜곡 모델과 별도의 fisheye 전용 모델이 공존하며, 동일한 체커보드 사진을 써도 추정 방식과 결과물이 달라집니다.
이 글은 파이썬에서 OpenCV의 fisheye 모듈을 사용해 정확히 캘리브레이션하고, undistortImage로 안정적으로 보정하는 흐름을 처음부터 끝까지 친절하게 안내합니다.
현장에서 바로 적용할 수 있도록 파라미터 의미, 체커보드 준비 요령, 실패 사례를 줄이는 팁까지 실제 사용 맥락에 맞춰 설명합니다.
복잡한 수식보다 실무에서 필요한 결정 포인트를 중심으로 정리해 혼란을 줄이고 시간을 아끼는 데 도움을 드립니다.

먼저 fisheye 모델이 왜 필요한지 개념을 명확히 잡고, 기존의 Brown–Conrady 계열 왜곡 모델과 어떤 점이 구조적으로 다른지 간단한 비교로 출발합니다.
이어 fisheye.calibrate에서 자주 헷갈리는 플래그와 종료 기준, 체커보드 수집 전략을 구체적으로 짚습니다.
그 다음 fisheye.undistortImage의 입력과 출력 좌표계, 새로운 내재 파라미터 조정법을 사례와 함께 설명합니다.
마지막으로 보정 품질을 수치와 시각적으로 검증하는 방법, 빈틈을 줄이는 디버깅 체크리스트를 제공해 실전 적용력을 높입니다.
이 글만 읽어도 광각 영상의 직선 복원과 해상도 손실 최소화를 모두 고려한 보정 파이프라인을 설계할 수 있을 것입니다.



🔗 개요와 핵심 개념

OpenCV에는 두 가지 왜곡 모델 계열이 공존합니다.
일반 카메라를 위한 기본 calib3d 모델과, 광각·어안 전용의 fisheye 모델입니다.
두 모델은 수학적 투영 가정과 왜곡 파라미터의 의미가 달라서, 동일한 체커보드 데이터를 써도 추정 결과와 보정 품질이 달라질 수 있습니다.
따라서 렌즈의 화각과 투영 특성에 맞는 모델 선택이 첫 번째 결정 포인트입니다.

기본 모델은 핀홀(pinhole) 투영을 전제로 하며, 반지름에 대한 다항식 계열의 Brown–Conrady 왜곡을 사용합니다.
대표 함수는 cv2.calibrateCameracv2.undistort입니다.
반면 어안 전용 모델은 광시야각에서 직선이 크게 휘는 특성을 반영하기 위해 equidistant(θ 기반) 투영을 채택하며, 파이썬에서는 cv2.fisheye.calibratecv2.fisheye.undistortImage를 사용합니다.
fisheye 모델의 왜곡 계수는 일반적으로 4개(K1~K4)가 기본이며, 투영반경이 입사각의 홀수차 다항식으로 표현된다는 점이 핵심입니다.

항목1 항목2
기본 모델 (calibrateCamera/undistort) fisheye 모델 (fisheye.calibrate/undistortImage)
핀홀 투영 + Brown–Conrady 왜곡.
중·준광각에 유리.
equidistant 기반 투영.
초광각·어안에서 직선 복원 안정적.
왜곡계수 k1~k?, p1, p2 등(방사·접선 포함). 왜곡계수 K1~K4(홀수차 다항식 중심).
시야각이 매우 넓으면 모서리 왜곡 보정 불안정. 광시야각에서도 지오메트리 보존이 비교적 안정.
CODE BLOCK
import cv2 as cv
import numpy as np

# 체커보드 스펙 (내부 코너 수)
CHECKERBOARD = (9, 6)

# fisheye 보정에 필요한 버퍼
objpoints, imgpoints = [], []
objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
objp[0, :, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)

# 예시: 코너 누적
# gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# ret, corners = cv.findChessboardCorners(gray, CHECKERBOARD, cv.CALIB_CB_ADAPTIVE_THRESH + cv.CALIB_CB_FAST_CHECK + cv.CALIB_CB_NORMALIZE_IMAGE)
# if ret:
#     cv.cornerSubPix(gray, corners, (3,3), (-1,-1), (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 1e-6))
#     imgpoints.append(corners)
#     objpoints.append(objp)

# 캘리브레이션 (fisheye)
K = np.zeros((3, 3))
D = np.zeros((4, 1))
rvecs, tvecs = [], []
rms, K, D, rvecs, tvecs = cv.fisheye.calibrate(
    objectPoints=objpoints,
    imagePoints=imgpoints,
    image_size=(1920, 1080),   # 실제 입력 이미지 크기
    K=K, D=D, rvecs=rvecs, tvecs=tvecs,
    flags=cv.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv.fisheye.CALIB_FIX_SKEW,
    criteria=(cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 1e-6)
)

# 보정 (undistortImage)
undistorted = cv.fisheye.undistortImage(distorted=img, K=K, D=D, Knew=K, new_size=(1920,1080))

💡 TIP: 어안 보정에서는 이미지 해상도가 바뀌면 K(내재행렬)의 초점거리 계수도 해상도 비율만큼 재조정해야 일관된 결과를 얻습니다.
원본 크기와 다른 출력 크기를 쓴다면 Knew를 명시해 리사이즈 스케일을 반영하세요.

⚠️ 주의: fisheye 데이터에 기본 모델(undistort)을 적용하면 모서리에서 직선이 과도하게 늘어나거나, 내부 영역이 불필요하게 확대되는 현상이 발생할 수 있습니다.
광시야각·어안 이미지에는 동일한 체커보드라도 fisheye 전용 함수를 사용해야 합니다.

  • 🧭화각 150° 이상, 가장자리 곡률이 크면 fisheye 모델 우선 검토
  • 🧩보정 출력 크기가 바뀌면 Knew로 초점거리를 스케일링
  • 🧪체커보드는 다양한 기울기·거리 조합으로 최소 15장 이상 수집

💎 핵심 포인트:

모델 선택이 품질 절반을 좌우합니다.
핀홀 기반 기본 모델은 왜곡이 비교적 약한 광각에 적합하고, 어안 모델은 초광각에서도 직선 복원을 안정적으로 수행하도록 설계되었습니다.
함수 세트도 분리되어 있으므로, 캘리브레이션·보정 단계 모두 같은 계열로 일관되게 적용해야 합니다.

🧭 fisheye 모델과 기존 왜곡 모델 차이

OpenCV의 기본(calib3d) 모델은 핀홀 투영을 전제로 방사·접선 왜곡을 다항식으로 근사하는 Brown–Conrady 계열을 사용합니다. 대표 함수는 cv2.calibrateCamera, cv2.undistort이며, 왜곡 계수는 보통 k1~k3(또는 k6까지 확장)과 p1, p2(접선)로 구성됩니다. 반면 cv2.fisheye 네임스페이스는 광시야각을 위한 equidistant 투영 가정을 채택하고, 4개의 왜곡계수(K1~K4)를 사용하는 것이 표준입니다. 따라서 동일한 체커보드 데이터를 쓰더라도 투영 가정과 계수 정의가 달라 결과가 다르게 나옵니다.

🧭 모델 수식과 파라미터 해석

기본 모델은 이상적 핀홀 이미지 반경 r을 다항식 rdistorted=r(1+k1r2+k2r4+k3r6+…)로 근사해 왜곡을 보정합니다. fisheye 모델은 입사각 θ와 이미지 반경 r이 선형 비례(r=f·θ)하는 equidistant 투영을 기본으로 하고, 여기에 K1~K4를 사용하는 보정항을 더해 실제 렌즈를 근사합니다. 이 차이는 특히 150° 이상의 초광각에서 모서리 수축·신장 거동에 큰 차이를 만듭니다.

비교 항목 기본 모델 (calibrateCamera/undistort) fisheye 모델 (fisheye.calibrate/undistortImage)
투영 가정 핀홀(중심 투영) + 다항식 왜곡 equidistant(θ 기반) + K1~K4 보정항
왜곡 계수 k1~k3(+k4~k6), p1, p2 K1, K2, K3, K4
권장 화각 표준~광각(≤ 약 120–140°) 초광각/어안(≥ 약 150°)
대표 API cv2.calibrateCamera, cv2.undistort cv2.fisheye.calibrate, cv2.fisheye.undistortImage, initUndistortRectifyMap

🧭 보정 파이프라인과 성능 차이

cv2.fisheye.undistortImage는 내부적으로 fisheye.initUndistortRectifyMap과 remap 조합으로 구현되며, R=I(단위 회전)일 때 단일 카메라 보정 경로가 됩니다. 이 방식은 보정 전 매핑을 미리 생성·캐시할 수 있어 실시간 처리에 유리합니다. 반대로 cv2.undistort는 기본 모델의 매핑을 사용하며, 초광각에서는 모서리 정보 손실과 과보정이 발생하기 쉽습니다.

CODE BLOCK
import cv2 as cv import numpy as np def undistort_fisheye(img, K, D, balance=0.0, scale=1.0): h, w = img.shape[:2] dim = (w, h) # 출력 카메라 내재행렬 (평면화 수준 조절) # balance: 0=최대한 펴기(크롭↑), 1=왜곡 유지(크롭↓) newK = cv.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, dim, np.eye(3), balance=balance, fov_scale=scale) map1, map2 = cv.fisheye.initUndistortRectifyMap(K, D, np.eye(3), newK, dim, cv.CV_16SC2) return cv.remap(img, map1, map2, interpolation=cv.INTER_LINEAR, borderMode=cv.BORDER_CONSTANT)

💡 TIP: fisheye.undistortImage는 Knewnew_size를 통해 출력 화각과 크롭 정도를 직접 조정할 수 있습니다. 맵을 재사용하려면 initUndistortRectifyMap + remap 조합이 더 효율적입니다.

⚠️ 주의: fisheye 캘리브레이션 플래그는 기본 모델과 세트가 다릅니다. 예를 들어 CALIB_RECOMPUTE_EXTRINSIC, CALIB_FIX_SKEW, CALIB_USE_INTRINSIC_GUESS, CALIB_CHECK_COND 등이 별도로 정의되어 있으니, 잘못 섞어 쓰면 수렴이 불안정해질 수 있습니다.

💬 핵심 비교: 초광각·어안에서는 투영 가정이 맞는 fisheye 모델이 지오메트리 보존과 보정 안정성에서 유리합니다. 일반 광각 이하에서는 기본 모델이 단순하고 노이즈에 강해 실무에서 여전히 널리 쓰입니다.



🧪 fisheye.calibrate 파라미터와 체커보드 수집

fisheye 캘리브레이션은 입력 데이터의 다양성과 정확한 파라미터 설정에 따라 품질이 크게 달라집니다.
기본적으로 체커보드 코너 탐지 → 물체 좌표(objpoints)와 이미지 좌표(imgpoints) 매칭 → 내부행렬(K)과 왜곡계수(D) 추정의 순서로 진행됩니다.
이 과정에서 체커보드 촬영 각도, 거리, 조명 조건을 다양하게 확보해야 결과가 안정적으로 수렴합니다.

🧪 주요 파라미터와 종료 조건

cv2.fisheye.calibrate는 다음과 같은 인자를 받습니다.

인자 설명
objectPoints 체커보드 3D 좌표 (Z=0 평면에 배치)
imagePoints 코너 검출로 얻은 2D 픽셀 좌표
image_size (width, height) 실제 이미지 크기
K, D 카메라 내부행렬(3×3)과 왜곡계수(4×1)
flags 옵션 (RECOMPUTE_EXTRINSIC, FIX_SKEW 등)
criteria 종료 조건 (ITER, EPS 혼합)

특히 CALIB_FIX_SKEW 플래그는 스큐 계수를 0으로 강제해 비정상 수렴을 방지하고, CALIB_RECOMPUTE_EXTRINSIC은 외부 파라미터 재추정을 활성화합니다.
종료 조건은 보통 100회 반복 또는 오차 1e-6 이하로 설정하는 것이 안정적입니다.

🧪 체커보드 촬영 전략

체커보드는 다양한 각도와 거리에서 최소 15장 이상 확보하는 것이 좋습니다.
모서리 영역, 가까운 거리, 멀리서 촬영한 샷을 고르게 포함해야 내재 파라미터와 왜곡 계수가 잘 추정됩니다.
또한 너무 어둡거나 코너 검출이 실패한 이미지는 제거해야 전체 RMS 오차가 줄어듭니다.

  • 📸다양한 기울기와 거리에서 최소 15~20장 촬영
  • 💡체커보드 전체가 프레임에 꽉 차거나 모서리 일부만 잡히도록 다양화
  • ⚠️코너 검출 실패 이미지는 반드시 제외
CODE BLOCK
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 1e-6)
flags = cv.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv.fisheye.CALIB_FIX_SKEW
rms, K, D, rvecs, tvecs = cv.fisheye.calibrate(
    objpoints, imgpoints, gray.shape[::-1], K, D, rvecs, tvecs,
    flags=flags, criteria=criteria
)
print("RMS:", rms)

💎 핵심 포인트:
체커보드 촬영의 다양성이 보정 정확도를 좌우합니다.
코너가 잘 잡히는 이미지를 우선으로 선정하고, 동일한 구도 반복을 피하는 것이 중요합니다.

🛠️ fisheye.undistortImage로 왜곡 보정하기

fisheye.undistortImage는 캘리브레이션으로 얻은 카메라 내부 파라미터(K)와 왜곡 계수(D)를 이용해 왜곡된 이미지를 보정합니다.
핵심은 입력 이미지 크기와 출력 크기(new_size), 그리고 새 카메라 행렬(Knew)을 어떻게 설정하느냐에 따라 결과가 크게 달라진다는 점입니다.
Knew를 원본 K와 동일하게 두면 해상도 손실이 적지만, balance 값을 조정하거나 FOV를 확장하면 직선 보정은 잘 되지만 일부 영역이 잘릴 수 있습니다.

🛠️ 함수 사용법과 옵션

기본 호출 방식은 다음과 같습니다.

CODE BLOCK
undistorted = cv.fisheye.undistortImage(
    distorted=img,
    K=K,       # 캘리브레이션으로 구한 카메라 내재행렬
    D=D,       # 왜곡계수 (K1~K4)
    Knew=K,    # 새 내재행렬 (출력 크기나 balance에 따라 조정)
    new_size=(w, h)  # 출력 이미지 크기
)

출력 해상도(new_size)와 Knew는 반드시 짝을 맞춰야 합니다.
balance=0.0이면 직선 보정이 최대화되며 주변부가 잘리고, balance=1.0이면 원래 화각을 최대한 보존합니다.
실시간 응용에서는 initUndistortRectifyMap + remap을 활용해 매번 보정 맵을 재사용하는 방식이 더 효율적입니다.

🛠️ 실전 적용 팁

  • 📏balance=0.0~1.0 범위로 조정하며 최적의 직선-화각 타협점 찾기
  • 실시간 카메라 스트림에는 initUndistortRectifyMap + remap 조합 사용
  • 💻출력 크기를 원본과 다르게 설정할 경우 Knew 초점거리 재조정 필요

⚠️ 주의: balance를 지나치게 크게 주면 왜곡은 남아 있고 직선이 충분히 펴지지 않습니다.
반대로 너무 작게 주면 화각이 크게 잘려 영상 활용도가 떨어질 수 있습니다.
따라서 목적(예: 지도 작성, 로봇 비전, 영상미학)에 맞게 타협하는 것이 중요합니다.

💎 핵심 포인트:
undistortImage는 단순 호출만으로도 보정이 가능하지만, 실전에서는 balance·출력 크기·Knew 매개변수를 잘 설계하는 것이 품질을 좌우합니다.
특히 로봇 비전이나 3D 재구성처럼 기하학적 정확도가 중요한 경우 balance=0.0에 가깝게 두는 것이 일반적입니다.



📏 왜곡 보정 품질 평가와 디버깅 팁

fisheye 캘리브레이션의 품질은 RMS 재투영 오차(Root Mean Square Error)와 보정 결과의 시각적 직선성을 통해 평가할 수 있습니다.
RMS 값은 일반적으로 0.5 픽셀 이하라면 안정적인 결과로 간주할 수 있습니다.
하지만 수치가 낮아도 체커보드 구도 다양성이 부족하면 실제 영상에서는 직선이 덜 펴질 수 있으므로, 수치와 시각적 확인을 모두 병행해야 합니다.

📏 평가 방법

  • 📊RMS 오차가 0.5 이하인지 확인
  • 📐보정된 이미지에서 직선(벽, 문틀 등)이 곧게 펴졌는지 시각적 확인
  • 🔄balance, Knew 조정을 반복하며 최적의 화각·직선성 타협점 탐색

📏 디버깅 팁

캘리브레이션 결과가 불안정하거나 직선 보정이 기대에 못 미친다면 다음을 점검하세요.

⚠️ 주의: RMS 값만 보고 보정 품질을 단정 짓지 마세요.
체커보드 구도 다양성이 부족하거나 코너 검출이 부정확하면 RMS가 낮아도 보정 품질이 떨어질 수 있습니다.

💬 보정 정확도를 높이려면 다양한 거리·각도의 체커보드 이미지 확보, 조명 균일화, 코너 검출 파라미터 튜닝이 필수입니다.

CODE BLOCK
# RMS 오차 출력 예시
print("Calibration RMS Error:", rms)

# 직선성 확인용 테스트 (보정 전/후)
cv.line(img, (50, 500), (600, 500), (0, 255, 0), 2)
cv.line(undistorted, (50, 500), (600, 500), (0, 255, 0), 2)

💎 핵심 포인트:
보정 품질 평가는 단일 수치가 아니라 수치와 시각적 검증을 함께 고려해야 합니다.
특히 어안 보정은 balance 조정에 따라 결과가 크게 달라지므로 반복 테스트를 통해 최적 값을 찾는 것이 중요합니다.

자주 묻는 질문 (FAQ)

fisheye와 일반 캘리브레이션의 가장 큰 차이는 무엇인가요?
일반 모델은 핀홀 투영 기반의 다항식 왜곡을 사용하지만, fisheye는 초광각에서 직선 보정을 안정적으로 하기 위해 equidistant 투영을 사용합니다.
체커보드는 몇 장 정도 준비해야 하나요?
최소 15장 이상이 권장되며, 각도와 거리, 위치를 다양하게 확보할수록 보정 정확도가 향상됩니다.
balance 파라미터는 어떤 역할을 하나요?
balance는 화각 보존과 직선 보정 사이의 타협점을 조절하는 값으로, 0에 가까울수록 직선 보정이 강해지고 1에 가까울수록 원래 화각이 유지됩니다.
RMS 오차가 낮으면 항상 좋은 결과인가요?
반드시 그렇지는 않습니다. 구도 다양성이 부족하면 RMS는 낮아도 실제 영상에서는 직선이 잘 보정되지 않을 수 있습니다.
fisheye.undistortImage 대신 remap을 쓰는 이유는 무엇인가요?
실시간 카메라 스트림에서는 매번 보정 맵을 계산하는 것보다 initUndistortRectifyMap + remap 조합이 더 효율적이기 때문입니다.
보정 후 이미지가 과도하게 잘리는 이유는 무엇인가요?
balance 값을 너무 낮게 설정했거나 Knew 행렬을 잘못 지정해 출력 화각이 줄어든 경우일 수 있습니다.
fisheye 캘리브레이션에서 자주 쓰이는 플래그는 무엇인가요?
CALIB_RECOMPUTE_EXTRINSIC, CALIB_FIX_SKEW, CALIB_USE_INTRINSIC_GUESS 등이 자주 사용됩니다.
보정 품질이 만족스럽지 않을 때 가장 먼저 확인할 점은 무엇인가요?
체커보드 데이터의 다양성과 코너 검출 정확성을 먼저 확인하고, 그 다음 balance와 Knew 설정을 조정하는 것이 효과적입니다.

📝 파이썬 OpenCV fisheye 캘리브레이션 핵심 정리

fisheye 보정은 단순히 함수를 호출하는 문제를 넘어, 적합한 모델 선택과 안정적인 캘리브레이션 데이터 확보가 품질을 결정합니다.
OpenCV의 기본 모델은 표준 광각 카메라에서 효과적이지만, 초광각이나 액션캠처럼 극단적으로 왜곡된 영상에는 fisheye 전용 모델이 필요합니다.
체커보드 이미지는 최소 15장 이상 다양한 각도와 거리에서 수집해야 하고, RMS 오차뿐 아니라 실제 직선 보정 여부도 반드시 확인해야 합니다.
또한 balance, Knew 같은 파라미터 조정은 화각과 직선성 사이의 절충점을 찾는 과정이므로, 실험적으로 최적값을 선택하는 것이 좋습니다.
결국 fisheye 모듈을 제대로 이해하고 활용한다면, 로봇 비전, 3D 재구성, 영상 처리 등에서 안정적인 결과를 얻을 수 있습니다.


🏷️ 관련 태그 : 파이썬OpenCV, 카메라보정, fisheye모듈, 캘리브레이션, undistortImage, 영상처리, 로봇비전, 컴퓨터비전, 체커보드보정, 카메라왜곡