WPF OpenCV 프로젝트 #26: Lens Distortion – Barrel & Pincushion

Barrel DistortionPincushion Distortion 을 이번 WPF OpenCV 프로젝트에서 구현하겠습니다.
지난 포스팅(#25)에서는 Cv2.Remap을 이용해 물결처럼 일렁이는 효과(Wave Effect)를 구현했습니다. Sin, Cos 함수로 픽셀을 흔들어주니 재미있는 결과가 나왔었죠?
그리고, Lens Distortion 의 내용을 정리 하면서 Barrel Distortion(볼록 왜곡)과 Pincushion Distortion(오목 왜곡)을 짧게 정리 하긴 했었는데, 이것을 프로젝트에 구현하진 않았습니다. Remap() 함수를 통해 이미지를 X방향과 Y 방향으로 삼각함수를 이용해서 픽셀 좌표를 왜곡 시키고 실행 했었는데요. 이번에는 볼록 왜곡과 오목 왜곡을 이미지에 대한 시각적 크기로 한번 정리하고 구현 후 실행 해서 이미지를 한번 살펴보도록 하죠. CCTV나 블랙박스, 혹은 GoPro 같은 광각 렌즈로 찍은 사진을 보면 가장자리가 둥글게 휘어지는 현상을 보신 적 있죠? 이런 효과를 영상 처리 방법으로 Simulation 보겠습니다.
(원래는 펴는 게 목적이지만, 찌그러뜨리는 법을 알아야 펴는 법도 알 수 있으니까요!)

Barrel Distortion (볼록 왜곡) – “풍선 효과”

  • 시각적 느낌: 이미지가 풍선처럼 부풀어 오른 느낌.
  • 중심부 크기: 커 보입니다 (확대).
  • 가장자리(Edge) 크기: 작아 보입니다 (압축)

Pincushion Distortion (오목 왜곡) – “빨려 들어가는 효과”

  • 시각적 느낌: 이미지의 중심을 손가락으로 꾹 눌러서 안으로 빨려 들어가는 느낌입니다.
  • 중심부 크기: 작아 보입니다 (축소).
  • 가장자리(Edge) 크기: 커 보입니다 (확대/늘어짐).

직교 좌표 vs 극 좌표

지난번 물결 효과는 x, y 좌표 (직교 좌표계)에서 바로 계산했습니다. 하지만 렌즈 왜곡은 “중심점으로부터의 거리”에 따라 휘어짐의 정도가 달라집니다.

그래서 바둑판 같은 직교 좌표(x, y)를 사용하는 것보다, 레이더망 같은 극 좌표 (r, θ\theta)를 사용하는 것이 훨씬 계산하기 편합니다.

알고리즘 순서:

  1. 변환 ( x,yr,θx, y \rightarrow r, \theta): 픽셀 위치를 ‘중심에서 얼마나 떨어졌는지(rr)’와 ‘어느 각도인지(θ\theta)’로 바꿉니다.
  2. 왜곡 (x,yr,θx, y \rightarrow r, \theta): 거리 rr 을 지수승(Power)하여 늘리거나 줄입니다.
    • rnew=rexponentr_{new} = r^{\text{exponent}}
  3. 복원 (rnew,θxnew,ynewr_{new}, \theta \rightarrow x_{new}, y_{new}): 왜곡된 거리를 다시 x,yx, y 좌표로 돌려놓습니다.

수식이 복잡해 보이지만, 핵심은 “거리를 조작한다”는 것이죠. 렌즈는 둥근 유리알입니다. 빛이 굴절되는 정도는 중심에서의 거리(rr)에 따라 결정됩니다. 만약 굴절 정도를 직교 좌표계에서 적용하려면, 오른쪽 3칸, 위로 5칸 이렇게 가기 때문에 왜곡 공식을 적용하기가 힘들죠. 하지만 극좌표계에서는 중심에서 10만큼 떨어진 거리(rr)에 있고, 45도 방향에 있다 와 같이 중심에서 거리 (rr) 만 늘리거나 줄이면 왜곡에 대한 처리는 끝나버리는 거죠. OpenCV에서는 직교 좌표 변환 함수와 극좌표 변환함수를 제공하는데요. 이들의 흐름을 아래와 같이 정리하고 구현하도록 하겠습니다.

[입력: 픽셀 좌표] -> cartToPolar():(직교 좌표 ->극좌표 변환) -> [왜곡 연산] -> polarToCart():극 좌표 ->직교 좌표 변환 -> [출력: 변환된 좌표]

구현 요약

  1. AlgorithmParameters.cs: RemapParams 클래스를 확장하여 렌즈 왜곡에 필요한 파라미터(Center Point, Mode, Exponent, Scale 등)를 추가 정의합니다.
  2. MainWindow.xaml: RemapParams를 위한 UI 템플릿을 추가하고, 이미지 위에서 마우스 클릭 시 십자가 표시를 위한 Canvas 등의 요소를 확인합니다. (기존 OverlayCanvas 활용하도록 하죠.)
  3. MainWindow.xaml.cs: 마우스 클릭 이벤트를 처리하여 “Lens Distortion” 모드일 때 클릭한 좌표를 RemapParams의 중심점(CenterX, CenterY)으로 설정하고, 화면에 십자가를 그리는 로직을 추가합니다.
  4. MainViewModel.cs: “Lens Distortion” 메뉴를 추가하고 파라미터를 연결합니다.
  5. OpenCVService.cs: RemapcartToPolar, polarToCart를 활용한 렌즈 왜곡 알고리즘을 구현합니다.

Step 1: AlgorithmParameters (Model)

AlgorithmParameters.cs 파일에서 이전 포스팅에서 추가된 RemapParams 클래스를 아래와 같이 Property 변수를 추가하여 확장합니다. 기존 파라미터와 새로운 렌즈 왜곡(볼록/오목) 파라미터가 모두 포함되도록 말이죠.

Step 2: UI (View)

MainWindow.xaml 파일에서는 RemapParams 클래스의 DataTemplate를 수정하도록 하죠. 이전 포스팅의 코드에서 가장 큰 변화는 Triggers를 사용해서 사용자가 선택한 Mode에 따라 필요한 설정 창만 보이도록 구현하였습니다. 아래의 코드를 참고해 주세요.

Step 3: UI (Behind Code)

MainWindow.xaml.cs 파일에서는 이전 포스팅의 코드를 그대로 사용하면 됩니다. Lens Distortion(렌즈 왜곡)에 대해 볼록 효과와 오목 효과를 분리하였다면 추가했어야 하는데, 앞서 설명하였던 것 처럼 RemapParams 클래스를 확장하였기 때문에 여기서는 추가할 것이 없습니다.

Step 4: ViewMode

MainViewModel.cs 파일에서는 Lens Distortion(렌즈 왜곡) 효과사용자가 마우스 클릭한 포인트를 기준으로 처리를 하기 때문에 ZoomBorder_MouseDown() 이벤트 함수에 추가 코드가 필요합니다. 그리고, 클릭한 마우스 포인트에 십자가 표시를 위해 DrawCrosshair() 함수도 추가하여 분리하였습니다.
아래 코드를 참고해 주세요.

Step 5: OpenCVService (Model)

OpenCVService.cs 코드에서는 사용자가 지정하고, 조정한 RemapParams 클래스의 파라미터를 이용해 블록 왜곡과 오목 왜곡을 처리하는 코드를 추가합니다. 사실 이 부분에 할 이야기가 좀 더 있긴 한데, 우리는 알고리즘 정리를 하고 구현을 마우스 클릭으로 지정된 위치를 기준으로 직교 좌표계 데이터를 극 좌표계로 변환하여 왜곡 연산을 하겠다고 했었습니다. 기억 하죠? 약속한 방식대로 코드를 아래와 같이 추가하도록 하겠습니다. ProcessImageAsync() 함수에 이전 포스팅에 구현했던 Lens Distortion (Remap) 구문을 아래와 같이 변경해 주세요.

실행 결과 확인

Lens Distortion (렌즈 왜곡) 에 대해 이전 포스팅을 포함해 조금 많은 내용이 있었는데요.

이번 포스팅을 정리해보면, 볼록 왜곡오목 왜곡 효과를 이미지에 적용하기 위해 파라미터 클래스를 따로 만들지 않았습니다. 이전 포스팅에 추가했던 RemapParams 클래스를 확장했습니다. 확장된 내용을 살펴 보면 왜곡 효과를 줄 좌표를 입력 받을 수 있도록 했구요. 왜곡 지수(exponent) 값을 이용해서 볼록 (1보다 클 경우) 과 오목 효과( 1보다 작고, 0보다 큰 경우)를 적용할 수 있도록 했습니다. 그리고, 왜곡 영역(Scale) 값을 사용하여 렌즈의 왜곡 효과를 줄 수 있는 원 모양의 영역의 크기를 비율로 지정하도록 했습니다. 마지막으로 사용자 좌표는 직교 좌표이므로, 왜곡 연산을 위해 극 좌표 변환이 필요한데, 그렇게 구한 좌표는 Remap() 함수를 사용해서 효과를 준거죠. (시퀀스가 어떻게 되고, 파라미터를 어떻게 설정해야 하는지 헤깔려 할까 싶어 다시 한번 정리한 겁니다.)
아래는 실행한 이미지들입니다. 확인해 보도록 하죠.

먼저 이미지를 불러 들입니다.

이미지로딩_원본이미지

알고리즘 선택 부분에서 Lens Distortion (Remap) 항목을 선택하고, 아래에 나타나는 속성 창에서 Distortion Mode 를 선택 후 LensSimulate 를 선택합니다. 그러면 속성 창 아래가 볼록/오목 왜곡 효과를 적용할 파라미터들로 아래 이미지와 같이 변경됩니다.

Lens Distortion 설정 파라미터

이상태에서 아래의 그림 처럼 이미지의 코 부분을 클릭하면 십자가 표시가 나타납니다. (잘 따라 오고 있죠?)

임의의 점 지정

Exponent (왜곡 지수) 값을 3.0으로 변경 해서 적용 해보죠. (1 보다 큰 임의의 값으로 변경해 보세요.)

볼록 왜곡 효과

이번에는 Exponent (왜곡 지수) 값을 0.5 로 변경 해서 실행해 봅니다. (1보다 작고, 0보다 큰 임의 값으로 변경해 보세요.)

오목 왜곡 효과

마지막으로 Exponent 값과 Scale 값을 임의의 값으로 모두 변경한 이미지는 아래와 같습니다. 각자 알아서들 변경 해 보세요. 효과를 줄 위치도 변경해 보면서 말이죠.

이제 마무리 하겠습니다.
Exponent > 1 (볼록): 중심부의 픽셀들이 더 넓은 영역을 차지하려고 밀고 나옵니다. 그래서 코가 왕만두처럼 커집니다.
Exponent < 1 (오목): 가장자리의 픽셀들이 중심으로 몰려듭니다. 얼굴이 홀쭉하게 빨려 들어갑니다.

제가 이번 포스팅 중간에 음.. 정확히는 Step 5에서 볼록 왜곡과 오목 왜곡의 영상 처리를 위해 더 할 얘기가 있긴 한데 라며 이야기 했던 것 기억 하나요? 그러면서 영상 처리 구현 코드에 직교좌표와 극좌표 변환 방식으로 왜곡 효과 처리를 구현했었습니다. 이렇게 구현하는게 코드의 가독성에서는 명확하고 이해하기가 쉽긴 하죠. 하지만, 대용량 이미지에서 해당 방식을 사용하면 느려질 수 있습니다. 내부에 병렬 처리를 하긴 했어도 말이죠. 그래서 말인데, 다음 편에서는 Lens Distortion 효과 처리를 수동 계산하는 방식으로 WPF OpenCV 프로젝트에 구현하는 내용을 진행하도록 할게요.

참고 자료

[Post #25] 물결 효과: [WPF OpenCV Project #25] – Remap 기초 (Wave Effect)
Distortion (Optics): Wikipedia – Distortion – 배럴 및 핀쿠션 왜곡의 광학적 정의
OpenCV Camera Calibration: OpenCV Docs – 실제 렌즈 왜곡 모델과 보정 방법

댓글 남기기