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

目标

学会

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

理论

什么是直方图?你可以将直方图视为图形或图表,它可以为你提供图像强度分布的总览。这是一个图表,X 轴上是像素值(从 0 到 255,但不总是这样),Y 轴上是图像中对应的像素数。

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

image

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

查找直方图

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

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

所以你要做的就是把整个直方图简单的分割成 16 个子部分,而每个子部分的值就是其中所有像素计数的和。每个子部分被称为 "BIN"。第一种情况下,bin 的数量是 256(每个像素一个),而第二种情况下,数量仅为 16。在 OpenCV 文档中,BIN 由术语 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:这是我们的 RANGE。通常,它是 [0,256]。

因此让我们以一个示例图像开始。只需以灰度模式加载一张图片并找出它的完整直方图。

img = cv.imread('home.jpg', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
hist = cv.calcHist([img],[0],None,[256],[0,256])
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件加载图像。
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(),它快很多(大約 10 倍)於 np.histogram()。因此,對於一維直方圖,您可以嘗試它。不要忘記在 np.bincount 中設定 minlength = 256。例如,hist = np.bincount(img.ravel(),minlength=256)
OpenCV 函式快(大約 40 倍)於 np.histogram()。所以,請堅持使用 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, "file could not be read, check with 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, "file could not be read, check with 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, "file could not be read, check with 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. Cambridge in Color 网站

练习