[code level] [opencv] 카메라 intrinsic 정확히 구하기!

FSA·2024년 7월 25일
0

camera

목록 보기
6/11
post-thumbnail

1. 실천 방법: GPT의 대답

  • 카메라의 intrinsic 파라미터를 정확하게 구하기(카메라 캘리브레이션)이라고 부름
  • 이 과정을 통해 얻어진 카메라 매트릭스(mtx)와 왜곡 계수(dist)는 카메라의 intrinsic 파라미터를 의미
  • 이 파라미터들은 이미지 왜곡 보정, 3D 포인트 복원 등 다양한 컴퓨터 비전 작업에 필수적
  • 아래에 그 절차를 단계별로 자세히 설명

1.1. 준비물 및 환경 설정

  • 카메라: 캘리브레이션할 카메라.
  • 캘리브레이션 보드: 일반적으로 체스보드 패턴이 사용
  • 소프트웨어 도구: OpenCV 라이브러리를 사용하는 것이 일반적입니다. Python을 추천합니다.
  • 고정된 환경: 조명이 일정하고 움직임이 없는 환경.

1.2. 캘리브레이션 보드 준비

  • 체스보드 패턴의 크기를 결정합니다.
    • 예를 들어, 9x6 (내부 코너 기준) 보드를 사용할 수 있습니다.
  • 보드를 평평하고 균일하게 인쇄하고, 단단한 표면에 부착

1.3. 이미지 캡처

  • 캘리브레이션 보드를 다양한 각도와 거리에서 촬영
    • 보드는 여러 위치와 각도에서 충분히 촬영되어야 함
  • 각 이미지에서 보드의 전체 패턴이 포함되도록 촬영해야 함

4. 코너 검출

  • 코너 검출 알고리즘: OpenCV의 findChessboardCorners 함수를 사용합니다.

  • 코드 예시:

    import cv2
    import numpy as np
    
    # 체스보드 크기
    chessboard_size = (9, 6)
    
    # 객체 포인트와 이미지 포인트 저장할 배열
    objpoints = []
    imgpoints = []
    
    # 체스보드 패턴 생성
    objp = np.zeros((np.prod(chessboard_size), 3), np.float32)
    objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
    
    # 이미지 로드
    images = [...]  # 캘리브레이션 이미지 파일 리스트
    
    for image in images:
        img = cv2.imread(image)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
    
        if ret:
            objpoints.append(objp)
            imgpoints.append(corners)
    
            # 코너를 이미지에 그립니다.
            cv2.drawChessboardCorners(img, chessboard_size, corners, ret)
            cv2.imshow('img', img)
            cv2.waitKey(500)
    
    cv2.destroyAllWindows()

5. 카메라 캘리브레이션 수행

  • 캘리브레이션 함수: cv2.calibrateCamera를 사용하여 intrinsic 파라미터를 구합니다.

  • 코드 예시:

    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
    
    # 결과 출력
    print("Camera matrix:\n", mtx)
    print("Distortion coefficients:\n", dist)

6. 결과 검증

  • 캘리브레이션이 잘 되었는지 확인하기 위해 몇 가지 검증 단계를 거칩니다.

  • 리프로젝션 에러 계산:

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

7. 보정된 이미지 확인

  • 캘리브레이션된 파라미터를 사용하여 왜곡 보정된 이미지를 확인합니다.

  • 코드 예시:

    img = cv2.imread(images[0])
    h, w = img.shape[:2]
    newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
    
    # 왜곡 보정
    dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
    
    # 결과를 자르고 저장
    x, y, w, h = roi
    dst = dst[y:y+h, x:x+w]
    cv2.imwrite('calibresult.png', dst)


코드 레벨에서 구하기


1. cv2.findChessboardCorners 메서드

1. 알고리즘 작동 원리:

cv2.findChessboardCorners는 체스보드 패턴에서 코너를 찾는 함수입니다.

  • 이 함수는 입력 이미지에서 명확한 코너를 감지하여,
    • 이를 사용자가 지정한 패턴 크기 (예: 7x6)와 맞추려고 시도합니다.

알고리즘은 다음 단계를 거칩니다:
1. 그레이스케일 변환:

  • 이미지를 그레이스케일로 변환합니다 (이미 그레이스케일인 경우 생략).
  1. 코너 감지:
  • 이미지를 여러 스케일로 변환하여 코너를 감지합니다.
  • 이를 통해 각 스케일에서 코너 후보를 찾습니다.
  1. 패턴 맞추기:
  • 감지된 코너 후보를 사용자가 지정한 체스보드 패턴 크기에 맞춰 정렬
  1. 정제:
  • 패턴이 맞춰지면, 서브픽셀 정확도로 코너 위치를 정제

2. 파라미터의 의미:

  • image: 코너를 찾을 입력 이미지. 일반적으로 그레이스케일 이미지로 제공됩니다.
  • patternSize:
    • 체스보드 패턴의 내부 코너 개수 (열, 행) 쌍으로,
    • 예를 들어 (7, 6)은 7개의 열과 6개의 행을 의미합니다.
  • corners:
    • 코너를 찾았을 때 코너의 위치를 저장할 배열.
    • 함수 호출 시에는 None으로 전달
  • flags:
    • 추가 옵션을 지정할 수 있는 플래그.
    • 기본적으로는 None으로 설정되며, 다양한 설정 값을 지정할 수 있습니다.

3. 반환 값의 의미:

  • ret:
    • 체스보드 코너를 성공적으로 찾았는지 여부를 나타내는 불리언 값.
    • True이면 코너를 찾았음을 의미
  • corners:
    • 체스보드 코너의 좌표를 포함하는 배열. retTrue일 때 유효합니다.

2. cv2.cornerSubPix 메서드

1. 알고리즘 작동 원리:

  • cv2.cornerSubPix는 감지된 코너의 위치를 서브픽셀 수준으로 정제
  • 이를 통해 코너 위치의 정확도를 높임
  • 이 함수는 이미지의 그레이디언트를 분석하여 각 코너의 정확한 위치를 결정

  • 알고리즘은 다음 단계를 거칩니다:
      1. 초기화: 초기 코너 위치와 설정된 윈도우 크기, 정확도 기준을 사용하여 서브픽셀 정제 과정을 시작합니다.
      1. 그레이디언트 계산:
      • 코너 주변의 이미지 그레이디언트를 계산하여 코너 위치를 미세하게 조정
      1. 정제 반복:
      • 설정된 반복 횟수 또는 정확도 기준에 도달할 때까지 정제 과정을 반복

2. 파라미터의 의미:

  • image: 입력 이미지. 일반적으로 그레이스케일 이미지입니다.
  • corners: 초기 코너 위치를 포함하는 배열. cv2.findChessboardCorners의 출력 값입니다.
  • winSize:
    • 코너 주변을 고려할 윈도우 크기 (예: (11, 11)).
  • zeroZone:
    • 중앙 영역의 크기를 설정하여 노이즈를 제거하는 데 사용.
    • 일반적으로 (-1, -1)로 설정하여 모든 영역을 고려
  • criteria:
    • 정제 종료 기준.
    • cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER로 설정하여 최대 반복 횟수와 정확도를 기준으로 정지합니다.

3. 반환 값의 의미:

  • corners2:
    • 서브픽셀 수준으로 정제된 코너의 위치 배열. 입력된 corners 배열의 정제된 버전

3. cv2.drawChessboardCorners 메서드

1. 알고리즘 작동 원리:

  • cv2.drawChessboardCorners는 체스보드 패턴에서 감지된 코너를 이미지 위에 그리는 함수
  • 이 함수는, 각 코너를 원이나 점으로 표시하여
    • 체스보드 패턴의 감지 결과를 시각적으로 확인할 수 있도록 도와줌

2. 파라미터의 의미:

  • image: 코너를 그릴 이미지.
  • patternSize: 체스보드 패턴의 내부 코너 개수 (열, 행) 쌍.
  • corners: 코너의 위치 배열. cv2.findChessboardCornerscv2.cornerSubPix의 출력 값입니다.
  • patternWasFound: 코너가 성공적으로 감지되었는지 여부를 나타내는 불리언 값.

3. 반환 값의 의미:

  • 반환 값은 없습니다. 함수는 입력 이미지에 코너를 직접 그립니다.

cv::calibrateCamera 함수

  • 여러 캘리브레이션 패턴 뷰에서 카메라의 내부 및 외부 파라미터를 찾습니다.

파라미터

  • objectPoints:
    • 캘리브레이션 패턴 좌표 공간에서 캘리브레이션 패턴 점의 벡터의 벡터입니다 (예: std::vector<std::vector<cv::Vec3f>>).
    • 외부 벡터에는 패턴 뷰의 수만큼 요소가 포함됩니다.
    • 각 뷰에서 동일한 캘리브레이션 패턴이 표시되고 완전히 보이면, 모든 벡터는 동일합니다.
    • 그러나 부분적으로 가려진 패턴이나 다른 뷰에서 다른 패턴을 사용할 수도 있습니다.
    • 그런 경우, 벡터는 다를 것입니다.
    • 점은 3D이지만, 사용된 캘리브레이션 패턴이 평면 장치인 경우, 모든 점이 캘리브레이션 패턴의 XY 좌표 평면에 놓이게 됩니다 (따라서 Z 좌표는 0입니다).
  • imagePoints:
    • 캘리브레이션 패턴 점의 투영 벡터의 벡터
    • (예: std::vector<std::vector<cv::Vec2f>>).
    • imagePoints.size()objectPoints.size(),
    • 그리고 각 i에 대해 imagePoints[i].size()objectPoints[i].size()가 같아야 합니다.
  • imageSize:
    • 카메라 내부 행렬을 초기화하는 데 사용되는 이미지의 크기.
  • cameraMatrix:
    • 입력/출력 3x3 부동 소수점 카메라 내부 행렬.
    • CALIB_USE_INTRINSIC_GUESS 및/또는 CALIB_FIX_ASPECT_RATIO, CALIB_FIX_PRINCIPAL_POINT 또는 CALIB_FIX_FOCAL_LENGTH가 지정된 경우, 호출 전에 fx, fy, cx, cy의 일부 또는 전부가 초기화되어야 합니다.
  • distCoeffs:
    • 입력/출력 왜곡 계수 벡터.
  • criteria: 반복 최적화 알고리즘의 종료 기준.
  • 반환 값:
    • ret:
      • 전체 Root Mean Square re-projection error
      • 이 값은 캘리브레이션 과정의 품질을 나타내며, 낮을수록 더 정확한 캘리브레이션 결과를 의미
    • mtx(cameraMatrix):
      • (3 , 3) numpy array
      • intrinsic matrix
    • dist (distCoeffs):
      • (5,) shape numpy
      • [k1, k2, p1, p2, k3]을 포함하며,
      • 이는 각각 반경 왜곡 계수(k1, k2, k3)와 접선 왜곡 계수(p1, p2)
    • rvecs (rotation vectors):
      • 각 패턴 뷰에 대한 회전 벡터의 튜플.
      • Tuple[np.ndarray]
      • 각 뷰에 대한 3x1 회전 벡터로, 월드 좌표계를 카메라 좌표계로 변환하는 회전을 나타냅니다.
      • 이 벡터는 Rodrigues 변환을 통해 회전 행렬로 변환할 수 있습니다.
      • (array([[ 0.01413114], [ 0.01593625], [-0.00571477]]),)
    • tvecs (translation vectors):
      • 설명: 각 패턴 뷰에 대한 변환 벡터의 튜플.
      • 값의 의미:
        • 각 뷰에 대한 3x1 변환 벡터로, 월드 좌표계를 카메라 좌표계로 변환하는 평행 이동을 나타냄
        • (array([[-3.89025738], [-2.26881858], [ 7.43518093]]),)

  • 이 함수는 내부 카메라 파라미터 및 각 뷰에 대한 외부 파라미터를 추정
  • 알고리즘은 다음 단계를 수행합니다:

참고:

  • 비정방형 (즉, 비 N x N) 그리드를 사용하고 findChessboardCorners로 캘리브레이션을 수행하며,
    • calibrateCamera가 잘못된 값을 반환하는 경우 (왜곡 계수가 0이고, cxcy가 이미지 중심에서 매우 멀리 떨어져 있으며, fxfy 간의 큰 차이 (비율이 10:1 이상)),
    • patternSize=cvSize(rows, cols) 대신 patternSize=cvSize(cols, rows)를 사용하고 있는지 확인하십시오.
  • 이 함수는 지원되지 않는 파라미터 조합이 제공되거나 시스템이 제약이 있는 경우 예외를 발생시킬 수 있음

  • 이제 이미지의 왜곡을 제거할 수 있는데, OpenCV에서는 2가지 방법을 제공합니다.
  • 그러나 먼저 왜곡을 제거하기에 앞서 cv2.getOptimalNewCameraMatrix() 함수를 사용하여 카메라 메트릭스(intrinsic)를 개선할 수 있음
  • 이 함수는 또한 결과를 자르는데 사용할 수 있는 이미지 ROI를 반환
  • 13개의 샘플 이미지 중 왜곡 현상을 제거할 하나를 사용해 이미지의 크기를 얻고, 카메라 메트릭스(intrinsic)를 얻는 코드는 다음과 같습니다.
img = cv2.imread('./data/chess/left12.jpg')
h,  w = img.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))

cv::getOptimalNewCameraMatrix 함수

  • 자유 스케일링 파라미터(alpha)를 기반으로, 최적의 새로운 카메라 내부 행렬(intrinsic)을 반환
  • 알파가 0보다 클 때, 왜곡 보정된 결과는,
    • 캡처된 왜곡 이미지 밖의 "가상" 픽셀에 해당하는 일부 검은 픽셀을 가질 가능성이 있습니다.
  • 원본 카메라 내부 행렬, 왜곡 계수, 계산된 새로운 카메라 내부 행렬 및 새로운 이미지 크기는 initUndistortRectifyMap에 전달되어 remap용 맵을 생성

파라미터

  • cameraMatrix: 입력 카메라 내부 행렬.
  • distCoeffs: 입력 왜곡 계수 벡터. 벡터가 NULL/비어 있으면, 왜곡 계수가 0으로 간주
  • imageSize: 원본 이미지 크기.
  • alpha:
    • 자유 스케일링 파라미터로, 0~1 사이
    • 0
      • 원본 이미지의 유효한 픽셀만, 보정된 이미지로 가져오기
      • 원본 이미지의 원치않는 픽셀을 최소로 갖는 보정된 이미지가 얻어지는데,
        • 원본 이미지의 코너 지점의 픽셀들이 제거될 수도 있음
      • 왜곡 보정된 이미지의 모든 픽셀이 유효한 pixel임
    • 1
      • 원본 이미지의 모든 픽셀은, 보정된 이미지에 유지됩니다.
      • (원본 이미지의 모든 픽셀왜곡 보정된 이미지유지)
    • 자세한 내용은 stereoRectify를 참조
    • https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html#ga7a6c4e032c97f03ba747966e6ad862b1
  • newImgSize:
    • 보정 후 이미지 크기. 기본값은 imageSize로 설정됩니다.

반환 값

  • new_camera_matrix:
    • 출력되는 새로운 카메라 intrinsic 행렬.
    • np.ndarray (3, 3)
  • validPixROI:
    • 선택적 출력 사각형으로, 왜곡 보정된 이미지에서 모든 유효 픽셀 영역을 둘러싼 사각형
    • Tuple[int]
    • (x, y, w, h)
    • (14, 12, 3997, 2995)

왜곡 제거하기

  • 위에서 왜곡을 제거하는 방법이 OpenCV에서는 2가지를 제공한다고 했는데, 첫번재는 다음과 같습니다.
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)

cv::undistort 함수

  • 렌즈 왜곡을 보정하기 위해 이미지를 변환
  • 이 함수는 반경 및 접선 렌즈 왜곡을 보정하기 위해 이미지를 변환
  • 이 함수는 아래 2개의 조합
  • 수행되는 변환의 세부 사항은 initUndistortRectifyMap 함수를 참조
  • 원본 이미지에서 해당 픽셀이 없는 대상 이미지의 픽셀은 0 (검은색)으로 채워짐
  • 수정된 이미지에서 보일 원본 이미지의 특정 하위 집합은 newCameraMatrix로 조정할 수 있습니다.
  • 카메라 행렬왜곡 파라미터calibrateCamera를 사용하여 결정할 수 있음
  • 이미지의 해상도가 캘리브레이션 단계에서 사용된 해상도와 다른 경우,
    • fxfy, cxcy는 각각 스케일링되어야 하며, 왜곡 계수는 동일하게 유지됨
  • 이 함수는 원본 이미지의 왜곡을 보정하여, 출력 이미지로 변환
  • 보정된 이미지에서 원본 이미지의 특정 하위 집합을 보이게 하려면 newCameraMatrix를 사용할 수 있음

파라미터

  • src: 입력 (왜곡된) 이미지.
  • dst: 출력 (보정된) 이미지로, src와 동일한 크기 및 유형을 가집니다.
  • cameraMatrix: 입력 카메라 행렬.
  • distCoeffs:
    • 4, 5, 8, 12 또는 14 요소의 왜곡 계수 벡터.
    • 벡터가 NULL/비어 있으면, 왜곡 계수는 0으로 간주됨
  • newCameraMatrix:
    • 왜곡된 이미지의 카메라 행렬.
    • 기본값은 cameraMatrix와 동일하지만, 다른 행렬을 사용하여 결과를 추가로 스케일링하고 이동시킬 수 있음

반환 값

  • dst: 출력 (보정된) 이미지.

  • 위와 동일한 결과를 제공하는 또 다른 방법은 다음과 같습니다.
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)


  • 왜곡 제거: 이미지의 프로젝션
  • 이 왜곡 제거 시 수행된 프로젝션에 발생하는 오차가 얼마인지를 알기 위해 cv2.projectPoints() 함수가 사용
  • 결과적으로 얻어지는 값이 0에 가까울수록 정확한 것
tot_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv2.norm(imgpoints[i],imgpoints2, cv2.NORM_L2)/len(imgpoints2)
    tot_error += error
print("total error: ", tot_error/len(objpoints))
profile
모든 의사 결정 과정을 지나칠 정도로 모두 기록하고, 나중에 스스로 피드백 하는 것

0개의 댓글