본문 바로가기
영상처리/스테레오 비전

OpenCV를 사용한 카메라 보정

by j2b2 2023. 4. 10.

https://learnopencv.com/camera-calibration-using-opencv/

 

Camera Calibration using OpenCV | LearnOpenCV #

A step by step tutorial for calibrating a camera using OpenCV with code shared in C++ and Python. You will also understand the significance of various steps.

learnopencv.com

위의 글을 구글 번역기로 돌린 결과물


 

OpenCV를 사용한 카메라 보정

카메라는 시각 센서로 사용될 때 로봇, 감시, 우주 탐사, 소셜 미디어, 산업 자동화 및 엔터테인먼트 산업과 같은 여러 영역의 필수적인 부분입니다.

많은 응용 분야에서 카메라를 시각적 센서로 효과적으로 사용하려면 카메라의 매개변수를 아는 것이 필수적입니다.

이 게시물에서는 카메라 보정과 관련된 단계와 그 중요성을 이해하게 될 것입니다.

우리는 또한 바둑판 패턴의 예제 이미지와 함께 C++ 및 Python으로 코드를 공유하고 있습니다.

 

 

 

카메라 캘리브레이션이란?

카메라의 매개변수를 추정하는 과정을 카메라 보정이라고 합니다.

즉, 실제 세계의 3D 포인트와 보정된 카메라로 캡처한 이미지의 해당 2D 투영(픽셀) 간의 정확한 관계를 결정하는 데 필요한 카메라에 대한 모든 정보(매개변수 또는 계수)가 있습니다.

일반적으로 이것은 두 종류의 매개변수를 복구하는 것을 의미합니다.

  1. 카메라/렌즈 시스템의 내부 매개변수(Internal parameters). 예를 들어 초점 거리, 광학 중심 및 렌즈의 방사 왜곡 계수.
  2. 외부 매개변수(External parameters) : 이것은 일부 세계 좌표계에 대한 카메라의 방향(회전 및 이동)을 나타냅니다.

아래 이미지에서 기하학적 보정을 사용하여 추정된 렌즈의 매개변수는 이미지 왜곡을 제거하는 데 사용되었습니다.

왜곡된 이미지에 대한 기하학적 보정 효과.

 

 

 

OpenCV를 사용한 카메라 보정

보정 과정을 이해하려면 먼저 이미지 형성의 기하학을 이해해야 합니다. 자세한 설명은 아래 링크를 클릭하세요

이미지 형성의 기하학

포스트에서 설명했듯이 이미지 평면에 대한 3D 점의 투영을 찾으려면 먼저 외부 매개변수를 사용하여 점을 세계 좌표계(world coordinate system)에서 카메라 좌표계(camera coordinate system)로 변환해야 합니다.(회전 R and 이동 t).

 

다음으로 카메라의 고유 매개변수(intrinsic parameters)를 사용하여 점을 이미지 평면에 투영합니다.

 

세계 좌표의 3D 점(X_w, Y_w, Z_w)과 이미지 좌표의 투영(u, v)을 연결하는 방정식은 다음과 같습니다.

 

여기서, P는 두 부분으로 구성된 3×4 투영 행렬입니다. - 내부 매개변수를 포함하는 고유 행렬intrinsic matrix(K)외부 행렬extrinsic matrix ( [ R | t ] ). 그것은 3×3 회전 행렬 R와 3×1 평행이동 t 벡터의 조합입니다.


이전 게시물에서 언급했듯이 고유 행렬(intrinsic matrix) K는 상삼각형(upper triangular)입니다.


여기서,

 are the x and y focal lengths ( yes, they are usually the same ).
                x 및 y 초점 거리입니다(예, 일반적으로 동일합니다).

are the x and y coordinates of the optical center in the image plane. Using the center of the image is usually a good enough approximation.

              이미지 평면에서 광학 중심의 x 및 y 좌표입니다. 이미지의 중심을 사용하는 것이 일반적으로 충분한 근사치입니다.

 is the skew between the axes. It is usually 0.
        축 사이의 기울기입니다. 일반적으로 0입니다.

 

 


카메라 보정의 목표

보정 프로세스의 목표는 알려진 3D 포인트 세트(X_w, Y_w, Z_w)와 해당 포인트 이미지 좌표(u, v)를 사용하여 3×3 행렬 K, 3×3 회전 행렬 R, 3×1 변환 벡터 t를 찾는 것입니다. 내부 및 외부 매개변수의 값을 얻을 때 카메라가 보정되었다고 합니다.

요약하면 카메라 보정 알고리즘에는 다음과 같은 입력 및 출력이 있습니다.

  1. 입력: 2D 이미지 좌표와 3D 세계 좌표가 알려진 점을 포함하는 이미지 모음입니다.
  2. 출력: 3×3 카메라 고유 매트릭스, 각 이미지의 회전 및 변환.

참고: OpenCV에서 카메라 내장 행렬에는 스큐 매개변수가 없습니다. 따라서 행렬은 다음과 같은 형식입니다.


다양한 유형의 카메라 보정 방법

다음은 주요 유형의 카메라 보정 방법입니다.

  1. 보정 패턴: 이미징 프로세스를 완전히 제어할 수 있는 경우 보정을 수행하는 가장 좋은 방법은 여러 관점에서 물체 또는 알려진 치수 패턴의 여러 이미지를 캡처하는 것입니다. 이번 포스트에서 배울 바둑판 기반 방식은 이 카테고리에 속합니다. 바둑판 패턴 대신 알려진 치수의 원형 패턴을 사용할 수도 있습니다.
  2. 기하학적 단서: 때때로 보정에 사용할 수 있는 직선 및 소실점과 같은 다른 기하학적 단서가 장면에 있습니다.
  3. 딥 러닝 기반: 이미징 설정에 대한 제어가 거의 없는 경우(예: 장면의 단일 이미지가 있는 경우) 딥 러닝 기반 방법을 사용하여 카메라의 보정 정보를 얻는 것이 여전히 가능할 수 있습니다.

 

 

 

단계별 카메라 보정

보정 프로세스는 아래에 주어진 순서도에 의해 설명됩니다.


이 단계들을 살펴보겠습니다.

 

 


1단계: 바둑판 패턴으로 실제 좌표 정의

 


세계 좌표계(World Coordinate System) : 우리의 세계 좌표는 방의 벽에 부착된 이 바둑판 패턴으로 고정됩니다. 3D 포인트는 바둑판에 있는 사각형의 모서리입니다. 위 보드의 모든 모서리는 세계 좌표계의 원점으로 선택할 수 있습니다. X_w 및 Y_w 축은 벽을 따라 있고 Z_w 축은 벽에 수직입니다. 따라서 바둑판의 모든 점은 XY 평면에 있습니다(즉, Z_w = 0 ).

 

보정 과정에서 우리는 이미 알고 있는 3D 포인트(X_w, Y_w, Z_w)와 이미지의 해당 픽셀 위치(u,v)로 카메라 매개변수를 계산합니다.

3D 포인트의 경우 다양한 방향에서 알려진 치수의 바둑판 패턴을 촬영합니다. 세계 좌표는 바둑판에 붙어 있고 모든 모서리 점이 평면에 있기 때문에 모든 점이 0이 되도록 Z_w를 임의로 선택할 수 있습니다. 바둑판에서는 점들이 균등하게 간격을 두고 있기 때문에, 각 3D 점의 (X_w, Y_w) 좌표는 한 점을 기준으로 (0, 0) 그 기준점에 대해 나머지를 정의함으로써 쉽게 정의됩니다.

 

 


왜 체커보드 패턴이 교정에 널리 사용됩니까?

바둑판 패턴은 뚜렷하고 이미지에서 쉽게 감지할 수 있습니다. 뿐만 아니라 바둑판의 사각형 모서리는 두 방향으로 급격한 기울기를 가지므로 위치 파악에 이상적입니다. 또한 이러한 모서리는 바둑판 선의 교차점에 있다는 사실과도 관련이 있습니다. 이러한 모든 사실은 바둑판 패턴에서 사각형의 모서리를 견고하게 찾는 데 사용됩니다.

감지된 바둑판 모서리를 그린 후 결과

 

 


2단계: 다양한 시점에서 바둑판의 여러 이미지를 캡처합니다.

 

위의 이미지는 카메라 보정에 사용됩니다.


다음으로, 우리는 바둑판을 정적으로 유지하고 카메라를 움직여 바둑판의 여러 이미지를 찍습니다.

또는 카메라를 일정하게 유지하고 다른 방향에서 바둑판 패턴을 촬영할 수도 있습니다. 두 상황은 수학적으로 유사합니다.

 

 


3단계 : 바둑판의 2D 좌표 찾기

이제 바둑판 이미지가 여러 개 있습니다. 우리는 또한 세계 좌표에서 바둑판에 있는 점의 3D 위치를 알고 있습니다. 마지막으로 필요한 것은 이미지에서 이러한 바둑판 모서리의 2D 픽셀 위치입니다.


3.1 바둑판 모서리 찾기

OpenCV는 바둑판을 찾고 모서리 좌표를 반환하는 findChessboardCorners라는 내장 함수를 제공합니다. 아래 코드 블록에서 사용법을 살펴보겠습니다. 그것의 사용법은

C++

bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE )


Python

retval, corners = cv2.findChessboardCorners(image, patternSize, flags)


여기서,

image 소스 체스판 보기. 8비트 회색조 또는 컬러 이미지여야 합니다.
patternSize 체스판 행 및 열당 내부 모서리 수
( patternSize = cvSize (points_per_row, points_per_colum) = cvSize(columns,rows) ).
corners 감지된 모서리의 출력 배열입니다.
flags 다양한 작업 플래그입니다. 일이 잘 되지 않을 때에만 이것들에 대해 걱정해야 합니다.
기본값으로 실행합니다.

 

패턴이 감지되었는지 여부에 따라 출력이 true 또는 false입니다.

 

 


3.2 바둑판 모서리 다듬기

좋은 교정은 정밀도에 관한 것입니다. 좋은 결과를 얻으려면 하위 픽셀 수준의 정확도로 모서리 위치를 얻는 것이 중요합니다.

OpenCV의 cornerSubPix 함수는 원본 이미지와 모서리 위치를 가져와서 원래 위치의 작은 이웃 내에서 가장 좋은 모서리 위치를 찾습니다. 알고리즘은 본질적으로 반복적이므로 종료 기준(예: 반복 횟수 및/또는 정확도)을 지정해야 합니다.

C++

void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)


Python

cv2.cornerSubPix(image, corners, winSize, zeroZone, criteria)



여기서,

image 이미지를 입력합니다.
corners 입력 모서리의 초기 좌표와 출력을 위해 제공되는 정제된 좌표.
winSize 검색 창의 측면 길이의 절반입니다.
zeroZone 아래 공식의 합산이 이루어지지 않은 탐색 영역의 중간에 있는 사각 영역 크기의 절반.
자기상관 행렬의 가능한 특이성을 피하기 위해 때때로 사용됩니다.
(-1,-1) 값은 그러한 크기가 없음을 나타냅니다.
criteria 모서리 미세 조정의 반복 프로세스 종료 기준.
즉, 모서리 위치 미세 조정 프로세스는 criteria.maxCount 반복 후 또는 일부 반복에서 모서리 위치가 criteria.epsilon 미만으로 이동할 때 중지됩니다.

 

 


4단계: 카메라 보정

보정의 마지막 단계는 세계 좌표의 3D 포인트와 모든 이미지의 2D 위치를 OpenCV의 calibrateCamera 메서드에 전달하는 것입니다. 구현은 Zhengyou Zhang의 논문을 기반으로 합니다. 수학은 약간 복잡하며 선형 대수학에 대한 배경 지식이 필요합니다.

calibrateCamera의 구문을 살펴보겠습니다.

C++

double calibrateCamera(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs)


Python

retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objectPoints, imagePoints, imageSize)



여기서,

objectPoints 3D 점의 벡터로 구성된 벡터입니다.

외부 벡터는 패턴 뷰의 수만큼 요소를 포함합니다.
imagePoints 2D 이미지 점의 벡터로 구성된 벡터입니다.
imageSize 이미지의 크기
cameraMatrix 내장 카메라 매트릭스
distCoeffs 렌즈 왜곡 계수. 이 계수는 향후 포스트에서 설명하겠습니다.
rvecs 회전은 3×1 벡터로 지정됩니다. 벡터의 방향은 회전 축을 지정하고 벡터의 크기는 회전 각도를 지정합니다.
tvecs 3×1 이동 벡터.

 

 


카메라 보정 코드

Python 및 C++를 사용한 카메라 보정 코드는 아래에 공유됩니다. 그러나 아래 링크를 사용하여 모든 이미지와 코드를 다운로드하는 것이 훨씬 간단합니다.

 


카메라 보정을 위한 Python 코드

각 단계가 수행하는 작업을 설명하는 코드 주석을 읽어 보십시오.

#!/usr/bin/env python

import cv2
import numpy as np
import os
import glob

# Defining the dimensions of checkerboard
CHECKERBOARD = (6,9)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# Creating vector to store vectors of 3D points for each checkerboard image
objpoints = []
# Creating vector to store vectors of 2D points for each checkerboard image
imgpoints = [] 


# Defining the world coordinates for 3D points
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)
prev_img_shape = None

# Extracting path of individual image stored in a given directory
images = glob.glob('./images/*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # Find the chess board corners
    # If desired number of corners are found in the image then ret = true
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE)
    
    """
    If desired number of corner are detected,
    we refine the pixel coordinates and display 
    them on the images of checker board
    """
    if ret == True:
        objpoints.append(objp)
        # refining pixel coordinates for given 2d points.
        corners2 = cv2.cornerSubPix(gray, corners, (11,11),(-1,-1), criteria)
        
        imgpoints.append(corners2)

        # Draw and display the corners
        img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)

cv2.destroyAllWindows()

h,w = img.shape[:2]

"""
Performing camera calibration by 
passing the value of known 3D points (objpoints)
and corresponding pixel coordinates of the 
detected corners (imgpoints)
"""
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

print("Camera matrix : \n")
print(mtx)
print("dist : \n")
print(dist)
print("rvecs : \n")
print(rvecs)
print("tvecs : \n")
print(tvecs)



C++ 코드
각 단계를 이해하려면 주석을 읽어보세요.

#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
#include <iostream>

// Defining the dimensions of checkerboard
int CHECKERBOARD[2]{6,9}; 

int main()
{
  // Creating vector to store vectors of 3D points for each checkerboard image
  std::vector<std::vector<cv::Point3f> > objpoints;

  // Creating vector to store vectors of 2D points for each checkerboard image
  std::vector<std::vector<cv::Point2f> > imgpoints;

  // Defining the world coordinates for 3D points
  std::vector<cv::Point3f> objp;
  for(int i{0}; i<CHECKERBOARD[1]; i++)
  {
    for(int j{0}; j<CHECKERBOARD[0]; j++)
      objp.push_back(cv::Point3f(j,i,0));
  }


  // Extracting path of individual image stored in a given directory
  std::vector<cv::String> images;
  // Path of the folder containing checkerboard images
  std::string path = "./images/*.jpg";

  cv::glob(path, images);

  cv::Mat frame, gray;
  // vector to store the pixel coordinates of detected checker board corners 
  std::vector<cv::Point2f> corner_pts;
  bool success;

  // Looping over all the images in the directory
  for(int i{0}; i<images.size(); i++)
  {
    frame = cv::imread(images[i]);
    cv::cvtColor(frame,gray,cv::COLOR_BGR2GRAY);

    // Finding checker board corners
    // If desired number of corners are found in the image then success = true  
    success = cv::findChessboardCorners(gray, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
    
    /* 
     * If desired number of corner are detected,
     * we refine the pixel coordinates and display 
     * them on the images of checker board
    */
    if(success)
    {
      cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.001);
      
      // refining pixel coordinates for given 2d points.
      cv::cornerSubPix(gray,corner_pts,cv::Size(11,11), cv::Size(-1,-1),criteria);
      
      // Displaying the detected corner points on the checker board
      cv::drawChessboardCorners(frame, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, success);
      
      objpoints.push_back(objp);
      imgpoints.push_back(corner_pts);
    }

    cv::imshow("Image",frame);
    cv::waitKey(0);
  }

  cv::destroyAllWindows();

  cv::Mat cameraMatrix,distCoeffs,R,T;

  /*
   * Performing camera calibration by 
   * passing the value of known 3D points (objpoints)
   * and corresponding pixel coordinates of the 
   * detected corners (imgpoints)
  */
  cv::calibrateCamera(objpoints, imgpoints, cv::Size(gray.rows,gray.cols), cameraMatrix, distCoeffs, R, T);

  std::cout << "cameraMatrix : " << cameraMatrix << std::endl;
  std::cout << "distCoeffs : " << distCoeffs << std::endl;
  std::cout << "Rotation vector : " << R << std::endl;
  std::cout << "Translation vector : " << T << std::endl;

  return 0;
}