Python과 OpenCV – 22 : 히스토그램(Histogram) 3/4

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_histograms/py_2d_histogram/py_2d_histogram.html#twod-histogram 입니다.

지금까지는 하나의 값을 가진 화소, 즉 Grayscale 이미지에 대한 히스토그램을 살펴봤습니다. 이를 1차원 히스토그램이라고 합니다. 이 글에서는 2차원 히스토그램에 대해 살펴봅니다. 2차원 히스토그램은 2개의 값을 가지는 화소에 대한 히스토그램으로, HSV 중 H와 S의 값을 의미합니다. H는 Hue, S는 Saturation입니다.

2차원 히스토그램을 얻는 방법은 OpenCV와 numpy 방식이 있습니다. 먼저 OpenCV 방식에 대한 코드는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

hist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])

이미지를 읽고 BGR 채널을 HSV로 변환합니다. H에 대한 값의 범위는 0-180이고 S에 대한 값의 범위는 0-256라는 점을 통해 cv2.calcHist의 인자값의 의미를 이해할 수 있습니다. 이제 Numpy 방식에 대한 코드를 살펴보면..

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

img = cv2.imread('./data/home.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)

hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])

2차원 히스토그램을 그래프로 표시하는 방법으로 Matplotlib을 사용해 예제를 살펴보면…

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

img = cv2.imread('./data/home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hist = cv2.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )

plt.imshow(hist,interpolation = 'nearest')
plt.show()

실행 결과는 다음과 같습니다.

X축은 S이고 Y축은 H입니다.

Python과 OpenCV – 21 : 히스토그램(Histogram) 2/4

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_histograms/py_histogram_equalization/py_histogram_equalization.html#histogram-equalization 입니다.

히스토그램을 활용하여 이미지의 품질을 개선하기 위한 방법이 히스토그램 균등화(Equalization)입니다. 이해를 돕기 위해 아래의 코드를 살펴보면..

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

img = cv2.imread('./data/wiki.jpg', 0)

hist,bins = np.histogram(img.flatten(),256,[0,256])

#cdf = hist.cumsum()
#cdf_m = np.ma.masked_equal(cdf,0)
#cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
#cdf = np.ma.filled(cdf_m,0).astype('uint8')
#img = cdf[img]

cv2.imshow('img', img)

plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.show()

결과는 다음과 같습니다.

이미지의 화소값이 0-255에 걸쳐 균등하게 분포하지 못하고 120-210 정도 사이에 밀집되어 있어 있습니다. 이 이미지의 품질을 히스토그램 균동화 방법을 이용해 개선해 보겠습니다. 먼저 numpy를 이용한 코드입니다.

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

img = cv2.imread('./data/wiki.jpg', 0)

hist,bins = np.histogram(img.flatten(),256,[0,256])

cdf = hist.cumsum()
cdf_m = np.ma.masked_equal(cdf,0)
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
cdf = np.ma.filled(cdf_m,0).astype('uint8')
img = cdf[img]

cv2.imshow('img', img)

plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.show()

결과는 다음과 같습니다.

뿌옇게 보였던 이미지가 좀더 명확이 보이고 있습니다. 이유는 위의 이미지의 히스토그램 그래프처럼 화소값이 0-255 사이에 고루게 분포하고 있습니다.

OpenCV의 함수를 이용한 방식에 대한 코드는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/tsukuba_l_clr.png',0)
equ = cv2.equalizeHist(img)
res = np.hstack((img,equ)) #stacking images side-by-side

cv2.imshow('img', res)
cv2.waitKey()
cv2.destroyAllWindows()

결과는 다음과 같습니다.

왼쪽은 원본이고 오른쪽이 히스토그램 균등화를 통한 이미지입니다. 이미지가 명확해지긴했으나 석고상의 밝기값이 너무 큽니다. 이는 이미지가 전체적으로 빛이 고르게 비치지 못하고 석고상에 상대적으로 더 많은 빛이 비춰졌기 때문입니다. 이를 위해서는 히스토그램 균등화를 이미지 전체에 대해 적용하는게 아닌 일정한 영역을 분리하여 해당 영역에 대한 히스토그램 균등화 연산을 수행해 그 결과를 조합하면 됩니다. 이러한 알고리즘을 CLAHE(Contrast Limited Adaptive Histogram Equalization)라고 하는데, 해당 코드는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/tsukuba_l_clr.png',0)

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)

res = np.hstack((img,cl1)) #stacking images side-by-side

cv2.imshow('img', res)
cv2.waitKey()
cv2.destroyAllWindows()

결과는 다음과 같습니다.

CLAHE 알고리즘을 구현한 cv2.createCLAHE 함수에서 tileGridSize=(8,8)이라는 의미는 8×8 격자 크기의 영역을 사용한다는 것입니다.

Python과 OpenCV – 20 : 히스토그램(Histogram) 1/4

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.html#histograms-getting-started 입니다.

아래 그림을 보면..

이미지에 대해 어떤 그래프를 표시하고 있는데, 화소의 강도(Gray 값 또는 하나의 채널에 대한 값)를 갖는 화소의 개수를 각각 X축과 Y축으로 표시하고 있습니다. 대부분의 경우 화소의 강도는 0-255입니다.

히스토그램을 얻는 방법은 Numpy를 이용하는 것과 OpenCV를 이용하는 것이 있습니다. 먼저 OpenCV를 이용한 예제를 보면..

import cv2

img = cv2.imread('./data/home.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])

4번째 cv2.calcHist 함수가 이미지에 대한 히스토그램 정보를 얻는데, 이 함수의 첫번째는 입력 이미지의 배열이며 두번째는 히스토그램을 얻을 채널 인덱스, 세번째는 Mask 이미지, 네번째는 X 축 요소(BIN)의 개수이고 다섯번째는 Y 축 요소값의 범위로 하나의 채널에 대한 화소 강도가 0~255이므로 대부분의 경우 [0,256]이 됩니다. 이 함수의 반환값은 256개의 요소를 갖는 배열입니다.

Numpy를 이용하여 히스토그램을 얻는 코드는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/home.jpg',0)
hist,bins = np.histogram(img.ravel(),256,[0,256])

5번째 코드의 np.histogram 함수가 히스토그램을 얻는 함수인데 반환값은 반환값은 256개의 요소를 갖는 배열인 hist와 X축 요소의 값을 나타내는 배열인 bins입니다. 이 함수 이외에도 hist = np.bincount(img.ravel(),minlength=256) 와 같은 더 빠른 함수가 가능합니다.

속도면에서 Numpy보다 OpenCV 방식이 훨씬 빠르므로 OpenCV를 사용하는 것이 좋습니다.

히스토그램 값을 그래프로 표시하기 위한 예제는 다음과 같습니다.

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('./data/home.jpg',0)
plt.hist(img.ravel(), 256, [0,256]); 
plt.show()

결과는 다음과 같습니다.

BGR 형태와 같이 3개의 채널로 구성된 이미지에 대한 각 채널의 히스토그램도 시각화가 가능한데, 관련된 예제는 다음과 같습니다.

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('./data/home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
    histr = cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])
plt.show()

결과는 다음처럼 3개 채널 각각의 히스토그램 결과가 표시됩니다.

지금까지는 히스토그램 분석을 이미지 전체에 대해서 수행했는데, 필요할 경우 이미지의 원하는 영역에 대한 마스크를 지정해 해당 영역에 대한 히스토그램만을 분석할 수 있습니다. 아래의 코드가 예입니다.

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

img = cv2.imread('./data/home.jpg',0)

# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)

# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])

plt.show()

결과는 다음과 같습니다.

Python과 OpenCV – 19 : 이미지의 등치선(Contours) – 5/5

이 글은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contours_hierarchy/py_contours_hierarchy.html#contours-hierarchy 를 참조로 하였습니다.

먼저 다음과 같은 코드가 있습니다.

import numpy as np
import cv2

img = cv2.imread('./data/opencv_contour_h.png')
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255,0)

_, contours,hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

print(hierarchy)
cv2.imshow('img', img)

for i in range(len(contours)):
    cv2.waitKey()
    img = cv2.drawContours(img, contours, i, (0,0,255), 2)
    cv2.imshow('img', img)

print('END')
cv2.waitKey()
cv2.destroyAllWindows()

8번 코드의 cv2.findContours 함수의 2번째 인자에 따라 그 결과가 달라지는데, 특히 반환값 중 3번째인 hierarchy 값이 크게 달라집니다. hierarchy 값은 추출된 등치선 간의 계층 정보를 나타냅니다. cv2.findContours 함수의 2번째 인자에 따라 어떻게 변경되는지 요약 그림을 언급하는 선에서 정리합니다.

먼저 cv2.RETR_LIST 일 경우, 추출된 등치선의 인덱스 번호에 대한 그림입니다.

그리고 반환된 계층 정보는 다음과 같습니다.

[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [ 3  1 -1 -1]
  [ 4  2 -1 -1]
  [ 5  3 -1 -1]
  [ 6  4 -1 -1]
  [ 7  5 -1 -1]
  [ 8  6 -1 -1]
  [-1  7 -1 -1]]]

총 9개의 등치선이 추출되었으므로 위와 같이 총 9개의 계층 정보가 반환되는데 위의 각 9개 요소의 순서는 앞선 그림에서 표시된 등치선의 인덱스 순서와 동일합니다. 0부터 시작하고요. 그리고 각 요소는 다시 4개로 구성되는데.. [Next, Previous, First_Child, Parent]와 같습니다. 즉, [다음 등치선의 인덱스, 이전 등치선의 인덱스, 첫번째 자식 등치선의 인덱스, 부모 등치선의 인덱스] 입니다. 위의 계층 정보에 대한 내용은 다음 그림으로 표시될 수 있습니다.

다음은 cv2.RETR_EXTERNAL 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [-1  1 -1 -1]]]

위 계층정보의 의미는 다음과 같습니다.

다음은 cv2.RETR_CCOMP 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 3 -1  1 -1]
  [ 2 -1 -1  0]
  [-1  1 -1  0]
  [ 5  0  4 -1]
  [-1 -1 -1  3]
  [ 7  3  6 -1]
  [-1 -1 -1  5]
  [ 8  5 -1 -1]
  [-1  7 -1 -1]]]

위 계층정보의 의미는 다음과 같고요.

마지막으로 cv2.RETR_TREE 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 7 -1  1 -1]
  [-1 -1  2  0]
  [-1 -1  3  1]
  [-1 -1  4  2]
  [-1 -1  5  3]
  [ 6 -1 -1  4]
  [-1  5 -1  4]
  [ 8  0 -1 -1]
  [-1  7 -1 -1]]]

위의 계층정보의 의미를 도식화하면 다음과 같습니다.