[OpenCV] 04-10. Histograms in OpenCV (1)
🐍Python/OpenCV

[OpenCV] 04-10. Histograms in OpenCV (1)

728x90
반응형

< Find, Plot, Analyze !!! >

이번 장에서는

  • OpenCV와 Numpy 함수를 같이 사용하여 히스토그램을 찾는 것
  • OpenCV와 Matplotlib 함수를 사용하여 히스토그램을 그리는 것
  • cv2.calcHist(), np.histogram()등의 함수에 대해서 볼 것 이다.

Theory

그래서 히스토그램이란 무엇일까? 히스토그램을 이미지 분포의 강도에 관한 총량을 알려주는 그래프나 도표정도로 생각할 수 있다. 이는 X축에 픽셀값(항상 0부터 255는 아니다)을 나타내고 Y축에 이에 대응하는 이미지의 픽셀의 수를 나타낸다.

이는 이미지를 이해하는 다른 방법일 뿐이다. 이미지의 히스토그램을 보면 이미지의 대조, 밝기, 강도 분포에 대한 직관을 얻을 수 있다. 최근의 이미지 처리 도구들은, 히스토그램에 특성을 제공한다. 아래의 이미지는 Cambridge in Color website에서 가져온 것이며, 사이트를 방문하여 더 찾아보길 바란다.


이미지와 이에 대한 히스토그램을 볼 수 있다.(이 히스토그램은 칼라가 아닌 흑백스케일 이미지에 대해 그려진 것이다) 히스토그램의 왼쪽 지역은 이미지의 어두운 픽셀의 양을 보여주고 오른쪽은 밝은 픽셀들의 양을 보여준다. 히스토그램에서, 어두운 지역이 밝은 지역보다 더 많은 것을 볼 수 있다. 그리고 중간톤의 양은 매우 적다.

Find Histogram

이제 히스토그램이 무엇인지 알았으니, 어떻게 찾는지 알아보자. OpenCV와 Numpy 모두 이를 위한 내장 기능을 제공한다. 이 함수들을 사용하기 전에, 히스토그램과 연관된 용어들을 이해할 필요가 있다.

< BINS >

위 히스토그램은 모든 픽셀값에 대한 픽셀들의 수를 보여준다. (0~255) 즉, 256개의 값이 필요하다. 그러나 모든 픽셀 값의 픽셀 수를 별도로 찾을 필요가 없고 픽셀 값의 간격에서 픽셀 수를 찾을 필요가 있다면 어떻게 할 것인가? 예를 들어서, 0에서 15, 16에서 31, 240에서 255사이에 놓인 픽셀의 수를 찾아야 할 필요가 있다고 하자. 히스토그램을 대표하는 16개의 값만 필요한 것이다. OpenCV Tutorials on histograms에서 자세하게 볼 수 있다.

그래서 간단하게 16개의 하위파트로 나누고 각 하위 파트의 값은 그 안에 있는 모든 픽셀 카운트의 합이다. 이 각 하위 파트는 “BIN”이라고 불린다. 처음 경우, 256개인 bins의 수가 16개에 불과하지만, 두 번째의 경우, BINS는 OpenCV문서에서 histSize라는 용어로 표시된다.

< DIMS >

이는 우리가 데이터를 수집하는 파라미터의 수이다. 이 경우, 우리는 오직 한 가지. 강도 값에 대한 데이터를 수집한다. 그래서 1이다.

< RANGE >

측정하고자하는 강도 값의 범위이다. 보통, [0,256]이다.

1. Histogram Calculation in OpenCV

이제 히스토그램을 찾기 위해 cv2.calcHist()를 사용할 것 이다. 이 함수와 파라미터들에 익숙해져보자.

cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])

  1. images : 이미지가 uint8인지 float32인지 말해준다. [] 속에 넣어서 주어져야한다. (ex. [img])
  2. channels : 이 또한 [ ] 에 들어가야한다. 우리가 계산할 히스토그램의 채널의 인덱스이다. 예를 들어, 만약 입력이 흑백 이미지라면 값은 [0]이 된다. 컬러 이미지라면, 각각 채널에 맞게 [0],[1],[2]를 넘겨주어 히스토그램을 계산하면 된다.
  3. mask : 마스크 이미지이다. 전체 이미지에 대한 히스토그램을 찾고자 할 때, None을 넘긴다. 하지만 만약 이미지의 부분 지역의 히스토그램을 찾고자 할 때, 마스크 이미지를 생성하여야 한다. (뒤에서 더 봐볼 것!)
  4. histSize : 이는 BIN 카운트를 나타낸다. 이 또한 [ ] 속에 있어야한다. 전체 스케일에 대해 [256]을 넘긴다.
  5. ranges : 이것이 우리의 RANGE이다. 보통 [0,256]이다.

이제 샘플 이미지를 가지고 시작해보자. 이미지를 흑백스케일로 불러오고 전체 히스토그램을 찾아보자

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

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

hist 는 256x1 배열이고, 각각 값은 해당 픽셀 값을 가진 이미지의 픽셀의 수에 해당한다.

2. Histogram Calculation in Numpy

Numpy는 np.histogram() 함수를 제공한다. 그래서 calcHist() 함수 대신에 아래처럼 시도해 볼 수 있다.

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

hist는 우리가 이전에 계산했던 것과 같다. 하지만 bins는 257개의 성분을 가질 것이다. 왜냐하면 Numpy는 bins를 0-0.99, 1-1.99,2-2.99로 계산한다. 이를 표현하기 위해, bin 끝에 256개를 추가하기도 한다. 하지만 우리는 256말고 255이상이면 충분하다.


Numpy는 다른 함수인 np.bincount()는 np.histogram()보다 10배 빠르다. 그래서 1차원 히스토그램에 대해, 시도해보는 것도 좋다. np.bincount에서 minlength=256를 잊지 말자. 예를 들어, hist = np.bincount(img.ravel(), minlength=256)과 같다.


OpenCV 함수는 np.histogram()보다 40배 가량 빠르다.

Plotting Histograms

두 가지 방법이 있다

  1. Matplotlib 플로팅 함수 사용하기
  2. OpenCV 함수로 그리기

1. Using Matplotlib

Matplotlib에는 히스토그램 플롯기능이 있다 : matplotlib.pyplot.hist()

이는 바로 히스토그램을 찾고 그려준다. 히스토그램을 찾기 위해 calcHist()나 np.histogram() 함수를 사용할 필요가 없다. 아래 코드를 보자!

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

plt.figure(figsize=(12,6))
img = cv2.imread('./images/face2.jpg',0)
plt.subplot(121)
plt.imshow(img,cmap='gray')
plt.subplot(222)
plt.hist(img.ravel(),256,[0,256])
plt.show()


또는 정규 플롯을 그릴 수도 있는데, BGR 플롯에 적합할 것이다. 그러기 위해서는 먼저 히스토그램 데이터를 찾아야한다.

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

img = cv2.imread('./images/face2.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.ylim([0,12500])
plt.show()


2. Using OpenCV

여기서 히스토그램의 값을 bin 값과 함께 x,y 좌표와 같이 조정하여 cv2.line() 또는 cv2.polyline() 함수를 사용하여 위에서와 같은 이미지를 생성할 수 있도록 한다. 이것은 OpenCV-Python2 공식 샘플에서 볼 수 있다. 코드확인

Application of Mask

전체 이미지의 히스토그램을 찾이 위해 cv2.calcHist()를 사용했었다. 이미지의 특정 지역의 히스토그램을 찾고싶다면 어떻게 해야할까? 원하는 지역을 흰색으로 나머지를 검정색으로한 이미지를 생성한다. 그리고 마스크를 보내주면 된다.

img = cv2.imread('./images/face2.jpg',0)

# 마스크 만들기
mask = np.zeros(img.shape[:2],np.uint8)
mask[200:300,90:300] = 255
masked_img = cv2.bitwise_and(img,img, mask=mask)

# 마스크 있고 없고 히스토그램을 계산한다
# 마스크의 3번째 인자를 확인하자
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

plt.figure(figsize=(12,8))
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()

아래의 결과를 보자. 히스토그램 플롯에서, 파란 선은 전체 이미지의 히스토그램을 보여주고, 주황색 선은 마스크 지역의 히스토그램이다.


728x90
반응형