WPF OpenCV 프로젝트 #24: Perspective Transform (원근 변환) 구현

Perspective Tranform (원근 변환)에 대해 알아보고 WPF OpenCV 프로젝트에 추가하도록 하겠습니다.
지난 포스팅(#23)에서는 Affine Transform (어핀 변환)을 통해 이미지를 평행 하게 찌그러뜨리는 법을 배웠습니다. 어핀 변환의 핵심은 “평행한 선은 영원히 평행하다” 라고 했었던 것 기억하죠?

하지만 현실 세계는 다릅니다. 기찻길을 생각해보세요. 분명히 두 레일은 평행 하지만, 멀리 볼수록 한 점(소실점)에서 만나는 것처럼 보입니다. 이것이 바로 Perspective (원근감)입니다.

오늘은 이 원근감을 다루는 Perspective Transform (원근 변환), 다른 말로 투시 변환 이라고 하는데, 이것을 구현해 보겠습니다. 이 알고리즘은 명함 인식이나 문서 스캔 앱에서 기울어진 문서를 정면으로 쫙 펴줄 때 필수적으로 사용되는 아주 유용한 기술입니다. 구현 후 테스트 할 때 아주 재미 있을 겁니다.

Perspective Transform (원근 변환)

3차원 실 세계를 2차원 카메라로 찍으면 가까운 건 크게, 먼 건 작게 나옵니다. 이 과정에서 직사각형 모양의 물체(예: A4 용지)는 사다리꼴이나 임의의 사각형 모양으로 왜곡됩니다. 사람이 원근감을 느끼는 것은 실 세계가 3차원 좌표계이기 때문인데요. 영상은 2차원 좌표계죠. 그래서 차원 간의 보정을 해줘야 하기에 추가 적인 연산과 시스템이 필요합니다. 이때 사용하는 좌표계를 동차 좌표(Homogeneous coordinates)라고 해서, Perspective Transform (원근 변환)Homography (호모 그래피) 라고도 합니다. Perspective Transform(원근 변환)은 영상 처리의 기하학적 변환의 대장이라고도 할 수 있겠네요. 이전 포스팅의 Affine Transform(어핀 변환)은 조건이 있었던 거 기억하나요? “평행한 선은 영원히 평행하다” 하지만 원근 변환은 앞서 설명했던 3차원 공간을 2차원 카메라에 모방하기 때문에 평행한 선도 멀어지면 하나의 점으로 보입니다. 이번에 구현하겠지만, 찌그러진 문서를 펴거나, 자율 주행 차선 인식에서 Bird’s Eye View 변환에 필수적으로 사용됩니다.
원근 변환은 이 왜곡을 바로잡거나, 반대로 왜곡을 줄 수 있습니다.
항상 그래왔던 것처럼 어려운 수학식으로 표현할 수도 있지만 그렇게 하지 않겠습니다. 그냥 이해하기에 딱~ 필요한 부분만 정리하고 구현하도록 하겠습니다.
어핀 변환(점 3개)과 달리, 원근 변환은 3×3 행렬(Homography matrix)을 사용합니다.

H=[h11h12h13h21h22h23h31h32h33]H = \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix}

이 행렬을 완성하기 위해서는 총 8개의 미지수를 구해야 합니다(마지막 h33은 보통 1로 고정). 점 하나(x, y)당 2개의 식이 나오므로, 총 4개의 점(Point) 쌍이 있어야 정확한 변환 행렬을 계산할 수 있습니다. 개념 정리는 이정도로 하고, 구현 상 주의 점을 정리하죠.

Precautions

아주 특별한 주의 사항은 아니지만, “4개의 점을 찍는 순서“에 주의 해야 합니다. OpenCV는 점의 순서가 뒤섞이면 이미지가 꼬여버립니다. 그래서 우리는 다음과 같은 ‘ㄹ자’와 유사한 순서(혹은 시계 방향)를 약속하고 구현하겠습니다.

  1. 좌상단 (Top-Left)
  2. 우상단 (Top-Right)
  3. 우하단 (Bottom-Right)
  4. 좌하단 (Bottom-Left)

※ 주의: 앞서 ‘ㄹ모양’이라고 언급했지만, 일반적으로 [좌상 -> 우상 -> 우하 -> 좌하] 순서는 시계 방향에 가깝습니다. 코드 구현 시 반드시 이 순서를 꼭 지켜주세요!

Step 1: AlgorithmParameters (Model)

AlgorithmParameters.cs 파일에 PerspectiveParams 클래스를 아래와 같이 추가합니다. Property 변수에는 4개의 점 좌표와 보간법 설정을 클래스로 구현 합니다.

Step 2 : UI(View)

MainWindow.xaml 파일에 UI(View)에 연결된 PerspectiveParams 클래스 객체를 데이터 바인딩 하고, UI Element(TextBlock, TextBox, ComboBox) 들을 아래와 같이 작성합니다. 당연히 <Window.Resource> </Window.Resource> 사이에 넣어야 겠죠?

Step 3: Mouse Event

MainWindow.xaml.cs (마우스 클릭 이벤트) 이미지를 클릭할 때마다 점을 추가하고 화면에 원을 그립니다. 4개가 모두 그려진 상태에서 더 추가하면 초기화 되도록 합니다.

Step 4: ViewModel

MainViewModel.cs 파일에서 제일 MainViewModel() 생성자 함수에 추가할 알고리즘 문자열(Perspective Transform)을 추가합니다. 그리고 사용자가 UI(View)에서 Perspective Transform을 선택하였을 때, PerspectiveParams 객체를 생성하도록 CreateParametersForAlgorithm 함수에 아래와 같이 코드를 추가합니다.

Step 5: OpenCV 구현 (Service)

마지막으로 OpenCVService 파일에서는 Perpective 변환을 실행 할 코드를 ProcessImageAsync() 함수에 추가합니다. 가장 중요한 변환 로직으로 사용자가 지정한 4개의 점(찌그러진 영역)을, 반듯한 직사각형(원본 이미지 전체 크기)으로 매핑합니다.

실행 및 검증

자, 이제 빌드 후 이미지를 이용해 검증 테스트해 볼까요? 책상 위에 비스듬히 놓인 책이나 문서 이미지, 명함(Name Card) 이미지 등을 준비하세요. 그리고 [좌상 -> 우상 -> 우하 -> 좌하] 순서로 모서리를 콕콕 찍어줍니다. 아래에 제가 클릭한 4개의 점 좌표와 같이 말이죠. 다시 한번 강조하지만, 순서! 굉장히 중요합니다. 잊지 마세요. (만약 점을 찍는 순서가 달라지면, 이미지가 리본 처럼 꼬이거나, 뒤집혀서 이미지가 알아볼 수 없는 형태로 변형되니 꼭 지켜 주세요.)

이번 포스팅으로 Geometric Transform(기하학적 변환)끝판 왕Perspective Transform (원근 변환)까지 다뤘습니다. 다음 포스팅에서는 렌즈의 왜곡(Lens Distortion)을 다뤄 보도록 하겠습니다.

이미지 로딩_이미지1
이미지1_4개의 임의의 좌표지정
Perspective Transform Image
이미지로딩_이미지2
이미지2_4개의 임의의 좌표 지정
Perspective Transform Image

참조 자료

[Post #23] 어핀 변환: [WPF OpenCV Project #23] – 점 3개를 이용한 Affine Transform
GetPerspectiveTransform: OpenCV Docs – getPerspectiveTransform – 4점 매핑 행렬 계산
WarpPerspective: OpenCV Docs – warpPerspective – 원근 변환 적용
Homography (Computer Vision): Wikipedia – Homography – 투시 변환의 수학적 원리

댓글 남기기