본문 바로가기
OpenCV/OpenCV-Chapter

CH07 OpenCV-Python 이진화(Binarization)

by kiimy 2021. 12. 24.
728x90
728x90

영상의 이진화(Binarization)란?

• 영상의 픽셀 값을 0 또는 255(1)로 만드는 연산

  ▪ 배경(background) vs. 객체(object)

  ▪ 관심 영역 vs. 비관심 영역

 

* GrayScale 영상의 이진화

T 보다 작으면 0, T 보다 크면 255

# 검은 객체 찾기
'''
OpenCV에선 '흰색이 객체다' 라고 정의
'''
src = cv2.imread('ch07\\images\\cells.png', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

def onChange(pos):
    # cv2.THRESH_BINARY_INV = 검은 객체 흰색으로
    ret, dst = cv2.threshold(src, pos, 255, cv2.THRESH_BINARY)
    print(ret) # Threshold 값
    cv2.imshow('thresh', dst)

cv2.imshow('src', src)
cv2.namedWindow('thresh')
cv2.createTrackbar('threshold', 'thresh', 0, 255, onChange)
cv2.setTrackbarPos('threshold', 'thresh', 50)
BINARY BINARY_INV
T보다 크면 255 T보다 크면 0
T보다 작으면 0 T보다 작으면 255
일반적으로 영상을 이진화할 때 이진화 값(Threshold)은 어떻게 정할까?
- 영상이 조명이나 환경에 따라서 픽셀값이 아주 미세하게 다르기 때문에
  영상에서 사용할 수 있는 임계값(Threshold)이 조금씩 다르다.

자동 이진화 - Otsu 방법

▪ 임계값 자동 결정 방법

  • 영상의 히스토그램이 bimodal이고, 전경&배경 픽셀 분포가 비슷하다면?

픽셀값의 평균

 • 히스토그램이 bimodal이지만, 전경&배경 픽셀 분포가 크게 다르다면?

mode가 두개 있어서 Bimodal Histogram

Otsu 이진화 방법

• 입력 영상이 배경(background)과 객체(object) 두 개로 구성되어 있다고 가정 → Bimodal histogram

• 임의의 임계값 T 에 의해 나눠지는 두 픽셀 분포 그룹의 분산이 최소가 되는 T 를 선택

• 일종의 최적화 알고리즘(optimization algorithm)

Otsu's method - Wikipedia

1. 평균 구하고

2. 분산(시그마²)

평균을 두번 구해야해서 느리다(= within-class variance 최소화)
그래서 Between-class variance 최대화 사용== 점화식으로 구현되있어서 빠르다.
# Otsu = Threshold 값을 자동으로 잡아줌
src = cv2.imread('ch07\\images\\rice.png', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

# cv2.THRESH_BINARY는 생략가능 (default)
th, dst = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print("otsu's threshold:", th)  # 131(실수형태로 나옴)

잘 안나오는 부분이 있음 = 지역이진화로 처리 가능
원래 이미지에서 약간 어두운 부분은 잘 안나옴(= 이미지 불균일)
Threshold를 낮추면 잘 보이는데 원래 보이는 것들이 잘안보임


지역 이진화

- 어둡다가 밝아지는 이미지? ==> 지역 이진화

- 전체 영상을 n등분하여 각각의 구역마다 따로 이진화하고 이어 붙임(= Window)

 

* 균일하지 않은 조명의 영향을 해결하려면?

• 픽셀 주변에 작은 윈도우를 설정하여 지역 이진화 수행

▪ 윈도우의 크기는? - block

▪ 윈도우 형태는? Uniform? Gaussian?

▪ 윈도우를 겹칠 것인가? Overlap? Non-overlap?

▪ 윈도우 안에 배경 또는 객체만 존재한다면?

* 지역 이진화 직접구현

# cv2.THRESH_BINARY는 생략가능 (default)
th, dst1 = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print("otsu's threshold:", th)  # 131(실수형태로 나옴)

# Local 이진화
dst2 = np.zeros(src.shape, np.uint8)

# block 설정(128, 128)
bw = src.shape[1] // 4
bh = src.shape[0] // 4
print(bw, bh)

# Local 이진화 직접구현
for y in range(4):
    for x in range(4):
        # crop
        src_ = src[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        dst_ = dst2[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        '''
        cv2.threshold(src, thresh, maxval, type, dst=None) -> retval, dst

        출력영상을 입력영상으로 받는다.
        why?  _, dst_ = cv2.threshold 로 진행하면 이전 dst_의 정보를 잃어버려
        dst: 출력 영상. src와 동일 크기, 동일 타입, 같은 채널 수
        dst_ = src_의 크기가 같아야함
        '''
        cv2.threshold(src_, 0,255,cv2.THRESH_OTSU, dst_)

Threshold / Local Threshold

 

* 적응형 이진화(= AdaptiveThreshold)

# 적응형 이진화(= 지역이진화)
'''
조명의 변화나 반사가 심한 경우 이미지 내의 밝기 분포가 달라 국소적으로 
임곗값을 적용해야 하는 경우
'''
src = cv2.imread('ch07\\images\\sudoku.jpg', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

# cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, 
#                   thresholdType, blockSize, C, dst=None) -> dst
'''
C: 블록 내 평균값 또는 블록 내 가중 평균값에서 뺄 값. 
    (x, y) 픽셀의 임계값으로 
'''
def onChange(pos):
    bsize = pos
    if bsize % 2 == 0:
        bsize = bsize -1
    if bsize < 3:
        bsize = 3

    dst = cv2.adaptiveThreshold(src, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, \
                        cv2.THRESH_BINARY, bsize, 5)
    cv2.imshow('dst', dst)

Local 이진화를 직접 구현한 것 보다 느림


blockSize: 블록 크기. 3 이상의 홀수(클 수록 좋게 보임)
작을 경우 window에 배경 or 객체만 있을 수 있기 때문에 약간의 noise가 발생

 

728x90

댓글