본문 바로가기

Python OpenCV

이진화(Binarization)

 오늘은 이진화, 자동 이진화(Otsu), 지역 이진화 대해서 글로 남기려 합니다.

🙋‍♂️ 이진화(Binarization) 란?

 영상의 각각의 픽셀 값을 0 또는 255로 만드는 연산으로써 배경과 객체를 구분하기 위한 경우에 많이 사용합니다.

이진화는 보통 그레이 스케일의 입력 이미지를 받아 사용하며 대표적으로 T : 임계값, 문턱 치, Threshold라고 불리는 값으로 조절하며 수식은 아래와 같습니다.

 

g(x, y) = { 0          if f(x, y) <= T

              { 255      if f(x, y)   > T

 

  이미지 비교

왼쪽부터 원본, T(Threshold : 210) 설정한 결과, T(100) 설정한 결과

  구현 소스

import sys
import cv2
 
src = cv2.imread("cells.png", cv2.IMREAD_GRAYSCALE)
 
if src is None:
    print("Image load failed !!")
    sys.exit()
 
_, rst = cv2.threshold(src, 210255, cv2.THRESH_BINARY)
_, rst2 = cv2.threshold(src, 100255, cv2.THRESH_BINARY)
 
# return 의 첫 번째 인자는 설정되어있는 Threashold 값이 반환 되기때문에 _로 처리
# cv2.THRESH_BINARY와 더불어 cv2.THRESH_BINARY_INV, 등 여러가지 Type을 설정 할 수 있다.
 
cv2.imshow('src', src)
cv2.imshow('rst', rst)
cv2.imshow('rst2',rst2)
 
cv2.waitKey()
cv2.destroyAllWindows()
cs

 

 

  cv2.threshold 함수에서 type별 설정

cv2.THRESH_BINARY : T 보다 이상인 픽셀 값에 대해서 255로 바꾸고 그렇지 않다면 0으로 이진화

cv2.THRESH_BINARY_INV : T 보다 이상인 픽셀 값에 대해서 0으로 바꾸고 그렇지 않다면 255로 이진화

cv2.THRESH_TRUNC : T 보다 이상인 픽셀 값에 대해서 T 값으로 바꾸고 그렇지 않다면 기본 픽셀 값으로 이진화

cv2.THRESH_TOZERO : T 보다 이상인 픽셀 값에 대해서 기본 픽셀 값으로 바꾸고 그렇지 않다면 0으로 이진화

cv2.THRESH_TOZERO_INV : T 보다 이상인 픽셀 값에 대해서 0으로 바꾸고 그렇지 않다면 기본 픽셀 값으로 이진화

cv2.THRESH_OTSU : Otsu Algorithm 방식으로 임계값을 자동 결정

cv2.THRESH_TRIANGLE : Triangle Algorithm 방식으로 임계값을 자동 결정

 

✏️ 위 방식 중 가장 일반적으로 사용되는 방법은 BINARY, BINARY_INV, OTSU 가 있으며

저는 밑에 예시로 하나 더 기록해 두려고 합니다.

 

  예시

왼쪽부터 원본, 이진화를 시키고 싶은 영역

 

Cell 이미지의 Histogram

예시로써 검은색 세포 부분들에 대해서 이진화를 시키고 싶다면

Histogram으로 픽셀 분포도를 확인 후 적절한 Threshold Value를 찾아서 이진화를 진행하면 됩니다.

저는 Histogram이 Bimodal 인 것을 확인 후 최대와 최솟값에서 나누기 2를 진행 한 값으로 이진화를 시켜 보았습니다.

 

이진화가 완성된 Cell 이미지

완벽하게 된 것은 아니지만 어느 정도 수준에서는 됐다고 판단됩니다.

 

  구현 소스

import sys
import numpy as np
import cv2
 
src = cv2.imread('cells.png', cv2.IMREAD_GRAYSCALE)
 
if src is None:
    print('Image load failed!')
    sys.exit()
 
def getGrayHistImage(hist):
    imgHist = np.full((100,256), 255, dtype=np.uint8)
 
    histMax = np.max(hist)
    for x in range(256):
        pt1 = (x, 100)
        pt2 = (x, 100 - int(hist[x,0* 100 / histMax))
        cv2.line(imgHist, pt1, pt2, 0)
 
    return imgHist
 
hist = cv2.calcHist([src], [0], None, [256], [0256])
histImg = getGrayHistImage(hist)
 
min = np.min(src)
max = np.max(src)
result = (max-min) / 2
 
print("min : ", min, ", max : ", max, ", (max - min) / 2  : ", result)
 
_, rst = cv2.threshold(src, result, 255, cv2.THRESH_BINARY)
 
cv2.imshow('histImg', histImg)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
 
cs
 
 

🙋‍♂️ 자동 이진화(Otsu Algorithm) 란?

 일반적으로 이진화 진행 시 사용자의 경험에 의한 T(Threshold) 값을 정하기 마련이지만

사용자가 T값을 정하지 않고 임의의 임계값 T에 의해 나눠지는 두 픽셀 분포 그룹의 분산이 최소가 되는 T를 선택하는

즉, 일종의 최적화 알고리즘(Optimization Algorithm)입니다.

 

 1979년에 발표된 논문이지만 최적화가 잘되어있기 때문에 이진화 진행 시 자주 사용하는 알고리즘 이기도 합니다.

 

  예시

쌀알 이미지
쌀알 이미지의 Histogram

위에서 설명한 일반적인 이진화 방법으로 진행하려고 한다면 어떻게 될까요?

일반적인 이진화 방법으로 이진화 진행시 결과 모습

 위 사진과 같이 이상한 모습으로 이진화가 되었습니다 왜 그럴까요?

결론부터 말하자면 Bimodal Histogram이 아니기 때문에 아까의 방식으로는 원활하게 진행되지 않은 것입니다.

왼쪽부터 Cell 이미자와 Rice 이미지의 Histogram 차이점

이번에는 Otsu Algorithm을 사용하여 이진화를 진행해 보겠습니다.

Otsu Algorithm을 사용한 결과 이미지

이미지 밑 부분의 쌀알 부분은 이진화가 잘 되지 않았지만 대부분 잘된 것을 확인할 수 있습니다.

위와 같이 Bimodal이 아닌 경우에는 Otsu 방법을 사용하여 이진화를 진행하는 것도 좋은 방법이라고 생각이 듭니다.

 

  구현 소스

 

import sys
import numpy as np
import cv2
 
src = cv2.imread('rice.png', cv2.IMREAD_GRAYSCALE)
 
if src is None:
    print('Image load failed!')
    sys.exit()
 
def getGrayHistImage(hist):
    imgHist = np.full((100,256), 255, dtype=np.uint8)
 
    histMax = np.max(hist)
    for x in range(256):
        pt1 = (x, 100)
        pt2 = (x, 100 - int(hist[x,0* 100 / histMax))
        cv2.line(imgHist, pt1, pt2, 0)
 
    return imgHist
 
hist = cv2.calcHist([src], [0], None, [256], [0256])
histImg = getGrayHistImage(hist)
 
Tvalue, rst = cv2.threshold(src, 0255, cv2.THRESH_OTSU)
 
print("Threshold Vlaue : ", Tvalue)
 
cv2.imshow('rst', rst)
cv2.imshow('histImg', histImg)
 
cv2.waitKey()
cv2.destroyAllWindows()
cs

 

🙋‍♂️ 지역 이진화 란?

균일하게 이진화가 되지 않은 이미지에 대해서 구역을 나누어서 각각 이진화를 진행하는 것을 뜻합니다.

쉬운 예시로는 위에서 설명한 Otsu 방법으로 이진화를 진행하였을 때 이미지의 윗부분은 이진화가 잘되었지만

이미지의 아랫부분은 잘되지 않았기 때문에 지역 이진화를 활용하여 구역을 나누어서 따로 이진화를 진행시킨 후

합치면 균일하게 이진화가 잘된 이미지를 만들 수 있습니다.

 

이때 고려해야 할 부분은

1. 얼마만큼 구역을 나눌 것인가?

2. 구역마다 겹치는 곳을 허용할 것인가 아닌가?

3. 구역을 나누었는데 배경 혹은 객체만 존재한다면 어떻게 처리할 것인가?

 

  결과 이미지

지역 이진화를 사용하여 이진화를 시킨 결과 이미지

  비교 이미지

왼쪽부터 전역 이진화와 지역 이진화

  구현 소스

import sys
import numpy as np
import cv2
 
src = cv2.imread('rice.png', cv2.IMREAD_GRAYSCALE)
 
if src is None:
    print('Image load failed!')
    sys.exit()
 
rst = np.zeros(src.shape, np.uint8)
# 검정색으로 원본이미지의 크기만큼 만든다.
 
bw = src.shape[1// 4
bh = src.shape[0// 4
 
for y in range(4):
    for x in range(4):
        _src = src[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        _rst = rst[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        cv2.threshold(_src, 0255, cv2.THRESH_BINARY | cv2.THRESH_OTSU, _rst)
        
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
cs

 

또한 위 방법 말고도 OpenCV에서 제공하는 지역 이진화 방법도 존재합니다.

cv2.adaptiveThreshold()

하지만 이 함수는 동작이 느리고 결과 이미지가 원활하지 않아

나중에 사용할 일이 있다면 검토 후 적용해봐야 할 것 같다는 생각이 듭니다.

 

본 학습 내용은 "OpenCV를 활용한 컴퓨터비전과 딥러닝" 을 참고하였음을 알려드립니다.

'Python OpenCV' 카테고리의 다른 글

잡음 제거(Denoised)  (0) 2022.06.29
필터링(Filtering)  (0) 2022.06.25
모폴로지(Morphology)  (0) 2022.06.12
화소 처리(Point Processing)  (2) 2022.06.08
Read, Show, Write to Image  (0) 2022.05.06