WPF OpenCV 프로젝트 #23: Affine Transform (어핀 변환)

Affine Transform (어핀 변환)대한 내용을 간략히 정리하고 WPF OpenCV 프로젝트에 추가하도록 하겠습니다. 지난 포스팅(#22)에서는 이미지의 이동, 회전, 확대/축소 같은 기본적인 Geometric Transform(기하학적 변환)을 다뤘습니다. 기억하시죠?

오늘은 Geometric Transform 에서 조금 더 나아가 Affine Transform (어핀 변환)에 대해 알아보고 구현해 보겠습니다. 이미지를 단순히 돌리고 키우는 것을 넘어, 이미지를 ‘찌그러뜨리거나 뒤틀어 버리는(Warping)’ 재미있는 작업입니다. 하지만 “직선의 평행성은 유지된다”는 것이 핵심이죠.

Affine Transform (어핀 변환)

Affine Transform (어핀 변환)은 영상의 평행성을 유지하면서 직선을 직선으로 보존하는 변환입니다.
말이 좀 어렵나요? 음… 조금 더 쉽게 표현하면 “직사각형을 평행사변형으로 만드는 변환“이라고 생각하면 됩니다. (이게 더 어렵게 느껴지는 건 아니겠죠?)

수학적 표현 : 3개의 Point

Affine Transform (어핀 변환)은 내부적으로 2×3 행렬을 사용합니다.

M=[a11a12txa21a22ty]M = \begin{bmatrix} a_{11} & a_{12} & t_x \\ a_{21} & a_{22} & t_y \end{bmatrix}
  • tx,tyt_x, t_y : 이동(Translation) 관련 요소.
  • a11, a22: 크기(Scale) 관련 요소.
  • a12, a21: 회전(Rotation)이나 전단(Shear – 밀림)과 관련 요소.

이 행렬을 완성하려면 총 6개의 미지수를 알아내야 합니다. 점 하나(x, y)당 식을 2개 만들 수 있으니, 총 3개의 점(Point) 쌍이 있으면 이 행렬을 완벽하게 계산할 수 있습니다. (OpenCV가 다 알아서 계산해 줍니다!)

구현 요약

이번 Affine Transform(어핀 변환) 포스팅 구현의 핵심은 “마우스로 점 3개를 찍어서 변환할 기준을 잡는 것”입니다. 기억해 두세요.

  1. AlgorithmParameters.cs: 점 3개(Pt1, Pt2, Pt3)를 저장할 AffineParams 클래스 생성.
  2. MainWindow.xaml: 점 좌표를 보여줄 UI 및 마우스 클릭 이벤트 처리.
  3. MainWindow.xaml.cs: 마우스로 점을 찍으면 화면에 원(Circle)을 그려주는 로직 구현.
  4. OpenCVService.cs: Cv2.GetAffineTransform 으로 행렬을 구하고 적용.

Step 1: AlgorithmParameters (Model)

AffineParams 클래스를 새로 만듭니다. 사용자가 지정한 3개의 점 좌표(Point2f)를 저장할 속성이 필요합니다. 사용자가 찍을 3개의 점 좌표와 보간법 설정을 담을 클래스입니다.

Step 2 & 3: UI (View) & MouseEvent

AffineParams용 UI(view) 에 <DataTemplate>을 만들어 <Window.Resource> </Window.Resource> 사이에 추가합니다. (3개 점의 좌표를 텍스트로 보여주고, “Reset Points” 버튼 등을 배치할 수 있습니다.) 사용자가 점을 찍어야 하니 UI에서 좌표를 보여주고, 마우스 클릭 시 시각적 피드백(점 그리기)을 줘야 합니다. 먼저 MainWindow.xaml 파일에 추가할 코드는 아래 같이 진행해 주세요.

MouseEvent 관련 부분을 MainWindow.xaml.cs 파일ZoomBorder_MouseDown() 이벤트 함수에 다음과 같이 코드를 추가합니다.

Step 4: OpenCVService

가장 중요한 영상 처리를 위한 Affine Transform 부분으로 여기서는 “사용자가 찍은 3개의 점(Input)”을 “출력 이미지의 고정된 코너 3곳(Output)”으로 매핑하는 방식을 사용했습니다. 이렇게 하면 사용자가 선택한 영역이 쫙 펴지는 효과를 볼 수 있습니다. ProcessImageAsync() 함수의 switch 구문 내에 아래의 코드로 추가 해주세요.

실행 및 결과

이제 추가된 코드들을 빌드하여 실행해 보도록 하겠습니다. 실행 해 보면서 검증을 해보면 뭔가 변환이 되는 듯 보일 겁니다. 그런데 이게 왜 이렇게 되지? 라는 의문이 생길 수 있어 정리를 한번 하고 가죠.

Step 진행 시 설명을 덧붙이려다가 마지막에 한번에 정리하는게 좋겠다 싶어 여기서 썰을 풀겠습니다.

Affine Transform은 앞서 평행선을 유지하는 선형 변환이라고 했던것 기억하시죠? 여기서 구현한 방식은 사용자가 3개의 점 (입력)을 출력 이미지의 정해진 위치(출력)으로 강제 이동 시키도록 하였습니다.

입력 점은 사용자가 마우스로 찍은 P1(빨강), P2(라임), P3(파랑) 이구요. 출력 점은 Step 4 (OpenCVService.cs)에서 고정된 위치로 매핑되도록 정해 두었습니다. P1 -> 좌상단 (0,0), P2 -> 우상단(Width, 0), P3->좌하단(0, Height) 로 가도록 말이죠. 이 규칙 때문에 3개의 점을 이용해서 이미지가 회전하고 늘어나는 겁니다.

P1과 P2를 연결하는 선분을 상상해볼까요? 만약 두 점의 선분이 오른쪽 위로 기울어져 있다면, Affine Transform수평을 맞추려고 이미지를 시계 방향으로 회전 시켜 수형을 맞춥니다. 반대라면 시계 방향으로 회전 시켜 수평을 맞추는 거죠. 즉 P1에서 P2가는 벡터가 수평선(X축)이 되도록 이미지를 돌리는 겁니다. (이해 가죠?)

즉 P1 은 무조건 왼쪽 위로 가고, P2는 무조건 오른쪽 위로 갑니다. 마지막 P3은 무조건 왼쪽 아래로 가는 거죠.

그리고 이미지의 크기는 원본 이미지 사이즈에 맞추도록 했기 때문에 이미지가 확대/축소 되는 거죠. 점의 간격을 짧게 찍으면 이미지가 엄청나게 확대되고, 점의 간격을 넓게 찍으면 축소되어 보일 겁니다.

제가 가지고 있는 이미지를 이용해서 실행한 것을 아래에 첨부하였으니, 위 내용을 토대로 한번 곰곰히 생각들 해보세요. 다음 포스팅에서는 이미지의 원근 변환(Perspective Transform)에 대해 다루도록 하겠습니다.

이미지로딩_원본이미지

참고 자료

[Post #22] 기하학적 변환: [WPF OpenCV Project #22] – 이동, 회전, 스케일 기초
GetAffineTransform: OpenCV Docs – getAffineTransform – 3점 매핑 행렬 계산 함수
WarpAffine: OpenCV Docs – warpAffine – 변환 적용 함수
Affine Transformation: Wikipedia – Affine transformation – 기하학적 정의와 행렬 수식

댓글 남기기