OpenCV 실전 가이드: 카메라 캘리브레이션부터 PnP 거리 측정까지 단안 시각 위치 결정 구현하기

1. 카메라 캘리브레이션 기초와 실전 준비

단안 시각 위치 결정은 로봇에 "지혜로운 눈"을 장착하는 것과 같으며, 카메라 캘리브레이션은 이 눈이 세상을 올바르게 이해하도록 가르치는 과정입니다. 부적절한 시력 교정 렌즈를 쓰고 세상을 볼 때 물체의 위치와 형태가 왜곡되는 것과 같은 문제를 해결하는 것이 바로 카메라 캘리브레이션의 목적입니다.

실제 구현에 앞서 다음과 같은 하드웨어를 준비해야 합니다:

  • 일반 USB 카메라 (노트북 내장 카메라도 가능)
  • 인쇄된 체스판 캘리브레이션 보드 (A4 크기 권장)
  • 평평한 하드 보드 (캘리브레이션 보드 고정용)

체스판 캘리브레이션 보드는 7x7 그리드를 사용하고 각 칸의 크기는 20-30mm가 권장됩니다. 저는 보통 골판지를 기판으로 사용하여 가볍게 유지하면서도 평평함을 유지합니다. 작은 팁: 캘리브레이션 보드를 베이킹 트레이에 붙이면 평평함을 유지하면서 다양한 각도로 촬영하기 편리합니다.

2. 카메라 캘리브레이션 전 과정 상세 설명

2.1 이미지 수집 실전 기술

캘리브레이션 이미지를 수집할 때 많은 초보자들이 같은 각도에서만 촬영하는 실수를 합니다. "공간 8자법"을 사용하는 것을 권장합니다:

  1. 캘리브레이션 보드를 고정한 상태에서 카메라를 이동
  2. 좌상, 우상, 정면, 좌하, 우하의 다섯 가지 기본 방향에서 촬영
  3. 각 방향에서 ±30° 기울기 추가
  4. 마지막으로 20cm 정도의 근접 촬영 몇 장 추가
// 개선된 이미지 수집 코드
VideoCapture capture(0);
if(!capture.isOpened()) {
    cerr << "카메라 열기 실패, 장치 연결 확인 필요" << endl;
    return -1;
}

int counter = 1;
while(true) {
    Mat frame;
    capture >> frame;
    imshow("실시간 미리보기", frame);
    
    int key = waitKey(30);
    if(key == 's') {  // s 키를 눌러 저장
        string filename = format("calib_img_%02d.jpg", counter++);
        imwrite(filename, frame);
        cout << "저장됨: " << filename << endl;
    }
    else if(key == 27) break; // ESC로 종료
}

2.2 캘리브레이션 핵심 코드 상세 분석

장정우 캘리브레이션 방법의 핵심은 다수의 2D-3D 점 대응 관계를 통해 카메라 매개변수를 구하는 것입니다. 아래 코드는 오류 처리 메커니즘을 최적화했습니다:

// 개선된 캘리브레이션 코드
vector<vector<Point2f>> 이미지점들;
Size 보드크기(7,7);
float 정사각형크기 = 25.0f; // 체스판 실제 크기(mm)

// 월드 좌표계에서의 코너 점 좌표
vector<vector<Point3f>> 월드점들(1);
for(int i=0; i<보드크기.height; ++i)
    for(int j=0; j<보드크기.width; ++j)
        월드점들[0].emplace_back(j*정사각형크기, i*정사각형크기, 0);
월드점들.resize(이미지점들.size(), 월드점들[0]);

// 캘리브레이션 수행
Mat 내부매개변수, 왜곡계수;
vector<Mat> 회벡들, 평벡들;
double rms = calibrateCamera(월드점들, 이미지점들, 이미지크기, 
                            내부매개변수, 왜곡계수, 회벡들, 평벡들,
                            CALIB_FIX_K3 | CALIB_FIX_PRINCIPAL_POINT);

cout << "재투영 오차: " << rms << " 픽셀" << endl;

주요 매개변수 설명:

  • CALIB_FIX_K3: k3 왜곡 계수를 고정하여 과적합 방지
  • CALIB_FIX_PRINCIPAL_POINT: 주점 좌표를 고정하여 안정성 향상
  • 이상적인 RMS 오차는 0.5 픽셀 미만이어야 함

3. PnP 거리 측정 원리와 구현

3.1 solvePnP 알고리즘 심층 분석

PnP(Perspective-n-Point) 문제의 본질은 카메라 포즈를 구하는 것입니다. 다음을 알고 있을 때:

  1. 물체 3D 좌표(월드 좌표계)
  2. 대응하는 2D 이미지 좌표
  3. 카메라 내부 매개변수

카메라가 물체에 대한 회전(R)과 평행 이동(T)을 계산할 수 있습니다. solvePnP는 다양한 해결 방법을 제공합니다:

  • ITERATIVE (기본값): Levenberg-Marquardt 최적화 기반, 정확하지만 느림
  • EPNP: 점이 4개 이상일 때 적합, 빠름
  • P3P: 3개의 점만 필요하지만 노이즈에 민감
// 개선된 PnP 해결 코드
vector<Point3f> 물체점들 = {
    {-42.5, -42.5, 0},  // 좌상
    {42.5, -42.5, 0},   // 우상 
    {42.5, 42.5, 0},    // 우하
    {-42.5, 42.5, 0}    // 좌하
};

vector<Point2f> 이미지점들;
// 특징 추출 또는 수동 표시를 통해 이미지 좌표 획득

Mat 회벡, 평벡;
bool 성공 = solvePnP(물체점들, 이미지점들, 
                       내부매개변수, 왜곡계수,
                       회벡, 평벡, false, SOLVEPNP_ITERATIVE);

if(!성공) {
    cerr << "PnP 해결 실패!" << endl;
    return -1;
}

3.2 거리 계산과 정밀도 향상

평행 이동 벡터 T를 얻은 후 실제 거리 계산은 좌표계 변환에 특주를 기울여야 합니다. 신뢰할 수 있는 계산 공식을 정리했습니다:

Mat 회전행렬;
Rodrigues(회벡, 회전행렬);  // 회전 벡터에서 행렬로 변환

// 월드 좌표계에서 카메라 위치 계산
Mat 카메라위치 = -회전행렬.t() * 평벡;  
double 거리 = norm(카메라위치);  // 유클리드 거리 계산

// 더 정확한 Z축 거리 계산
Mat z축(3,1,CV_64F);
z축.at<double>(0) = 0;
z축.at<double>(1) = 0; 
z축.at<double>(2) = 1;
Mat 카메라Z = 회전행렬.t() * z축;
double z거리 = 평벡.dot(카메라Z);

실제 테스트에서 발견한 세 가지 정밀도 향상 팁:

  1. 최소 4개의 특징점 사용 (6-8개 권장)
  2. 특징점을 물체 주변에 최대한 분산
  3. 평면 물체의 경우 Z 좌표 설정이 정확한지 확인

4. 공학적 실용과 디버깅 기술

4.1 일반적인 문제 해결 가이드

프로젝트 구현 과정에서 많은 함정을 겪었으며, 여기서 몇 가전 전형적인 문제의 해결 방법을 공유합니다:

문제 1: 캘리브레이션 오차가 너무 큼

  • 체스판이 평평한지 확인
  • 다양한 촬영 각도 확보 (15-20장 권장)
  • findChessboardCorners의 창 크기 조정 시도

문제 2: PnP 결과가 불안정함

  • 월드 좌표계와 이미지 좌표계 대응 관계가 올바른지 확인
  • 특징점 좌표가 정확한지 확인
  • 다른 PnP 해결 방법 시도

문제 3: 거리 계산 편차가 큼

  • 물체 실제 크기 입력이 올바른지 검증
  • 카메라 내부 매개변수가 정확한지 확인
  • 물체와 카메라 광축이 기본적으로 수직인지 확인

4.2 성능 최적화 제안

실시간성이 높은 애플리케이션에는 다음 최적화 전략을 사용할 수 있습니다:

  1. 캘리브레이션 결과 캐싱: 카메라 매개변수를 YAML 파일로 저장
// 카메라 매개변수 저장
FileStorage fs("카메라_매개변수.yml", FileStorage::WRITE);
fs << "내부_매개변수" << 내부매개변수;
fs << "왜곡_계수" << 왜곡계수;
fs.release();

// 카메라 매개변수 읽기
FileStorage fs("카메라_매개변수.yml", FileStorage::READ);
fs["내부_매개변수"] >> 내부매개변수;
fs["왜곡_계수"] >> 왜곡계수;
fs.release();
  1. 멀티 스레딩 처리: 이미지 수집과 계산 분리
  2. ROI 최적화: 관심 영역에서만 특징 검색 수행

최근 AGV 네비게이션 프로젝트에서 위 최적화 기법을 통해 처리 속도를 기존의 200ms/프레임에서 50ms/프레임으로 향상시켜 실시간성 요구 사항을 충족시켰습니다. 핵심은 특정 애플리케이션 시나리오에 적합한 정확도와 속도의 균형점을 선택하는 것입니다.

태그: OpenCV 카메라 캘리브레이션 PnP 단안 시각 위치 결정 C++

7월 3일 02:44에 게시됨