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

目标

学习如何

  • 使用 OpenCV 和 Numpy 函数查找直方图
  • 使用 OpenCV 和 Matplotlib 函数绘制直方图
  • 您将看到以下函数:cv.calcHist()np.histogram() 等。

理论

什么是直方图?您可以将直方图视为图形或绘图,它为您提供了图像强度分布的总体概念。它是一个以像素值(范围从 0 到 255,但不总是)为 X 轴,图像中对应像素数为 Y 轴的绘图。

这只是理解图像的另一种方式。通过查看图像的直方图,您可以直观地了解该图像的对比度、亮度、强度分布等。当今几乎所有的图像处理工具都提供直方图功能。以下是来自 剑桥色彩网站 的图片,我建议您访问该网站了解更多详情。

image

您可以看到图像及其直方图。(请记住,此直方图是为灰度图像绘制的,而不是彩色图像)。直方图的左侧区域显示图像中较暗像素的数量,右侧区域显示较亮像素的数量。从直方图可以看出,暗区多于亮区,并且中间调(中间范围内的像素值,例如 127 左右)的数量非常少。

查找直方图

现在我们对什么是直方图有了一个概念,我们可以研究如何找到它。OpenCV 和 Numpy 都带有用于此的内置函数。在使用这些函数之前,我们需要了解一些与直方图相关的术语。

BINS(组距):上面的直方图显示了每个像素值的像素数,即从 0 到 255。即,您需要 256 个值来显示上面的直方图。但是考虑一下,如果您不需要分别找到所有像素值的像素数,而是需要找到像素值间隔中的像素数呢?例如,您需要找到介于 0 到 15、16 到 31、...、240 到 255 之间的像素数。您只需要 16 个值来表示直方图。这就是 OpenCV 直方图教程 中给出的示例中显示的内容。

因此,您所做的只是将整个直方图简单地分成 16 个子部分,每个子部分的值是其中所有像素计数的总和。每个子部分称为“BIN(组距)”。在第一种情况下,组距数为 256(每个像素一个),而在第二种情况下,仅为 16。组距在 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:这代表我们的组距计数。需要以方括号给出。对于完整比例,我们传递 [256]。
  5. ranges:这是我们的 RANGE(范围)。通常,它是 [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()

您将获得如下图

image

或者您可以使用 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()

结果

image

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

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)计算按元素逐位的...

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

image

附加资源

  1. 剑桥色彩网站