目标
学习如何
- 使用OpenCV和Numpy函数查找直方图
- 使用OpenCV和Matplotlib函数绘制直方图
- 您将看到这些函数:cv.calcHist(),np.histogram() 等。
理论
那么什么是直方图呢?您可以将直方图视为图表或曲线图,它可以为您提供图像强度分布的整体概念。它是一个图表,X轴表示像素值(范围通常为0到255,但并非总是如此),Y轴表示图像中对应像素的数量。
这只是理解图像的另一种方式。通过查看图像的直方图,您可以直观地了解该图像的对比度、亮度、强度分布等。如今几乎所有图像处理工具都提供直方图功能。以下是来自剑桥色彩网站的图像,我建议您访问该网站了解更多详细信息。
图像
您可以看到图像及其直方图。(记住,此直方图是针对灰度图像绘制的,而不是彩色图像)。直方图的左侧区域显示图像中较暗像素的数量,右侧区域显示较亮像素的数量。从直方图可以看出,暗区多于亮区,中间色调(中等范围的像素值,例如大约127)的数量非常少。
查找直方图
现在我们了解了什么是直方图,我们可以看看如何找到它。OpenCV和Numpy都带有内置函数来实现此功能。在使用这些函数之前,我们需要了解与直方图相关的某些术语。
BIN(区间):上面的直方图显示了每个像素值的像素数量,即从0到255。也就是说,需要256个值来显示上述直方图。但是,如果您不需要分别查找所有像素值的像素数量,而是查找像素值区间内的像素数量呢?例如,您需要查找介于0到15之间的像素数量,然后是16到31,…,240到255。您只需要16个值即可表示直方图。这就是OpenCV直方图教程中给出的示例中所示内容。
因此,您只需将整个直方图分成16个子部分,每个子部分的值都是其中所有像素计数的总和。每个子部分称为“BIN”(区间)。在第一种情况下,区间数为256(每个像素一个),而在第二种情况下,它只有16。BIN在OpenCV文档中由术语histSize表示。
DIMS(维度):这是我们收集数据的参数数量。在这种情况下,我们只收集一项数据,即强度值。因此这里为1。
RANGE(范围):这是您要测量的强度值范围。通常是[0,256],即所有强度值。
1. 在OpenCV中计算直方图
现在我们使用cv.calcHist()函数来查找直方图。让我们熟悉一下该函数及其参数
cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])
- images:它是uint8或float32类型的源图像。它应该用方括号给出,即“[img]”。
- channels:它也用方括号给出。它是我们计算直方图的通道索引。例如,如果输入是灰度图像,则其值为[0]。对于彩色图像,您可以传递[0]、[1]或[2]分别计算蓝色、绿色或红色通道的直方图。
- mask:掩码图像。要查找整个图像的直方图,将其设置为“None”。但是,如果您想查找图像特定区域的直方图,则必须为此创建一个掩码图像并将其作为掩码给出。(我稍后将展示一个示例。)
- histSize:这表示我们的BIN计数。需要用方括号给出。对于全量程,我们传递[256]。
- ranges:这是我们的范围。通常是[0,256]。
所以让我们从一个示例图像开始。只需以灰度模式加载图像并查找其完整直方图。
img =
cv.imread(
'home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。
void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
计算一组数组的直方图。
hist是一个256x1数组,每个值对应于该图像中具有相应像素值的像素数量。
2. 在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等。因此最终范围将是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绘图函数
- 复杂方法:使用OpenCV绘图函数
1. 使用Matplotlib
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', cv.IMREAD_GRAYSCALE)
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
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
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
color = ('b','g','r')
for i,col in enumerate(color)
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
结果
图像
您可以从上图推断出,蓝色在图像中有一些高值区域(显然这应该是由于天空造成的)
2. 使用OpenCV
在这里,您可以调整直方图的值及其bin值,使其看起来像x,y坐标,以便您可以使用cv.line()或cv.polyline()函数生成与上面相同的图像。这在OpenCV-Python2官方示例中已经可用。请查看samples/python/hist.py中的代码。
掩码的应用
我们使用cv.calcHist()来查找整张图像的直方图。如果您想查找图像某些区域的直方图怎么办?只需创建一个掩码图像,在要查找直方图的区域使用白色,其他区域使用黑色。然后将其作为掩码传递。
img =
cv.imread(
'home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
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()
void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray())
计算两个数组的按位合取 (dst = src1 & src2) 计算每个元素的按位……
查看结果。在直方图图中,蓝线显示完整图像的直方图,而绿线显示掩码区域的直方图。
图像
附加资源
- 剑桥色彩网站