WPF OpenCV 프로젝트 #16: Adaptive Threshold (적응형 이진화) 구현


Adaptive Threshold (적응형 이진화)를 구현해 이미지에 있는 그림자 문제도 어느 정도 해결 가능하도록 WPF OpenCV 프로젝트에 적용해 보도록 하겠습니다.
지난 포스팅(#15)에서 우리는 Otsu 알고리즘을 구현했습니다. “자동으로 임계값을 찾아준다” 라는 정말 똑똑한 녀석이었죠. 하지만 그 똑똑한 Otsu에게도 치명적인 약점이 있었으니… 바로 “조명빨“을 심하게 탄다는 것입니다.

이미지 한쪽에 그림자가 져 있거나, 조명이 불균일하면 Otsu(전역 이진화)는 멍청해집니다. 그림자 부분을 전부 ‘물체’로 인식해버리거든요.

그래서 오늘은 이 문제를 해결해 줄 어느 정도 해결할 , 적응형 이진화(Adaptive Threshold)를 구현해 보겠습니다.

Adaptive Threshold (적응형 이진화)

기존의 Threshold가 “반 전체 평균 점수”로 우등생을 뽑는 방식이라면, Adaptive Threshold는 “각 분단별 1등”을 뽑는 방식과 비슷합니다.

  • 핵심 원리: 이미지 전체에 하나의 임계 값을 쓰는 게 아니라, 픽셀마다 서로 다른 임계 값을 적용합니다.
  • 어떻게? 내 주변(BlockSize) 친구들의 밝기 평균을 구하고, 거기서 조금 뺀 값(Constant C)을 나의 합격 기준(Threshold)으로 삼습니다.
  • 장점: 그림자가 져서 어두운 곳에 있는 픽셀은 기준도 같이 낮아지므로, 글자나 물체를 아주 기가 막히게 찾아냅니다. (스탠드 불빛 아래 문서 인식에 최고죠!)

AdaptiveThresholdParams : Model 클래스

먼저 설정 값을 저장할 클래스를 만듭니다. 이번엔 챙겨야 할 옵션이 좀 많습니다. AdaptiveThresholdParams 클래스를 AlgorithmParameters.cs 파일 아래에 적당한 곳에 작성합니다.

추가한 클래스 코드 내용에 머리를 아프게 할 만큼의 난이도 높은 부분은 없어 보이죠?
코드 중간에 if (value % 2 == 0) value++; 라는 부분이 있습니다. Adaptive Threshold“나(픽셀)를 중심으로 주변을 살피는” 알고리즘입니다. 내가 딱 한가운데 있으려면 전체 크기가 3×3, 5×5 처럼 홀수여야 대칭이 맞습니다. 짝수(예: 4×4)면 중심을 잡을 수가 없어서 OpenCV가 에러를 뱉습니다. (모르면 디버깅하느라 고생하는 포인트입니다!)

DataTemplate : UI(View) 구성

이제 사용자가 값을 조절할 수 있도록 슬라이더와 콤보박스를 배치합니다. MainWindow.xaml의 리소스 섹션에 아래 DataTemplate(템플릿)을 추가합니다. (이젠 익숙하시죠?)

ViewModel & Service: 알고리즘 연결

이제 ViewModel에 알고리즘 이름을 등록하고, OpenCVService에서 실제 처리를 할 차례입니다.

MainViewModel.cs:

OpenCVService.cs (KeyPoint):

어려운 코드는 전혀 없습니다. UI에서 받은 파라미터 4개를 그대로 Cv2.AdaptiveThreshold 함수에 넣어주기만 하면 끝입니다.

MeanC vs GaussianC : 실행 결과 및 비교

자, 이제 실행해서 결과를 비교해 봅시다. 특히 Adaptive Method에서 MeanCGaussianC의 차이를 눈여겨보세요. MeanCGaussianC 에 대해 조금 정리하면 이렇습니다.

MeanC (산술 평균): 주변 픽셀 값을 전부 똑같이 취급해서 평균을 냅니다. 속도는 빠르지만 Noise(노이즈)가 좀 생길 수 있습니다.
GaussianC (가중 평균): 나와 가까운 픽셀일수록 더 중요하게(가중치) 계산합니다. 훨씬 부드럽고 자연스러운 결과를 얻을 수 있습니다. (실제 동작해 보면 GaussianC가 훨씬 품질이 좋다는 걸 느끼실 겁니다.)

1번째 이미지는 원본 이미지를 보여줍니다. 2번째 이미지는 실행 후 Adaptive Threshold 알고리즘을 선택하면 Default 로 값으로 적용한 결과 이미지 이구요, 3번째 이미지는 AdaptiveMethod 만 GaussianC로 적용한 결과 이미지 입니다.

위의 4번째 이미지는 GaussianC Method에서 Block Size를 65로 조정하여 실행한 이미지 입니다. 5 번째 이미지는 GaussianC와 Block Size 11, Contant 7.0을 적용한 이미지이고, 6번째 이미지는 5번째 이미지도 동일하지만, Threshold Type만 BinaryInv 를 적용하여 실행한 이미지 입니다. 아래의 마지막 이미지는 대부분 동일하지만, Block Size를 55, ContantC 36.7 로 적용하여 실행한 결과 이미지 입니다. 빌드 후 이미지를 로드해서 각자 설정 가능한 파라미터들을 조절해 보면서 결과를 분석해 보면 좋을 듯 하네요 ^^;

Block Size와 ConstantC 를 변경한 이미지

이로써 조명이나 그림자 따위는 가볍게 무시해 버리는 강력한 이진화 도구를 손에 넣었습니다. 다음 시간에는 영상 분포를 한눈에 파악할 수 있는 히스토그램(Histogram) 기능을 구현해 보도록 하겠습니다.

참고 자료

Otsu 알고리즘: [WPF OpenCV Project #15] – 전역 이진화의 한계
DataTemplate: [WPF OpenCV Project #5] – UI 자동 생성 원리
OpenCV 공식 문서 (Adaptive Threshold API)
https://docs.opencv.org/4.x/d7/d1b/group__imgproc__misc.html#ga72b913f352e4a1b1b397736707afcde3

댓글 남기기