第13章:直方图处理
- 一、直方图的含义:
- 1. 普通直方图:
- 2. 归一化直方图:
- 二、绘制直方图:
- 1. 使用Numpy绘制直方图:
- 2. 使用OpenCV绘制直方图:
- 3. 使用掩码绘制直方图:
- 三、直方图均衡化:
- 1. 直方图均衡化的原理:
- (1) 在原有范围内实现均衡化:
- (2) 在更广泛的范围内实现均衡化:
- 2. 直方图均衡化处理:
- 直方图是图像处理过程中一种重要的分析工具。
- 直方图是从图像内部灰度级的角度对图像进行表述,包含丰富重要的信息。
- 从直方图的角度对图像进行处理,可以达到增强图像显示效果的目的。
一、直方图的含义:
1. 普通直方图:
从统计的角度讲,直方图统计的是图像内各个灰度级出现的次数。从直方图的图形上观察,横坐标
是图像中各像素点的灰度级
,纵坐标
是具有该灰度级(像素值)的像素个数
。
例如,有一幅图像。该图中只有9个像素点,存在1、2、3、4、5,共5个灰度级。
统计各个灰度级出现的次数:
在绘制直方图时,将灰度级作为x轴处理,该灰度级出现的次数作为y轴处理,则可知:
- x轴的数据为x=[1 2 3 4 5]。
- y轴的数据为y=[3 1 2 1 2]。
根据上述关系,可以绘制出如图所示的折线图(左图)和直方图(右图)。一般情况下,我们把左侧的直线图和右侧直方图都称为直方图。
在实际处理中,图像直方图的x轴区间一般是[0,255],对应的是8位位图的256个灰度级;y轴对应的是具有相应灰度级的像素点的个数。
2. 归一化直方图:
归一化直方图中,x轴仍然表示灰度级;y轴不再表示灰度级出现的次数,而是灰度级出现的频率。
- 灰度级出现的频率 = 灰度级出现的次数/总像素数
例如,统计各个灰度级出现的频率:
在归一化直方图中,各个灰度级出现的频率之和为1。例如,本例中:
在绘制直方图时,将灰度级作为x轴数据处理,将其出现的频率作为y轴数据处理,则可知:
- x轴的数据为x=[1 2 3 4 5]
- y轴的数据为y=[3/9 1/9 2/9 1/9 2/9]
注意:在OpenCV的官网上,特别提出了要注意三个概念:DIMS、BINS、RANGE。
- DIMS:表示在绘制直方图时,收集的参数的数量。一般情况下,直方图中收集的数据只有一种,就是灰度级。因此,该值为1。
- RANGE:表示要统计的灰度级范围,一般为[0,255]。0对应的是黑色,255对应的是白色。
- BINS:参数子集的数目,即参数分的组的数量。在处理数据的过程中,有时需要将众多的数据划分为若干个组,再进行分析。
例如,在灰度图像中,将[0,255]区间内的256个灰度级,按照每16个像素一组划分为子集:
[0,255]=[0,15]∪[16,31]∪…∪[240,255]
按照上述方式,整个灰度级范围可以划分为16个子集,具体为:
整个灰度级范围=bin1 ∪ bin2 ∪…∪ bin16
子集划分完以后,某灰度图像生成的直方图如图所示
讨论BINS的值:
- 在原始图像中,如果,共有5个灰度级,其BINS值为5。在以2个灰度级为一个小组划分子集后,得到3个子集,其BINS值为3。
- 针对灰度图像,灰度级区间为[0,255],共有256个灰度级,其BINS值为256;在以16个灰度级为一个小组划分子集后,其BINS值为16。
二、绘制直方图:
绘制直方图的方式有两种:
- Python的模块matplotlib.pyplot中的hist()函数能够方便地绘制直方图,我们通常采用该函数直接绘制直方图。
- OpenCV中的cv2.calcHist()函数能够计算统计直方图,还可以在此基础上绘制图像的直方图。
1. 使用Numpy绘制直方图:
Python模块matplotlib.pyplot 提供了一个类似于 MATLAB 绘图方式的框架,可以使用其中的matplotlib.pyplot.hist() 函数(以下简称为hist()函数)来绘制直方图。
此函数的作用是根据数据源
和灰度级
分组绘制直方图。其基本语法格式为:
- matplotlib.pyplot.hist(X,BINS)
- X:数据源,必须是一维的。图像通常是二维的,需要使用ravel()函数将图像处理为一维数据源以后,再作为参数使用。
- BINS:BINS的具体值,表示灰度级的分组情况。
示例:
import cv2
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)
# 将二维数组转换为一维的
data = img.ravel()
print(data)
plt.hist(data, 256)
plt.show()
cv2.imshow('img', img)cv2.waitKey()
cv2.destroyAllWindows()# 输出结果
[127 123 125 ... 102 95 97]
2. 使用OpenCV绘制直方图:
在OpenCV提供了函数cv2.calcHist()用来计算图像的统计直方图,该函数能够统计图像内各个灰度级的像素点个数。然后,利用matplotlib.pyplot模块中的plot()函数,可以将函数cv2.calcHist()的统计结果绘制成直方图。
- 用cv2.calcHist()函数统计图像直方图信息
- hist=cv2.calcHist(images,channels,mask,histSize,ranges,accumulate)
- hist:返回的统计直方图,是一个一维数组,数组内的元素是各个灰度级的像素个数。
- images:原始图像,该图像需要使用“[]”括起来。
- channels:指定通道编号。通道编号需要用“[]”括起来,如果输入图像是单通道灰度图像,该参数的值就是[0]。对于彩色图像,它的值可以是[0]、[1]、[2],分别对应通道B、G、R。
- mask:掩模图像。当统计整幅图像的直方图时,将这个值设为None。当统计图像某一部分的直方图时,需要用到掩模图像。
- histSize:BINS的值,该值需要用“[]”括起来。例如,BINS的值是256,需要使用“[256]”作为此参数值。
- ranges:即像素值范围。例如,8位灰度图像的像素值范围是[0,255]。
- accumulate:累计(累积、叠加)标识,默认值为False。如果被设置为True,则直方图在开始计算时不会被清零,计算的是多个直方图的累积结果,用于对一组图像计算直方图。该参数允许从多个对象中计算单个直方图,或者实时更新直方图。该参数是可选的,一般情况下不需要设置。
import cv2img = cv2.imread('../boat.512.tiff', 0)
hist = cv2.calcHist([img], [0], None, [256],[0, 255])
print(hist)
print(type(hist))
print(hist.shape)
print(hist.size)# 输出结果
[[7.000e+00][7.000e+00]......[0.000e+00][0.000e+00]]
<class 'numpy.ndarray'>
(256, 1)
256
- 使用plot()函数来绘制图像的统计直方图:
使用matplotlib.pyplot模块内的plot()函数,将函数cv2.calcHist()的返回值绘制为图像直方图
import cv2
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)
hist = cv2.calcHist([img], [0], None, [256],[0, 255])plt.plot(hist,color='b')
plt.show()
cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()
补充: 学习plot()函数的其他用法:
示例1: 将给定的x=[0,1,2,3,4,5,6],y=[0.3,0.4,2,5,3,4.5,4],使用plot()函数绘制出来。
import matplotlib.pyplot as pltx = [0, 1, 2, 3, 4, 5, 6]
y = [0.3, 0.4, 2, 5, 3, 4.5, 4]
plt.plot(x, y)
plt.show()
图中x轴由x=[0,1,2,3,4,5,6]指定,y轴由y=[0.3,0.4,2,5,3,4.5,4]指定。
示例2: 给定y=[0.3,0.4,2,5,3,4.5,4],使用plot()函数将其绘制出来,观察绘制结果。
import matplotlib.pyplot as plty = [0.3, 0.4, 2, 5, 3, 4.5, 4]
plt.plot(y)
plt.show()
从图中可以看出,在使用plot()函数时,如果仅仅指定一个参数,则其对应x轴的值默认是一个自然数序列x=[0,1,… ,n−1,n]。自然序列x的长度与y的长度保持一致。
示例3: 使用plot()函数将两组不同的值a=[0.3,0.4,2,5,3,4.5,4], b=[3,5,1,2,1,5,3]以不同的颜色绘制出来。
import matplotlib.pyplot as plta = [0.3, 0.4, 2, 5, 3, 4.5, 4]
b = [3, 5, 1, 2, 1, 5, 3]
plt.plot(a, color='b')
plt.plot(b, color='g')plt.show()
示例4: 使用函数plot()和函数cv2.calcHist(),将彩色图像各个通道的直方图绘制在一个窗口内。
import cv2
import matplotlib.pyplot as pltimg = cv2.imread('../lena512color.tiff')
hist_b = cv2.calcHist([img], [0], None, [256], [0, 255])
hist_g = cv2.calcHist([img], [1], None, [256], [0, 255])
hist_r = cv2.calcHist([img], [2], None, [256], [0, 255])plt.plot(hist_b, 'b')
plt.plot(hist_g, 'g')
plt.plot(hist_r, 'r')
plt.show()cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()
3. 使用掩码绘制直方图:
import cv2
import matplotlib.pyplot as plt
import numpy as npimg = cv2.imread('../lena512color.tiff')mask = np.zeros(img.shape[:2], np.uint8)
mask[200: 400, 200: 400] = 255hist_b = cv2.calcHist([img], [0], mask, [256], [0, 255])
hist_g = cv2.calcHist([img], [1], mask, [256], [0, 255])
hist_r = cv2.calcHist([img], [2], mask, [256], [0, 255])plt.plot(hist_b, 'b')
plt.plot(hist_g, 'g')
plt.plot(hist_r, 'r')
plt.show()rst = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
三、直方图均衡化:
为什么要直方图均衡化?
如果一幅图像拥有全部可能的灰度级,并且像素值的灰度均匀分布,那么这幅图像就具有高对比度和多变的灰度色调,灰度级丰富且覆盖范围较大。在外观上,这样的图像具有更丰富的色彩,不会过暗或过亮。
如上图直方图均衡化前后的对比
在OpenCV的官网上,对图像均衡化前后的直方图进行了对比,如图所示。其中,左图是原始图像的直方图,可以看到灰度级集中在中间,图像中没有较暗和较亮的像素点;右图是对原图均衡化后的直方图,像素分布更均衡。
什么是直方图均衡化?
直方图均衡化的主要目的是将原始图像的灰度级均匀地映射到整个灰度级范围内,得到一个灰度级分布均匀的图像。这种均衡化,既实现了灰度值统计上的概率均衡,也实现了人类视觉系统(Human Visual System,HVS)上的视觉均衡。
例如:在某幅图像内仅仅出现了1、2、3、101、102、103等6个像素值,其分布分别如表中的情况A和情况B所示。
下面分别讨论这两种情况下像素值均衡的情况。
- 情况A:每一个灰度级在图像内出现的次数都是1,灰度级均匀地映射到当前的灰度级范围内,所以可以理解为其直方图是均衡的。
- 情况B:灰度级1、2、3出现的次数都是1次,灰度级103出现的次数是3次,灰度级101、102出现的次数是0次。从表面上看,灰度级是不均衡的。但是,从HVS的角度来说,人眼的敏感度不足以区分1个像素值的差异,即人眼会将灰度级1、2和3看作是相同的,会将灰度级101、102和103看作是相同的。也就是说,HVS会自动地将灰度级划分为两组,灰度级[1,3]为一组,灰度级[101,103]为另一组。在整幅图像内,这两组的灰度级出现的次数都是3次,概率是相等的。在均衡化处理中,综合考虑了统计概率和HVS的均衡。
1. 直方图均衡化的原理:
直方图均衡化的算法主要包括两个步骤:
(1)计算累计直方图。
(2)对累计直方图进行区间转换
在此基础上,再利用人眼视觉达到直方图均衡化的目的。
下面我们通过一个例子进行讲解。例如,图像A,它是一幅3位的位图,即共有8(23)个灰度级,有49个像素。
图像A共有8个灰度级,范围为[0,7],计算其统计直方图如表所示:
在此基础上,计算归一化统计直方图,计算方式是计算每个像素在图像内出现的概率。出现概率=出现次数/像素总数,用每个灰度级的像素个数除以总的像素个数(49),就得到归一化统计直方图。
接下来,计算累计统计直方图,即计算所有灰度级的累计概率。
在累计直方图的基础上,对原有灰度级空间进行转换。可以在原有范围内对灰度级实现均衡化,也可以在更广泛的灰度空间范围内对灰度级实现均衡化。下面分别介绍这两种形式。
(1) 在原有范围内实现均衡化:
在原有范围内实现直方图均衡化时,用当前灰度级的累计概率乘以当前灰度级的最大值7,得到新的灰度级,并作为均衡化的结果。
- 原始图像A中的灰度级0,经直方图均衡化后调整为新的灰度级1(即均衡化值1)。在原始图像A中,灰度级0共有9个像素点,所以在均衡化后的图像中,灰度级1共有9个像素点。
- 原始图像A中的灰度级1和2经直方图均衡化后调整为灰度级3。在原始图像A中,灰度级1共有9个像素点,灰度级2共有6个像素点,所以在均衡化后的图像中,灰度级3共有9+6=15个像素点。
- 原始图像A中的灰度级3经直方图均衡化后调整为灰度级4。在原始图像A中,灰度级3共有5个像素点,所以在均衡化后的图像中,灰度级4共有5个像素点。
- 原始图像A中的灰度级4和5经直方图均衡化后调整为灰度级5。在原始图像A中,灰度级4共有6个像素点,灰度级5共有3个像素点,所以在均衡化后的图像中,灰度级5共有6+3=9个像素点。
- 原始图像A中的灰度级6经直方图均衡化后调整为灰度级6。在原始图像A中,灰度级6共有3个像素点,所以在均衡化后的图像中,灰度级6共有3个像素点。
- 原始图像A中的灰度级7经直方图均衡化后调整为灰度级7。在原始图像A中,灰度级7共有8个像素点,所以在均衡化后的图像中,灰度级7共有8个像素点。
经过均衡化处理后,灰度级在整个灰度空间内的分布会更均衡。
直方图均衡化前后的对比图。其中,左图是均衡化之前的直方图,右图是均衡化之后的直方图。
这里的均衡化是综合考虑了统计概率和HVS的结果。
- 在图像A中,未进行直方图均衡化之前:灰度级0 ~ 3之间的像素个数为29个,灰度级4 ~ 7之间的像素个数为20个。
- 对图像A进行直方图均衡化之后:灰度级0 ~ 3之间的像素个数为24个,灰度级4 ~ 7之间的像素个数为25个。
通过上述比较,可以看出,直方图均衡化之后图像的灰度级分布更均衡了。
(2) 在更广泛的范围内实现均衡化:
在更广泛的范围内实现直方图均衡化时,用当前灰度级的累计概率乘以更广泛范围灰度级的最大值,得到新的灰度级,并作为均衡化的结果。
例如,要将灰度级空间扩展为[0,255]共256个灰度级,就必须将原灰度级的累计概率乘以255,得到新的灰度级。如表所示的是图像A在新的灰度级空间[0,255]内的新的灰度级。
经过均衡化处理后,图像A的灰度级在新灰度空间[0,255]内保持均衡。
通过上述分析可知,通过如下两个步骤, 可以让直方图达到均衡化的效果。
-
计算累计直方图。
-
将累计直方图进行区间转换。
下面我们用数学表达式描述以上的直方图均衡化过程。假设图像中像素的总数是N,图像的灰度级数是L,灰度级空间是[0,L-1],用nkn_knk表示第k级灰度(第k个灰度级,灰度值为k)在图像内的像素点个数,那么该图像中灰度级为rkr_krk(第k个灰度级)出现的概率为:
根据灰度级概率,对其进行均衡化处理的计算公式为:
式中,表示累计概率,将该值与灰度级的最大值L-1相乘即得到均衡化后的新灰度级(像素值)。
直方图均衡化使图像色彩更均衡、外观更清晰,也使图像更便于处理,它被广泛地应用在医学图像处理、车牌识别、人脸识别等领域。
2. 直方图均衡化处理:
在OpenCV中通过函数cv2.equalizeHist()来实现直方图均衡化。该函数的具体语法格式为:
- dst = cv2.equalizeHist(src)
- src:8位单通道原始图像
- dst:直方图均衡化处理的结果。
import cv2
import matplotlib.pyplot as pltimg = cv2.imread('../lena_dark.bmp', 0)
equ = cv2.equalizeHist(img)cv2.imshow('img', img)
cv2.imshow('rst', equ)plt.hist(img.ravel(), 256)
plt.hist(equ.ravel(), 256)
plt.show()cv2.waitKey()
cv2.destroyAllWindows()
在直方图均衡化之前,图像整体比较亮;均衡化以后,图像的亮度变得比较均衡。而两幅图像的直方图的对比,则不太明显。这实际上体现了,均衡化是指综合考虑了统计概率和HVS的结果。