3 분 소요

이번 포스팅은 OpenCV 히스토그램, 임계값 처리에 관한 포스팅입니다.
히스토그램은 여러 데이터의 빈도 분포를 나타내는 뜻이고, 임계값은 특정 변수가 특이한 상태, 급격한 변화가 일어나 임계상태에 있을 때의 값을 뜻합니다.

임계값 영상 출력에는 보통 cv2.threshold()와 cv2.adaptiveThreshold() 함수가 사용되는데, 포인트 프로세싱을 하고 안 하고의 차이가 존재합니다.

import cv2
import numpy as np

src = cv2.imread('SamplePicture1.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('src', src)

ret, dst = cv2.threshold(src, 120, 255, cv2.THRESH_BINARY)
print('ret = ', ret)

ret2, dst2 = cv2.threshold(src, 200, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print('ret2 = ', ret2)

cv2.waitKey()
cv2.destroyAllWindows()
ret =  120.0
ret2 =  72.0

threshold함수에서, thresh값은 각각 120,200로 지정해주었고, maxVal값은 255 값으로 입력해주었습니다.
임계값 처리에 추가해주는 값이 존재하는데, cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV, cv2.THRESH_TRUNC 등이 있습니다.
위에서는 cv2.THRESH_BINARY, cv2.THRESH_BINARY + cv2.THRESH_OTSU가 사용되었는데, OTSU알고리즘은 thresh값을 어떤 값으로 지정하든 상관없이, 알아서 최적 임계값을 찾아줍니다.

다음으로 살펴볼 것은 적응형 임계값 입니다.

import cv2
import numpy as np

src = cv2.imread('SamplePicture1.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('src', src)

ret, dst = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imshow('dst', dst)

dst2 = cv2.adaptiveThreshold(src, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 51, 7)
cv2.imshow('dst2', dst2)

dst3 = cv2.adaptiveThreshold(src, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 51, 7)
cv2.imshow('dst3', dst3)

cv2.waitKey()
cv2.destroyAllWindows()

위의 코드와 비슷하지만 성격이 조금 다른 코드입니다.
threshold 함수는 적용 알고리즘에 따라 영상 전체를 계산한다는 느낌이면, adaptiveThreshold 함수는 말 그대로 적응형 임계값, 화소마다 다른 임계값을 적용하여 계산합니다. 여기에도 함수를 지정해줄 수 있으며, 위 코드에는 ADAPTIVE_THRESH_MEAN_C, ADAPTIVE_THRESH_GAUSSIAN_C를 적용했습니다.
maxVal값은 255, blockSize도 동일하게, 51, 뺄셈해주는 인자값인 C값은 7로 주었습니다.
출력 결과를 확인해보면, 적응형 임계값을 적용한 영상이 더 선명한 것을 볼 수 있습니다.


다음은 히스토그램 입니다. 특정 데이터의 빈도수를 막대그래프로 표현한 것을 생각하면 됩니다.

import cv2
import numpy as np

src = np.array([[0,0,0,0],
                [1,1,3,5],
                [6,1,1,3],
                [4,3,1,7]], dtype = np.uint8)

hist1 = cv2.calcHist(images = [src], channels = [0], mask = None,
                     histSize = [4], ranges = [0,8])
print('hist1 = ', hist1)

hist2 = cv2.calcHist(images = [src], channels = [0], mask = None,
                     histSize = [4], ranges = [0,4])
print('hist2 = ', hist2)
hist1 =  [[9.]
 [3.]
 [2.]
 [2.]]
hist2 =  [[4.]
 [5.]
 [0.]
 [3.]]

cv에서는 히스토그램 계산을 위해 cv2.calcHist()함수가 사용됩니다. 실습을 위해 임의로 4*4배열을 만들어 주었고,
channel은 0채널 값에, 사이즈는 동일하게 4, 범위는 0 ~ 7, 0 ~ 3값을 주었습니다.
출력 결과를 보면, hist1은 차례로 [0][0] 은 0,1 카운트, [1][0]은 2,3카운트, … [3][0]은 6,7 카운트 된 결과 입니다. hist2는 [0][0]에 0 카운트, [1][0]에 1 … [3][0]에 3이 카운트 된 결과입니다.
사이즈와 범위를 어떻게 설정하는지에 따라, 카운트 값도 다르게 나옵니다.

import cv2
import numpy as np
import matplotlib.pyplot as plt

src = cv2.imread('sample1.jpg')
histColor = ('b','g','r')

for i in range(3):
    hist = cv2.calcHist(images = [src], channels = [i], mask = None,
                        histSize = [200], ranges = [0, 200])
    plt.plot(hist, color = histColor[i])
    
plt.show()

output_10_0

다음은 컬러 영상을 계산한 것입니다. 사이즈는 200으로 주었고, 범위는 0 ~ 199 까지 입니다.
[0][0]은 1 카운트 [1][0]은 2 카운트, … , [199][0]은 199를 카운트 하게 됩니다.
출력 결과 밤에 찍은 것이어서 그런지 낮은 채도에 몰려있는 것을 확인 할 수 있습니다.


다음은 역투영에 관한 것 입니다.
영역 분할을 위해 색상 이용 시, 히스토그램 계산 후, 역투영을 하면, 컬러 정보가 비슷한 영역만 추출할 수 있습니다.

cv2.calcHist()계산 후, cv2.calcBackProject()함수를 사용해주면 역투영이 가능합니다.

import cv2
import numpy as np

src = np.array([[0,0,0,0],
                [1,1,3,5],
                [6,1,1,3],
                [4,3,1,7]], dtype = np.uint8)

hist = cv2.calcHist(images = [src], channels = [0], mask = None,
                    histSize = [4], ranges = [0,8])
print('hist = ')
print(hist)

backP = cv2.calcBackProject([src], [0], hist, [0,8], scale = 1)
print('backP = ')
print(backP)
hist = 
[[9.]
 [3.]
 [2.]
 [2.]]
backP = 
[[9 9 9 9]
 [9 9 3 2]
 [2 9 9 3]
 [2 3 9 2]]

임의로 4*4 배열을 만들어 주었습니다.
calcHist의 사이즈는 4, 범위는 0 ~ 7 이고, 역투영, 즉 calcBackProject()에서 채널값 0, 범위는 0~7로 동일 합니다.
그리고 src배열에서, [0][0] = 9, [1][0] = 3, [2][0] = 2, [3][0] = 2 라는 히스토그램 결과값이 나왔는데, 여기서 역투영을 실시한 backP값에서는, src(x,y)의 0,1 위치 backP(x,y)에서 hist[0][0] = 9, hist[1][0] = 3 이런식으로 나온 것 입니다.
쉽게 말해, 역투영 과정은 src -> hist -> src 이런식으로 이루어 집니다.

업데이트: