히스토그램 평활화
히스토그램 평활화란 히스토그램의 조작을 통한 영상 품질 개선 방식 중의 하나로 가장 대표적인 연산이다. 히스토그램의 분포를 평평하게 만들어 명암의 범위가 늘어나 영상이 이전보다 선명해 진다.
식은 다음과 같다.
\[c(l_{in}) = \sum_{l=0}^{l_{in}} \hat{h}(l) \\ l_{out} = T(l_{in}) = round(c(l_{in}) \times (L-1))\]여기서 \(c(.)\)는 누적 히스토그램을 나타내고 \(round(.)\)는 반올림 연산을 나타낸다. \(\hat{h}\)는 정규화 히스토그램을 나타낸다. 어떤 명암 \(l\)을 기준으로 생각했을 때, \(l\)보다 작은 명암을 갖는 화소의 비율은 \(1/L\)이어야 한다. 즉, 누적 히스토그램은 비율이 \(1/L\)인 점을 \(l\)로 매핑해준다.
코드구현
먼저 사진을 확인해보자. 이번 구현에서는 다음과 같이 어두운 사진을 히스토그램 평활화로 조금 더 밝게 만들것이다.
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
img = cv2.imread('./data/city.jpg')
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.show()
가장 먼저 히스토그램을 구해보자. 아래의 plot을 한 결과는 정규화된 히스토그램이다. 예제에서는 이 값을 저장을 하진 않았다. 혼동하지 말자.
b_hist = np.zeros((256,1))
g_hist = np.zeros((256,1))
r_hist = np.zeros((256,1))
img_flat = np.reshape(img.copy(),(-1,3))
leng=len(img_flat)
for i in range(leng):
b_hist[img_flat[i,0]]+=1
g_hist[img_flat[i,1]]+=1
r_hist[img_flat[i,2]]+=1
fig = plt.figure()
plt.subplot(231)
plt.bar(np.arange(256),b_hist.squeeze()/leng)
plt.xlim([0,256])
plt.subplot(232)
plt.bar(np.arange(256),g_hist.squeeze()/leng)
plt.xlim([0,256])
plt.subplot(233)
plt.bar(np.arange(256),r_hist.squeeze()/leng)
plt.xlim([0,256])
fig.tight_layout()
plt.show()
누적 히스토그램은 다음과 같이 순서대로 이전의 값을 더하면 된다. 그리고 이 값을 정규화해 \(L-1\)을 곱해준 후 반올림 한다.
for i in range(1,256):
b_hist[i]+=b_hist[i-1]
g_hist[i]+=g_hist[i-1]
r_hist[i]+=r_hist[i-1]
# 정규화한 값은 저장이 안되어 있으므로 다시 leng으로 나눠 정규화
b_hist= np.uint8(b_hist*255/leng+0.5)
g_hist= np.uint8(g_hist*255/leng+0.5)
r_hist= np.uint8(r_hist*255/leng+0.5)
이제 이미지의 값을 평활화된 히스토그램으로 매핑을 해주면 완성이다.
new = np.zeros_like(img)
for y in range(len(new)):
for x in range(len(new[0])):
new[y,x,0] = b_hist[img[y,x,0]]
new[y,x,1] = g_hist[img[y,x,1]]
new[y,x,2] = r_hist[img[y,x,2]]
plt.subplot(121)
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.subplot(122)
plt.imshow(cv2.cvtColor(new,cv2.COLOR_BGR2RGB))
plt.show()
평활화된 이미지의 histogram을 보면 다음과 같다.
b_hist_n = np.zeros((256,1),np.uint8)
g_hist_n = np.zeros((256,1),np.uint8)
r_hist_n = np.zeros((256,1),np.uint8)
img_flat_n = np.reshape(new.copy(),(-1,3))
for i in range(leng):
b_hist_n[img_flat_n[i,0]]+=1
g_hist_n[img_flat_n[i,1]]+=1
r_hist_n[img_flat_n[i,2]]+=1
fig=plt.figure()
plt.subplot(131)
plt.bar(np.arange(256),b_hist_n.squeeze()/leng)
plt.xlim([0,256])
plt.subplot(132)
plt.bar(np.arange(256),g_hist_n.squeeze()/leng)
plt.xlim([0,256])
plt.subplot(133)
plt.bar(np.arange(256),r_hist_n.squeeze()/leng)
plt.xlim([0,256])
fig.tight_layout()
plt.show()
확실히 이전보다 분포가 더 고르게 되어있는 것을 확인할 수 있다.
이 긴과정이 OpenCV의 cv2.equalizeHist 함수 하나면 바로 끝낼 수 있다. OpenCV를 이용한 예제는 다음과 같이 하면 된다.
cv_new_b = np.expand_dims(cv2.equalizeHist(img[...,0]),-1)
cv_new_g = np.expand_dims(cv2.equalizeHist(img[...,1]),-1)
cv_new_r = np.expand_dims(cv2.equalizeHist(img[...,2]),-1)
cv_new = np.concatenate([cv_new_b,cv_new_g,cv_new_r],-1)
plt.imshow(cv2.cvtColor(cv_new,cv2.COLOR_BGR2RGB))
Comments