카메라 왜곡보정 - 이론 및 실제

영상처리 2013. 2. 4. 02:49

저번 카메라 캘리브레이션에 대한 포스팅에 이어 오늘은 카메라 렌즈 왜곡 보정에 대해 이론에서 실제까지 전반적인 내용을 포스팅합니다.

 

카메라 캘리브레이션 및 왜곡 보정은 영상처리 분야에서는 거의 필수적으로 필요한 내용입니다. 하지만 막상 본인 스스로 이런 것들을 하려면 쉽지가 않습니다. 전반적인 카메라 시스템에 대한 이해가 필요하며 이것 저것 알아보는데 많은 시간이 필요합니다. 외국의 경우 (주로 영어권) 이러한 정보공유가 잘 되어 있어서 서로서로 잘 발전해 나가지만 우리에게는 진입장벽이 높은 편입니다. 본 글은 이러한 시간을 단축시키고 많은 분들이 좀더 손쉽게 캘리브레이션 및 영상 왜곡보정에 접근할 수 있도록 관련된 실질적인 이론 및 방법, 절차를 최대한 쉽게 정리하고자 노력하였습니다.

 

1. 렌즈계 왜곡의 종류

2. 렌즈계 왜곡의 수학적 모델

3. 영상 왜곡 보정

4. 영상좌표 왜곡 보정

5. 샘플 프로그램

 

카메라에 시야각(FOV)이 넓은 광각렌즈나 초광각 렌즈를 사용하면 넓은 범위를 볼 수 있지만 이로 인해 아래 그림과 같이 상대적으로 영상왜곡이 심해지는 문제가 있습니다.

 

그림출처: http://www.ivs.auckland.ac.nz/web/calibration.php

 

이러한 영상 왜곡은 시각적인 문제 외에도 영상분석을 통해 정확한 수치 계산이 필요할 때 특히 문제가 됩니다. 예를 들어서, 영상에서 검출한 물체의 실제 위치를 알기 위해 영상좌표를 물리적인 좌표로 변환한다면 영상왜곡 정도에 따라 심각한 오차가 발생할 것입니다.

 

 

1. 렌즈계 왜곡의 종류

 

렌즈 왜곡에는 크게 방사왜곡(radial distortion)과 접선왜곡(tangential distortion)이 있습니다.

 

방사왜곡은 볼록렌즈의 굴절률에 의한 것으로서 아래 그림과 같이 영상의 왜곡 정도가 중심에서의 거리에 의해 결정되는 왜곡입니다. 

 

방사왜곡(radial distortion)

 

반면, 접선왜곡(tangential distortion)은 카메라 제조(조립) 과정에서 카메라 렌즈와 이미지센서(CCD, CMOS)의 수평이 맞지 않거나 또는 렌즈 자체의 centering이 맞지 않아서 발생하는 왜곡으로서 아래 그림과 같이 타원형 형태로 왜곡 분포가 달라집니다 (접선왜곡은 다른 말로 decentering distortion 이라고도 불립니다). 참고로 http://carstart.tistory.com/181에 보면 방사왜곡과 접선왜곡의 개념이 그림을 통해 직관적으로 잘 설명되어 있습니다. 

 

접선왜곡(tangential distortion)

 

 

2. 렌즈계 왜곡의 수학적 모델

 

일반적으로 렌즈 왜곡의 수학적 모델은 카메라 내부 파라미터의 영향이 제거된 normalized image plane에서 정의됩니다.

 

렌즈계의 왜곡이 없다고 할 경우, 3차원 공간상의 한 점 (Xc, Yc, Zc)는 central projection (pinhole projection)에 의해 normalized image plane상의 한 점 (xn_u, yn_u)로 투영됩니다 (첨자 n: normalized, u: undistorted):

 

(1)

 

그러나 실제로는 (xn_u, yn_u)는 렌즈계의 비선형성에 의해 왜곡(주로 radial distortion)이 됩니다. (xn_d, yn_d)를 렌즈계의 왜곡이 반영된 normalized 좌표라면, 렌즈계 왜곡 모델은 다음과 같습니다 (첨자 d: distorted):

 

(2)

 

단,

 

 

위 수식에서, 우변의 첫번째 항은 radial distortion, 두번째 항은 tangential distortion을 나타냅니다 (k1, k2, k3는 radial distortion coefficient, p1, p2는 tangential distortion coefficient). ru는 왜곡이 없을 때의 중심(principal point)까지의 거리(반지름)입니다.

 

이 때, (xn_d, yn_d)는 normalized image plane에서의 좌표이며 실제 영상 픽셀 좌표 (xp_d, yp_d)는 카메라 내부 파라미터를 반영하여 다음과 같이 구해집니다.

 

(3)

 

즉,

 

(4)

 

여기서, fx, fy는 초점거리, cx, cy는 렌즈 중심 영상좌표(principal point), skew_c는 비대칭계수를 나타내는 카메라 내부 파라미터들입니다. 카메라 내부 파라미터에 대한 자세한 내용은 [영상처리] - 카메라 캘리브레이션 (Camera Calibration)를 참조하시기 바랍니다.

 

 

3. 영상 왜곡 보정

 

왜곡된 영상으로부터 보정된 영상을 생성하는 것은 비교적 간단하게 구현할 수 있습니다 (참고로, 왜곡된 영상을 보정하기 위해서는 먼저 카메라 캘리브레이션을 통해 카메라 내부 파라미터(intrinsic parameter)를 구해야 합니다. 카메라 캘리브레이션에 대해서는 [영상처리] - 카메라 캘리브레이션 (Camera Calibration)를 참조하시기 바랍니다).

 

왜곡된 영상을 Id, 보정된 Iu라 하겠습니다. 기본 아이디어는 Iu의 각 픽셀값을 해당 픽셀 좌표를 왜곡시켰을 때의 Id의 대응되는 픽셀값으로 채우는 것입니다.

 

영상 Iu의 한 점을 (xp_u, yp_u)라 하면, 일단 이것을 카메라 파라미터를 역으로 적용하여 normalized 좌표 (xn_u, yn_u)로 변환합니다.

 

(5)

 

즉,

 

(6)

 

다음으로, 중심까지의 거리 ru (ru2 = xn_u2 + yn_u2)를 구하고 식(2) 왜곡모델을 적용하여 왜곡된 좌표 (xn_d, yn_d)를 구합니다.

 

(7)

 

마지막으로 (xn_d, yn_d)를 다시 픽셀 좌표계로 변환하면 (xp_u, yp_u)의 왜곡된 영상에서의 좌표 (xp_d, yp_d)를 구할 수 있습니다.

 

(8)

 

 

영상 왜곡보정 (예제 코드)

 

function undistort_image(Id, Iu, fx, fy, cx ,cy, skew_c, k1, k2, k3, p1, p2)

    w: image width of Iu

    h: image height of Iu

    for y = 0:h-1,

        for x = 0:w-1,

            y_nu = (y-cy)/fy;

            x_nu = (x-cx)/fx – skew_c*y_nu;

 

            ru2 = x_nu*x_nu + y_nu*y_nu; // ru2 = ru*ru

            radial_d = 1 + k1*ru2 + k2*ru2*ru2 + k3*ru2*ru2*ru2;

 

            x_nd = radial_d*x_nu + 2*p1*x_nu*y_nu + p2*(ru2 + 2*x_nu*x_nu);

            y_nd = radial_d*y_nu + p1*(ru2 + 2*y_nu*y_nu) + 2*p2*x_nu*y_nu;

 

            x_pd = fx*(x_nd + skew_c*y_nd) + cx;

            y_pd = fy*y_nd + cy;

 

            Iu(x, y) = Id(x_pd, y_pd);

        end;

    end;    

return Iu;

 

 

 

☞ (2017.05.16) 정말 많은 분들이 왜 이렇게 왜곡보정을 해야 하는지 이해하기 힘들다고 합니다. 그래서 부연설명을 추가합니다. I가 입력영상(왜곡된 영상), I'을 왜곡을 편 영상(보정된 영상)이라 하겠습니다. 직관적으로 생각할 수 있는 방법은 원본영상의 각 픽셀 (x, y)에 대응되는 왜곡 보정된 좌표 (x', y')을 구한 후, 해당 위치의 보정영상에 대입하는 것입니다 (즉, ∀x,y, I'(x',y') = I(x,y)). 그런데, 이렇게 왜곡을 펴면 보정 영상(I')에 구멍이 뚫려서 보기 흉하게 되는 문제가 발생합니다. 이미지의 왜곡을 펴는 것은 손으로 잡고 쭉 늘이는 것과 같기 때문에 원본 픽셀을 보정영상에 대입하면 보정된 영상에서는 중간 중간 이빨이 빠진 것처럼 빈 픽셀(pixel)들이 발생하게 됩니다. 따라서 이러한 문제를 해결하기 위해서는 위의 예제 코드와 같이 발상의 전환이 필요합니다. 그건 입력영상의 왜곡을 펴서 보정영상을 만드는 것이 아니라 보정영상을 먼저 만들어 놓고 보정영상의 각 픽셀에 대응되는 원본영상의 픽셀을 가져오는 것입니다. 즉, 보정영상의 각 픽셀좌표 (x', y')에 대해 왜곡된(왜곡을 일부러 가한) 좌표를 구하고 이렇게 구한 좌표에 대응하는 원본영상의 픽셀을 가져와 보정영상에 대입합니다. 이렇게 하면 이빨빠짐 현상이 없는 깨끗한 영상이 얻어집니다.

 

☞ (2022.11.04) 위 설명도 지금보니 어렵네요. 다시 추가합니다.. 철수에게 늘어난(왜곡된) 그림을 주고는, 반듯하게 보정된 그림을 옆에 새로 그려라는 미션을 주었습니다. 철수는 고민하다가, 먼저 새로운 도화지에 눈금(모눈종이처럼)을 그립니다. 그리고, 첫번째 눈금에 해당되는 값을 원래의 일그러진 그림에서 찾아다가 그려 넣습니다. 그리고 두번째 눈금에 해당하는 값도 찾아서 넣습니다. 이렇게 그렸더니 깨끗하게 복원된 새로운 그림을 얻을 수 있었습니다. 

 

만일, 단순한 모델을 사용하고 싶거나 카메라 파라미터가 충분치 않으면 위 수식들을 적절히 수정하면 됩니다. 예를 들어, 대부분의 경우 radial distortion이 주이고 tangential distortion은 미약하기 때문에 식 (2)에서 tangential term을 생략하기도 합니다 (p1=p2=0). 또한 radial term도 k1, k2까지만 사용하는 것이 일반적입니다 (즉, k3 = 0). 비대칭 계수인 skew_c도 대부분 생략됩니다 (skew_c = 0). (cx, cy)도 구하기 힘든 경우에는 영상 중심(image_width/2, image_height/2)으로 놓기도 합니다 (하지만 cx, cy는 중요하기 때문에 가급적 정확하게 측정하는게 좋습니다).

 

 

4. 영상좌표 왜곡 보정

 

실제 영상처리에서는 종종 보정된 영상을 구하는 것보다는 보정된 영상좌표를 구하는 것이 훨씬 중요합니다. 왜냐하면 때로는 영상전체를 보정한 후에 영상처리 알고리즘을 적용하는 것보다는 원래 왜곡된 영상에서 처리를 한 후에 그 결과만 보정(undistort)을 하는 것이 효과적일 수 있기 때문입니다.

 

그런데, 왜곡된 영상좌표로부터 왜곡 보정된 영상좌표를 구하는 것은 closed-form solution이 없는 매우 어려운 문제로서 보통 근사적으로 해를 구합니다 (식 7을 잘 보면 n_u로부터 n_d를 구하는 것은 쉬우나 반대로 n_d에서 n_u를 구하는 것은 7차 방정식을 풀어야 하는 매우 어려운 문제임을 알 수 있습니다).

 

왜곡된 영상좌표 p_d로부터 왜곡 보정된 좌표 p_u를 구하는 한 방법은 p_u = p_d로 가정하고 식 (2)를 이용해서 p_u를 왜곡시켜 보는 것입니다. 이렇게 구한 점이 실제 p_d와 얼마나 오차가 나는지를 계산해서 그 오차를 p_u에 역으로 반영시킵니다. 예를 들어, p_u를 왜곡시킨 점이 p_d보다 δ만큼 크다면 p_u를 δ만큼 감소시킵니다. 이러한 과정을 오차가 임계값 이하가 될 때까지 반복합니다 (보통 3~4번만 반복해도 거의 정확한 p_u값을 얻을 수 있습니다).

 

영상좌표 왜곡보정 (예제 코드)

 

 

function undistort(point p_d)

      p_d’ = normalize(p_d);

      p_u’ = p_d’;

      while (1)

            err = distort_normal(p_u’) – p_d’;

            p_u’ = p_u’ – err;

            if(err<err_threshold) break;

      end;

      p_u = denormalize(p_u’);

return p_u;

 

function normalize(x, y)

      y_n = (y-cy)/fy;

      x_n = (x-cx)/fx – skew_c*y_n;

return (x_n, y_n);

 

function denormalize(x, y)

      x_p = fx*(x + skew_c*y) + cx;

      y_p = fy*y + cy;

return (x_p, y_p);

 

function distort_normal(x, y)

      r2 = x*x + y*y;

      radial_d = 1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2;

      x_d = radial_d*x + 2*p1*x*y + p2*(r2 + 2*x*x);

      y_d = radial_d*y + p1*(r2 + 2*y*y) + 2*p2*x*y;

return (x_d, y_d);

 

 

 

 

5. 샘플 프로그램

 

라이브로 카메라 영상 또는 비디오 파일을 왜곡 보정하여 보여주는 예제 프로그램(소스 포함)은 [개발한 것들] - 라이브 왜곡 보정 프로그램을 참조해 주세요.

 

 

관련 포스팅

 

by 다크 프로그래머