4 분 소요

이번 포스팅은 저번 포스팅에 이어 CV 임계값, 히스토그램 처리에 관한 내용입니다.

히스토그램 분포를 비교하는 함수에는 compareHist(), EMD() 함수가 있습니다.
compareHist()는 통계적 방법에 기초, EMD() 함수는 두 분포 사이의 최소 일(minimal work)을 측정합니다.

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

nPoints = 100000
pts1 = np.zeros((nPoints, 1), dtype = np.uint16)
pts2 = np.zeros((nPoints, 1), dtype = np.uint16)

cv2.setRNGSeed(int(time.time()))
cv2.randn(pts1, mean = (128), stddev = (10))
cv2.randn(pts2, mean = (110), stddev = (20))

H1 = cv2.calcHist(images = [pts1], channels = [0], mask = None,
                  histSize = [256], ranges = [0, 256])
cv2.normalize(H1, H1, 1, 0, cv2.NORM_L1)
plt.plot(H1, color = 'r', label = 'H1')

H2 = cv2.calcHist(images = [pts2], channels = [0], mask = None,
                  histSize = [256], ranges = [0, 256])
cv2.normalize(H2, H2, 1, 0, cv2.NORM_L1)

d1 = cv2.compareHist(H1, H2, cv2.HISTCMP_CORREL)
d2 = cv2.compareHist(H1, H2, cv2.HISTCMP_CHISQR)
d3 = cv2.compareHist(H1, H2, cv2.HISTCMP_INTERSECT)
d4 = cv2.compareHist(H1, H2, cv2.HISTCMP_BHATTACHARYYA)

print('d1(H1, H2, CORREL) = ', d1)
print('d2(H1, H2, CHISQR) = ', d2)
print('d3(H1, H2, INTERSECT) = ', d3)
print('d4(H1, H2, BHATTACHARYYA) = ', d4)

plt.plot(H2, color = 'b', label = 'H2')
plt.legend(loc = 'best')
plt.show()
d1(H1, H2, CORREL) =  0.573371690650081
d2(H1, H2, CHISQR) =  66.62361330841223
d3(H1, H2, INTERSECT) =  0.4898199874405691
d4(H1, H2, BHATTACHARYYA) =  0.4885381989203133

2023-02-23-OpenCV_C_5_2_p1

cv2.setRNGSeed(int(time.time())) 함수는 초기화 함수, randn()은 난수 생성
그리고 normalize()함수를 사용하여 정규화를 하게 되는데, 각각 H1, H2 히스토그램을 계산해 주었던 것을, cv2.NORM_L1함수를 사용하여, 1이 되게끔 확률로 변경합니다.
HISTCMP_CORREL, HISTCMP_CHISQR, HISTCMP_INTERSECT, HISTCMP_BHATTACHARYYA의 메소드를 사용하여 계산하게 됩니다.


다음은 EMD에 관한 코드입니다.

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

nPoints = 100000
pts1 = np.zeros((nPoints, 1), dtype = np.uint16)
pts2 = np.zeros((nPoints, 1), dtype = np.uint16)

cv2.setRNGSeed(int(time.time()))
cv2.randn(pts1, mean = (128), stddev = (10))
cv2.randn(pts2, mean = (110), stddev = (20))

H1 = cv2.calcHist(images = [pts1], channels = [0], mask = None,
                  histSize = [256], ranges = [0, 256])

H2 = cv2.calcHist(images = [pts2], channels = [0], mask = None,
                  histSize = [256], ranges = [0, 256])

S1 = np.zeros((H1.shape[0], 2), dtype = np.float32)
S2 = np.zeros((H1.shape[0], 2), dtype = np.float32)

for i in range(S1.shape[0]):
    S1[i,0] = H1[i,0]
    S2[i,0] = H2[i,0]
    S1[i,1] = i
    S2[i,1] = i

emd1, lowerBound, flow = cv2.EMD(S1, S2, cv2.DIST_L1)
print('EMD(S1, S2, DIST_L1) = ', emd1)

emd2, lowerBound, flow =  cv2.EMD(S1, S2, cv2.DIST_L2)
print('EMD(S1, S2, DIST_L2) = ', emd2)

emd3, lowerBound, flow = cv2.EMD(S1, S2, cv2.DIST_C)
print('EMD(S1, S2, DIST_C) = ', emd3)

plt.plot(H1, color = 'r', label = 'H1')
plt.plot(H2, color = 'b', label = 'H2')
plt.legend(loc = 'best')
plt.show()
EMD(S1, S2, DIST_L1) =  18.32979965209961
EMD(S1, S2, DIST_L2) =  18.32979965209961
EMD(S1, S2, DIST_C) =  18.32979965209961

2023-02-23-OpenCV_C_5_2_p2

EDM의 DIST_L1, DIST_L2, DIST_C의 결과값이 모두 같은 것을 확인할 수 있습니다.
같은 히스토그램에 EDM계산을 해주면 모두 같은 값이 나옵니다.


다음은 히스토그램 평활화에 관한 것입니다.
평활화는 저대비(low contrast)값을 고대비(high contrast)값으로 얻습니다.
한마디로 좀 더 뚜렷한 영상을 얻고 싶을때 사용하면 좋습니다.
대략적 순서는 BGR -> (HSV, YCrCb) -> BGR순으로 이루어 집니다.

import cv2
import numpy as np

src = np.array([[2,2,4,4],
                [2,2,4,4],
                [4,4,4,4],
                [4,4,4,4]], dtype = np.uint8)

dst = cv2.equalizeHist(src)
print('dst = ')
print(dst)
dst = 
[[  0   0 255 255]
 [  0   0 255 255]
 [255 255 255 255]
 [255 255 255 255]]

equalizeHist()함수를 사용하면, 평활화가 가능합니다.
저대비를 고대비로 변환한 값입니다.

import numpy as np
import cv2

src = cv2.imread('sample1.png')


hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

v2 = cv2.equalizeHist(v)
hsv2 = cv2.merge([h, s, v2])
dst = cv2.cvtColor(hsv2, cv2.COLOR_HSV2BGR)
cv2.imshow('dst', dst)

yCrCv = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)
y, Cr, Cv = cv2.split(yCrCv)

y2 = cv2.equalizeHist(y)
yCrCv2 = cv2.merge([y2, Cr, Cv])
dst2 = cv2.cvtColor(yCrCv2, cv2.COLOR_YCrCb2BGR)

cv2.imshow('dst2', dst2)
cv2.waitKey()
cv2.destroyAllWindows()

각각 BGR -> HSV, BGR -> YCrCb 평활화 입니다.
split으로 3채널 분리 하고, merge로 다시 합친 후 BGR 형태로 평활화를 진행하는 코드 입니다.


다음은 CLAHE 평활화 입니다.
이는 영상을 블록으로 나누어 평활화를 진행하게 됩니다.
앞의 단순 평활화 보다 밝기 조절에 매우 탁월합니다.

import cv2
import numpy as np

src = np.array([[2,2,2,2,0,0,0,0],
                [2,1,1,2,0,0,0,0],
                [2,1,1,2,0,0,0,0],
                [2,2,2,2,0,0,0,0],
                [0,0,0,0,255,255,255,255],
                [0,0,0,0,255,1,1,255],
                [0,0,0,0,255,1,1,255],
                [0,0,0,0,255,255,255,255]], dtype = np.uint8)

clahe = cv2.createCLAHE(clipLimit = 40, tileGridSize = (1,1))
dst = clahe.apply(src)
print('dst = ')
print(dst)

clahe2 = cv2.createCLAHE(clipLimit = 40, tileGridSize = (2,2))
dst2 = clahe2.apply(src)
print('dst2 = ')
print(dst2)
dst = 
[[116 116 116 116  44  44  44  44]
 [116  76  76 116  44  44  44  44]
 [116  76  76 116  44  44  44  44]
 [116 116 116 116  44  44  44  44]
 [ 44  44  44  44 255 255 255 255]
 [ 44  44  44  44 255  76  76 255]
 [ 44  44  44  44 255  76  76 255]
 [ 44  44  44  44 255 255 255 255]]
dst2 = 
[[ 80  80  80  72  32  40  48  48]
 [ 80  48  48  72  32  40  48  48]
 [ 80  48  48  72  32  40  48  48]
 [ 72  72  72  66  32  36  40  40]
 [ 32  32  32  32 255 255 255 255]
 [ 40  40  40  36 255  48  48 255]
 [ 48  48  48  40 255  48  48 255]
 [ 48  48  48  40 255 255 255 255]]

createCLAHE로 CLAHE 평활화를 진행합니다.
cliplimit으로 대비제한 값을, tileGridSize로 타일 크기를 지정합니다.
tileArea는 임의로 8*8로 지정을 해주었고, histSize는 256으로 지정했습니다.
대비제한 값은 각각 40으로, 타일 크기는 각각 1 * 1 그리고 2 * 2크기로 지정했습니다.

import cv2
import numpy as np

src = cv2.imread('sample1.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('src', src)

clahe2 = cv2.createCLAHE(clipLimit = 40, tileGridSize = (1, 1))
dst2 = clahe2.apply(src)
cv2.imshow('dst2', dst2)

clahe3 = cv2.createCLAHE(clipLimit = 40, tileGridSize = (8, 8))
dst3 = clahe3.apply(src)
cv2.imshow('dst3', dst3)

cv2.waitKey()
cv2.destroyAllWindows()

다음은 영상에 대해 CLAHE 평활화한 과정입니다.
확실히 타일 크기를 1 * 1 즉 1개의 히스토그램만 사용하니, 원본 영상과 비슷한 결과를 출력합니다.

업데이트: