目標
學會
- 使用OpenCV和Numpy函式查找直方圖
- 使用OpenCV和Matplotlib函式繪制直方圖
- 函式:
cv2.calcHist(),np.histogram()等,
理論
直方圖(Histograms)是什么?可以將直方圖視為圖形或繪圖,從而可以總體了解影像的強度分布,它是在X軸上具有像素值(不總是從0到255的范圍),在Y軸上具有影像中相應像素數量的圖,
hog只是理解影像的另一種方式,通過查看影像的直方圖,可以直觀地了解該影像的對比度,亮度,強度分布等,當今幾乎所有影像處理工具都提供直方圖功能,下圖來自網站( Cambridge in Color website)的圖片

可以看到影像及其直方圖,(記住,直方圖是針對灰度影像而非彩色影像繪制的),直方圖的左側區域顯示影像中較暗像素的數量,而右側區域則顯示明亮像素的數量,從直方圖中,可以看到暗區域多于亮區域,而中間調的數量(中間值的像素值,例如127附近)則非常少,
尋找直方圖
現在有了關于直方圖的認識,就可以研究如何找到它,OpenCV和Numpy都為此內置了實作函式,在使用這些函式之前,需要了解一些與直方圖有關的術語,
-
BINS:上面的直方圖顯示每個像素值的像素數,即從0到255,即,需要256個值來顯示上面的直方圖,但是假設如果不需要分別找到所有像素值的像素數,而是找到像素值間隔中的像素數怎么辦?
例如,需要找到介于0到15之間的像素數,然后找到16到31之間,…,240到255之間的像素數,只需要16個值即可表示直方圖,
因此,要做的就是將整個直方圖分成16個子部分,每個子部分的值就是其中所有像素數的總和,
每個子部分都稱為BIN,在第一種情況下,bin的數量為256個(每個像素一個),而在第二種情況下,bin的數量僅為16個,BINS由OpenCV檔案中的histSize術語表示, -
DIMS:為其收集資料的引數的數量,在這種情況下,僅收集關于強度值這一件事的資料,所以這里是1,
-
RANGE:要測量的強度值的范圍,通常,它是[0,256],即所有強度值,
OpenCV中的直方圖計算
在OpenCv中,可以使用cv2.calcHist()函式查找直方圖,熟悉一下該函式及其引數:
cv2.calcHist(images,channels,mask,histSize,ranges [,hist [,accumulate]])
images:它是uint8或float32型別的源影像,它應該放在方括號中,即 [img]channels:也以方括號給出,是計算直方圖的通道的索引,例如,如果輸入為灰度影像,則其值為[0],對于彩色影像,可以傳遞[0],[1]或[2]分別計算藍色,綠色或紅色通道的直方圖mask:影像掩碼,為了找到完整影像的直方圖,將其指定為“無”,但是,如果要查找影像特定區域的直方圖,則必須為此創建一個掩碼影像并將其作為掩碼histSize:這表示BIN計數,需要放在方括號中,對于全尺寸,設定為[256]ranges:這是RANGE,通常為[0,256]
img = cv.imread('home.jpg',0)
hist = cv.calcHist([img],[0],None,[256],[0,256])
hist是256x1的陣列,每個值對應于該影像中具有相應像素值的像素數

Numpy的直方圖計算
Numpy也提供了一個函式np.histogram(),因此,除了cv2.calcHist()函式外,可以嘗試下面的代碼:
import numpy as np
hist, bins = np.histogram(img.ravel(), 256, [0,256]) # 先展成一維再統計


可以看到,hist與opencv計算的結果相同,但是bin具有257個元素(0~256),因為Numpy計算出bin的范圍為0-0.99、1-1.99、2-2.99等, 因此最終范圍為255-255.99,為了表示這一點,他們還在最后添加了256,但使用時候不需要256, 最多255就足夠了,
另外
Numpy還有另一個函式np.bincount(),它比np.histogram()快10倍左右,因此,對于一維直方圖,可以更好地嘗試一下,不要忘記在np.bincount中設定minlength = 256,例如,
hist = np.bincount(img.ravel(),minlength = 256) # 先展成一維再統計

注意
OpenCV函式cv2.calcHist()比np.histogram()快大約40倍,因此,盡可能使用OpenCV函式的cv2.calcHist(),
繪制直方圖
繪制直方圖有兩種方法,
- 簡短的方法:使用Matplotlib繪圖功能
- 稍長的方法:使用OpenCV繪圖功能
使用Matplotlib
Matplotlib帶有直方圖繪圖功能:matplotlib.pyplot.hist()
它直接找到直方圖并將其繪制, 無需使用calcHist()或np.histogram()函式來查找直方圖,代碼如下:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('hog.jpg', 0)
plt.subplot(1,2,1)
plt.imshow(img, cmap='gray')
plt.subplot(1,2,2)
plt.hist(img.ravel(), 256, [0,256])
plt.show()
結果如下:

或者,可以使用matplotlib的法線圖,這對于BGR圖是很好的,為此,需要首先找到直方圖資料,代碼如下:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('hog.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
結果如下:

可以從上圖中得出,藍色在影像中具有一些高值域(顯然這應該是由于天空)
使用 OpenCV
在這里可以調整直方圖的值及其bin值,使其看起來像x,y坐標,以便可以使用cv2.line()或cv2.polyline()函式繪制它以生成與上述相同的影像,OpenCV-Python官方示例已經提供了樣例代碼
#!/usr/bin/env python
# https://github.com/opencv/opencv/blob/master/samples/python/hist.py
''' This is a sample for histogram plotting for RGB images and grayscale images for better understanding of colour distribution
Benefit : Learn how to draw histogram of images
Get familier with cv.calcHist, cv.equalizeHist,cv.normalize and some drawing functions
Level : Beginner or Intermediate
Functions : 1) hist_curve : returns histogram of an image drawn as curves
2) hist_lines : return histogram of an image drawn as bins ( only for grayscale images )
Usage : python hist.py <image_file>
Abid Rahman 3/14/12 debug Gary Bradski
'''
# Python 2/3 compatibility
from __future__ import print_function
import numpy as np
import cv2 as cv
bins = np.arange(256).reshape(256,1)
def hist_curve(im):
h = np.zeros((300,256,3))
if len(im.shape) == 2:
color = [(255,255,255)]
elif im.shape[2] == 3:
color = [ (255,0,0),(0,255,0),(0,0,255) ]
for ch, col in enumerate(color):
hist_item = cv.calcHist([im],[ch],None,[256],[0,256])
cv.normalize(hist_item,hist_item,0,255,cv.NORM_MINMAX)
hist=np.int32(np.around(hist_item))
pts = np.int32(np.column_stack((bins,hist)))
cv.polylines(h,[pts],False,col)
y=np.flipud(h)
return y
def hist_lines(im):
h = np.zeros((300,256,3))
if len(im.shape)!=2:
print("hist_lines applicable only for grayscale images")
#print("so converting image to grayscale for representation"
im = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
hist_item = cv.calcHist([im],[0],None,[256],[0,256])
cv.normalize(hist_item,hist_item,0,255,cv.NORM_MINMAX)
hist = np.int32(np.around(hist_item))
for x,y in enumerate(hist):
cv.line(h,(x,0),(x,y[0]),(255,255,255))
y = np.flipud(h)
return y
def main():
import sys
if len(sys.argv)>1:
fname = sys.argv[1]
else :
fname = 'lena.jpg'
print("usage : python hist.py <image_file>")
im = cv.imread(cv.samples.findFile(fname))
if im is None:
print('Failed to load image file:', fname)
sys.exit(1)
gray = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
print(''' Histogram plotting \n
Keymap :\n
a - show histogram for color image in curve mode \n
b - show histogram in bin mode \n
c - show equalized histogram (always in bin mode) \n
d - show histogram for gray image in curve mode \n
e - show histogram for a normalized image in curve mode \n
Esc - exit \n
''')
cv.imshow('image',im)
while True:
k = cv.waitKey(0)
if k == ord('a'):
curve = hist_curve(im)
cv.imshow('histogram',curve)
cv.imshow('image',im)
print('a')
elif k == ord('b'):
print('b')
lines = hist_lines(im)
cv.imshow('histogram',lines)
cv.imshow('image',gray)
elif k == ord('c'):
print('c')
equ = cv.equalizeHist(gray)
lines = hist_lines(equ)
cv.imshow('histogram',lines)
cv.imshow('image',equ)
elif k == ord('d'):
print('d')
curve = hist_curve(gray)
cv.imshow('histogram',curve)
cv.imshow('image',gray)
elif k == ord('e'):
print('e')
norm = cv.normalize(gray, gray, alpha = 0,beta = 255,norm_type = cv.NORM_MINMAX)
lines = hist_lines(norm)
cv.imshow('histogram',lines)
cv.imshow('image',norm)
elif k == 27:
print('ESC')
cv.destroyAllWindows()
break
print('Done')
if __name__ == '__main__':
print(__doc__)
main()
cv.destroyAllWindows()
掩碼的應用
使用cv2.calcHist()能查找整個影像的直方圖,如果想找到影像某些區域的直方圖呢?只需創建一個掩碼影像,將要找到直方圖為白色,否則黑色, 然后把這個作為掩碼傳遞,
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv2.imread('hog.jpg', 0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:200, 100:200] = 255 # set white
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.subplot(2,2,1)
plt.subplot(2,2,1)
plt.imshow(img, cmap='gray')
plt.subplot(2,2,2)
plt.imshow(mask, cmap='gray')
plt.subplot(2,2,3)
plt.imshow(masked_img, cmap='gray')
plt.subplot(2, 2, 4)
plt.plot(hist_full, color='r')
plt.plot(hist_mask, color='g')
plt.xlim([0, 255])
plt.show()
plt.show()
查看結果,在直方圖中,藍線表示完整影像的直方圖,綠線表示掩碼區域的直方圖,

附加資源
- https://docs.opencv.org/4.1.2/d1/db7/tutorial_py_histogram_begins.html
- Cambridge in Color website
- https://docs.opencv.org/4.1.2/d8/dbc/tutorial_histogram_calculation.html
- https://github.com/opencv/opencv/blob/master/samples/python/hist.py
- https://zhuanlan.zhihu.com/p/36656952
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/345586.html
標籤:其他

