目标
学会
- 使用 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]])
- images:它是 uint8 或 float32 类型源图像。应该以方括号形式提供,即“[img]”。
- channels:它也以方括号形式提供。它是我们计算直方图的通道索引。例如,如果输入是灰度图像,它的值为 [0]。对于彩色图像,可以分别传递 [0],[1] 或 [2] 来计算蓝色、绿色或红色通道的直方图。
- mask:遮罩图像。若要找出整张图像的直方图,它以 "None" 提供。但是如果你想要找出图像的特定区域的直方图,你必须创建专门针对它的遮罩图像并将其作为面罩提供。(我稍后会提供一个示例。)
- histSize:这表示我们的 BIN 计数。需要以方括号形式提供。对于全幅图像,我们传递 [256]。
- 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()"
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 函式。
現在我們應該繪製直方圖,但是如何做?
繪製直方圖
可以有兩種方式,
- 簡短方式:使用 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, "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
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)
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
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
附加资源
- Cambridge in Color 网站
练习