OpenCV 4.11.0
开源计算机视觉
加载中…
搜索中…
无匹配项
直方图 - 1:查找、绘制、分析!!!

目标

学习如何

  • 使用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]])
  1. images:它是uint8或float32类型的源图像。它应该用方括号给出,即“[img]”。
  2. channels:它也用方括号给出。它是我们计算直方图的通道索引。例如,如果输入是灰度图像,则其值为[0]。对于彩色图像,您可以传递[0]、[1]或[2]分别计算蓝色、绿色或红色通道的直方图。
  3. mask:掩码图像。要查找整个图像的直方图,将其设置为“None”。但是,如果您想查找图像特定区域的直方图,则必须为此创建一个掩码图像并将其作为掩码给出。(我稍后将展示一个示例。)
  4. histSize:这表示我们的BIN计数。需要用方括号给出。对于全量程,我们传递[256]。
  5. ranges:这是我们的范围。通常是[0,256]。

所以让我们从一个示例图像开始。只需以灰度模式加载图像并查找其完整直方图。

img = cv.imread('home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
hist = cv.calcHist([img],[0],None,[256],[0,256])
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函数。

现在我们应该绘制直方图,但是如何绘制呢?

绘制直方图

有两种方法:

  1. 简便方法:使用Matplotlib绘图函数
  2. 复杂方法:使用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
img = cv.imread('home.jpg')
assert img is not None, "文件无法读取,请使用os.path.exists()检查"
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()

结果

图像

您可以从上图推断出,蓝色在图像中有一些高值区域(显然这应该是由于天空造成的)

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
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, '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) 计算每个元素的按位……

查看结果。在直方图图中,蓝线显示完整图像的直方图,而绿线显示掩码区域的直方图。

图像

附加资源

  1. 剑桥色彩网站