OpenCV-Python系列之直方图简述
自本教程开始,我们开始系统的讨论OpenCV中的直方图,图像的直方图也是一个庞大的体系,我们将会用多次教程来讲述。
首先,我们来介绍下直方图是什么,我们可以将直方图视为一个图形或图表,它可以让我们对图像的强度分布有一个大致的了解。而该图形是一个x 轴上像素值(范围从0到255,但并不总是)和y 轴上有相应像素数的图像。
可以说,它是另一种对图像理解的方式。通过观察图像的直方图,我们可以得到关于图像的相反,亮度,强度分布等直观信息。如今现在几乎所有的图像处理工具都可以提供其图像直方图的特征。下面是一个从Cambridge in Color website 获取的图像,有条件的小伙伴可以访问这个网站了解更多的细节。
上图中我们可以看到原图像及其直方图。(注,这里的直方图是在灰度图像基础上绘制的,而不是彩色图像)。直方图的左区域显示较暗(pixel value比较小)的像素在图像中的数量,而右区域则显示了较亮(pixel value比较大)像素的数量。从上面的柱状图中我们可以看到黑暗的区域比亮区域大,还有中间色调(像素值在中档,值在127附近)的数量非常少。
寻找直方图
现在我们粗略了解了什么是直方图,我们接下去考虑如何找到直方图呢。无论 OpenCV 还是 Numpy 都带有内置函数。在使用这些函数之前,我们先需要了解一下和直方图相关的术语。
BINS:上面我们举例子用的直方图显示了每个像素的像素值范围,即从0到255,这很有帮助如果我们的直方图需要全部这256个像素值。但是,假如我们并不需要一整个拥有所有像素值的范围,而是一部分一部分分隔开来的像素值区间呢?比如说,我们需要区间在0到15,16到31,..... 240到255的像素的数量。那么我们只需要16个值就可以代表这个直方图了。
所以我们接下去要做的仅仅是把整个直方图分为16个部分(sub-part)并保证每个部分的值相加起来就是所有像素加起来的总和。这里的每个部分我们称之称为“BIN”。在一开始的例子中,BIN 数量为256(每一个数字代表一个像素),而在我们稍后分隔后的例子里,BIN 的数量就是16 了。BIN 用来代表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:掩膜图像。为了找到完整图像的直方图,我们将其设为“None”。但如果在未来想找到图像某特定区域的直方图,我们可以为它创建一个掩码图像并将其设为mask。(文章后面将会展示一个示例。)
histSize:这用来表示我们的BIN 数量,需要用方括号表示。对于全比例图像,我们则设置其值为 [256]。
范围 ranges:这用来表示我们的范围,通常是[0,256]。
现在让我们从一个样本图像开始,在灰度模式下加载图像并找到它的完整直方图:
代码如下:
def calhist(img): hist = cv2.calcHist([img],[0],None,[256],[0,256]) print(hist)
这样我们得到的hist 就是一个256 x1 的数组,数组中的每个值对应于图像中像素的数量和其对应的像素值。
Numpy 中的直方图计算
Numpy 也为我们提供一个函数,np.histogram(),用来取代calcHist() 函数的作用,我们可以试试下面这行代码:
def npcalhist(img): hist, bins = np.histogram(img.ravel(), 256, [0, 256]) print(hist)
这里的hist 计算方法和我们之前教程中使用的一样。区别是这儿的bins 有257个元素,因为Numpy 是这么计算bins 的,0 - 0.99,1 - 1.99,2 - 2.99 ... 这样,最后的范围则是255 - 255.99。为了257 这样的表示,我们在bins 的尾端增加256;但其实我们不需要256,所以实际上到255 就足够了。
Numpy 还有另一个函数np.bincount(),它比np.histogram() 函数快得多,快了将近10倍。因此对于一维直方图,我们可以用bincount() 这个函数,但别忘了在np.bincount() 中设置minlength = 256。例如:
hist = np.bincount(img.ravel(),minlength=256).
但是,OpenCV函数比histogram() 还要更快(约40倍),所以还是尽可能用OpenCV 函数吧。
好了,知道了怎么寻找直方图,接下来我们应该学会怎么绘制,但是要怎么做呢?
绘制直方图
我们有两种方法绘制:
· 简捷的方法:使用Matplotlib 绘图函数
· 复杂一点的方法:使用OpenCV 绘图功能
使用Matplotlib
Matplotlib 附带了一个直方图绘制函数:matplotlib.pyplot.hist()。
它可以直接找到直方图并绘制,我们就不需要使用calcHist() 或np.histogram() 函数来预先找到直方图。看下面的代码:
from matplotlib import pyplot as plt#导入Matplotlib库 def matdraw(img): plt.hist(img.ravel(), 256, [0, 256]) plt.show()
或者我们可以使用正常普通的matplotlib 绘图功能,这将有利于BGR 色彩绘图。如果要这么做,首先我们需要找到直方图数据。我们来看代码:
def RGBdraw(img): 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()
这样可以更直观的看RGB三个通道的直方图。
由于OpenCV的绘制直方图相当的繁琐,在这里就不介绍了,通常我们并不使用OpenCV的函数来生成直方图。
下一个教程我们将讲述直方图的其他应用。