翻译及二次校对:cvtutorials.com
目标
学习:
理论
那么什么是直方图?你可以把直方图看作是一种图,它可以让你对图像的灰度分布有一个整体的了解。它是一个在X轴上有像素值(范围从0到255,不一定),在Y轴上有图像中相应像素数的图。
它只是理解图像的另一种方式。通过观察图像的直方图,你可以获得关于该图像的对比度、亮度、灰度分布等的直觉。今天,几乎所有的图像处理工具都提供直方图的功能。下面是一张来自Cambridge in Color网站的图片,我建议你访问该网站了解更多细节。
你可以看到这个图像和它的直方图(这个直方图是为灰度图像绘制的,不是彩色图像)。直方图的左边区域显示图像中较暗像素的数量,右边区域显示较亮像素的数量。从直方图中,你可以看到暗色区域比亮色区域多,中间色调(中间范围的像素值,例如127左右)的数量非常少。
寻找直方图
现在我们对什么是直方图有了一个概念,我们可以研究如何找到它。OpenCV和Numpy都有内置的函数来完成这个任务。在使用这些函数之前,我们需要了解一些与直方图有关的术语。
BINS :上面的直方图显示了每个像素值的像素数,即从0到255,即你需要256个值来显示上述直方图。但请考虑,如果你不需要单独找到所有像素值的像素数,而是需要找到像素值的一个区间的像素数,怎么办?例如,你需要找到0到15之间的像素数,然后是16到31,...,240到255。你只需要16个值来表示直方图。
所以你要做的就是简单地把整个直方图分成16个子部分,每个子部分的值是其中所有像素数的总和。这个子部分被称为 "BIN"。在第一种情况下,BIN的数量是256(每个像素一个),而在第二种情况下,它只有16。在OpenCV的文档中,BINS是由术语histSize表示的。
DIMS : 它是我们收集数据的参数数量。在这种情况下,我们只收集一种数据,即灰度值。所以这里是1。
RANGE : 它是你想测量的灰度值的范围。通常情况下,它是[0,256],即所有灰度值。
所以现在我们使用cv.calcHist()函数来寻找直方图。让我们熟悉一下这个函数和它的参数。
cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]] )
那么,让我们从一个图像开始。简单地在灰度模式下加载一个图像,然后找到它的直方图。
img = cv.imread('home.jpg',0)
hist = cv.calcHist([img],[0],None,[256],[0,256])
hist是一个256x1的数组,每个值都对应于该图像中的像素数和其对应的像素值。
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等等。所以最终的范围将是255-255.99。为了表示这一点,他们还在bins的末尾添加了256。但我们不需要那256。到255就足够了。
注意:Numpy还有一个函数,np.bincount(),它比np.histogram()快得多(大约10倍)。所以对于一维直方图,你最好试试这个。不要忘记在np.bincount中设置minlength = 256。例如,hist = np.bincount(img.ravel(),minlength=256) OpenCV函数比np.histogram()快(大约40倍)。所以请使用OpenCV函数。
现在我们应该绘制直方图,但如何绘制呢?
绘制直方图
有两种方法可以做到这一点。
Matplotlib有一个直方图绘制函数:matplotlib.pyplot.hist()
它直接找到直方图并绘制出来。你不需要使用calcHist()或np.histogram()函数来寻找直方图。请看下面的代码。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg',0)
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('home.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()
结果:
你可以从上图中推断出,蓝色在图像中有一些高值区域(显然应该是由于天空的原因)。
我们可以将直方图的值和它的bin值调整成x,y坐标的样子,这样你就可以用cv.line()或cv.polyline()函数来绘制它,生成与上面相同的图像。这在OpenCV-Python2官方样本中已经有了。请查看样本/python/hist.py中的代码。
掩膜的应用
我们用cv.calcHist()来寻找全图的直方图。如果你想找到图像中某些区域的直方图呢?只要在你想找直方图的区域创建一个白色的蒙版图像,其他区域设置为黑色。然后把它作为掩膜传给你。
img = cv.imread('home.jpg',0)
# 创建一个掩膜
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img,img,mask = mask)
# 计算带掩膜和不带掩膜的直方图
# 检查第三个参数的掩膜
hist_full = cv.calcHist([img],[0],None,[256],[0,256] )
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256] )
plt.subplot(221), plt.imshow(img, 'grey')
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()
请看结果。在直方图图中,蓝线显示的是完整图像的直方图,而绿线显示的是被遮蔽区域的直方图。
其他资源