✍ 오늘은 이진화, 자동 이진화(Otsu), 지역 이진화에 대해서 글로 남기려 합니다.
🙋♂️ 이진화(Binarization) 란?
✍ 영상의 각각의 픽셀 값을 0 또는 255로 만드는 연산으로써 배경과 객체를 구분하기 위한 경우에 많이 사용합니다.
✍ 이진화는 보통 그레이 스케일의 입력 이미지를 받아 사용하며 대표적으로 T : 임계값, 문턱 치, Threshold라고 불리는 값으로 조절하며 수식은 아래와 같습니다.
g(x, y) = { 0 if f(x, y) <= T
{ 255 if f(x, y) > T
✅ 이미지 비교

✅ 구현 소스
|
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, 210, 255, cv2.THRESH_BINARY)
_, rst2 = cv2.threshold(src, 100, 255, 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 가 있으며
저는 밑에 예시로 하나 더 기록해 두려고 합니다.
✅ 예시


✍ 예시로써 검은색 세포 부분들에 대해서 이진화를 시키고 싶다면
Histogram으로 픽셀 분포도를 확인 후 적절한 Threshold Value를 찾아서 이진화를 진행하면 됩니다.
✍ 저는 Histogram이 Bimodal 인 것을 확인 후 최대와 최솟값에서 나누기 2를 진행 한 값으로 이진화를 시켜 보았습니다.

✍ 완벽하게 된 것은 아니지만 어느 정도 수준에서는 됐다고 판단됩니다.
✅ 구현 소스
|
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], [0, 256])
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년에 발표된 논문이지만 최적화가 잘되어있기 때문에 이진화 진행 시 자주 사용하는 알고리즘 이기도 합니다.
✅ 예시


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

✍ 위 사진과 같이 이상한 모습으로 이진화가 되었습니다 왜 그럴까요?
결론부터 말하자면 Bimodal Histogram이 아니기 때문에 아까의 방식으로는 원활하게 진행되지 않은 것입니다.

✍ 이번에는 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], [0, 256])
histImg = getGrayHistImage(hist)
Tvalue, rst = cv2.threshold(src, 0, 255, 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, 0, 255, 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 |