目标
使用 OpenCV 或 Numpy 函数计算直方图
使用 Opencv 或者 Matplotlib 函数绘制直方图
将要学习的函数有:
cv2.calcHist()
,np.histogram()
原理
什么是直方图呢?通过直方图可以对整幅图像的灰度分布有一个整体的了解。 直方图的 x 轴是灰度值(0 到 255),y 轴是图片中具有同一个灰度值的点的数目。
直方图其实就是对图像的另一种解释。一下图为例, 通过直方图可以对图像的对比度,亮度,灰度分布等有一个直观的认识。 几乎所有的图像处理软件都提供了直方图分析功能。 下图来自Cambridge in Color website,强烈推荐到这个网站了解更多知识。
接下来一起看看这幅图片和它的直方图吧。(要记住,直方图是根据灰度图像绘制的,而不是彩色图像)。 直方图的左边区域像是了暗一点的像素数量,右侧显示了亮一点的像素的数量。 从这幅图上可以看到灰暗的区域比两的区域要大,而处于中间部分的像素点很少。
统计直方图
现在知道什么是直方图了,那怎样获得一副图像的直方图呢? OpenCV 和 Numpy 都有内置函数做这件事。 在使用这些函数之前有必要想了解一下直方图相关的术语。
BINS:上面的直方图显示了每个灰度值对应的像素数。 如果像素值为 0到 255,就需要 256 个数来显示上面的直方图。 但是,如果不需要知道每一个像素值的像素点数目的, 而只希望知道两个像素值之间的像素点数目怎么办呢? 举例来说,若想知道像素值在 0 到 15 之间的像素点的数目, 接着是 16 到 31,....,240 到 255。 只需要 16 个值来绘制直方图。 OpenCVTutorials on histograms中例子所演示的内容。
那到底怎么做呢?只需要把原来的 256 个值等分成 16 小组, 取每组的总和。而这里的每一个小组就被成为 BIN。 第一个例子中有 256 个 BIN,第二个例子中有 16 个 BIN。 在 OpenCV 的文档中用 histSize 表示 BINS。
- DIMS:表示收集数据的参数数目。在本例中, 对收集到的数据只考虑一件事:灰度值。所以这里就是 1。
- RANGE:就是要统计的灰度值范围,一般来说为
[0,256]
, 也就是说所有的灰度值
使用 OpenCV 统计直方图 函数 cv2.calcHist
可以帮助统计一幅图像的直方图
那么一起来熟悉一下这个函数和它的参数:
cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])
- images: 原图像(图像格式为 uint8 或 float32)。
当传入函数时应该用中括号
[]
括起来,例如:[img]
。 - channels: 同样需要用中括号括起来,
它会告诉函数要统计那幅图像的直方图。如果输入图像是灰度图,
它的值就是
[0]
;如果是彩色图像的话, 传入的参数可以是[0]
,[1]
,[2]
它们分别对应着通道 B,G,R。 - mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。 但是如果想统计图像某一部分的直方图的话, 就需要制作一个掩模图像,并使用它。(后边有例子)
- histSize:BIN 的数目。也应该用中括号括起来,例如:
[256]
。 - ranges: 像素值范围,通常为
[0,256]
从一副简单图像开始吧。以灰度格式加载一幅图像并统计图像的直方图。
import cv2
import numpy as np
img = cv2.imread('../data/home.jpg',0)
#别忘了中括号 [img],[0],None,[256],[0,256] ,只有 mask 没有中括号
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist
是一个 256x1 的数组,每一个值代表了与次灰度值对应的像素点数目。
使用 Numpy 统计直方图 Numpy 中的函数 np.histogram()
也可以帮助统计直方图。
#img.ravel()
将图像转成一维数组,这里没有中括号。
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.histgram
的 十 倍。
所 以 对 于 一 维 直 方 图, 最 好 使 用 这 个函 数。
使 用 np.bincount
时 别 忘 了 设 置 minlength=256
。
例 如,
hist=np.bincount(img.ravel()
,minlength=256)
注意:OpenCV 的函数要比 np.histgram()
快 40 倍。
所以坚持使用OpenCV 函数。
现在是时候学习绘制直方图了。
绘制直方图
有两种方法来绘制直方图:
Short Way(简单方法):使用 Matplotlib 中的绘图函数。
Long Way(复杂方法):使用 OpenCV 绘图函数
使用 Matplotlib Matplotlib 中有直方图绘制函数: matplotlib.pyplot.hist()
它可以直接统计并绘制直方图。应该使用函数 calcHist()
或 np.histogram()
统计直方图。
代码如下:
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('../data/home.jpg',0)
plt.subplot(121),plt.imshow(img)
plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.xticks([]),plt.yticks([])
plt.hist(img.ravel(),256,[0,256])
plt.show()
/tmp/ipykernel_400/2326931546.py:11: MatplotlibDeprecationWarning: Passing the range parameter of hist() positionally is deprecated since Matplotlib 3.9; the parameter will become keyword-only in 3.11. plt.hist(img.ravel(),256,[0,256])
或者可以只使用 matplotlib 的绘图功能,这在同时绘制多通道(BGR)的直方图,很有用。 但是首先要告诉绘图函数的直方图数据在哪里。运行一下下面的代码:
img = cv2.imread('../data/home.jpg')
color = ('b','g','r')
# 对一个列表或数组既要遍历索引又要遍历元素时
# 使用内置 enumerrate 函数会有更加直接,优美的做法
#enumerate 会将数组或列表组成一个索引序列。
# 使我们再获取索引和索引内容的时候更加方便
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
img = cv2.imread('../data/home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)
# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.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()