前面我们已经讲过图像的直方图,那图像的直方图均衡化又是干嘛的呢?
顾名思义:其实对直方图进行均衡化,哈哈感觉自己说的就是废话...
举个例子:
import cv2
from matplotlib import pyplot as plt
img = cv2.imread("src.jpg", 0)
plt.hist(img.ravel(), 256, [0, 256])
plt.savefig("histogram.jpg", dpi = 300, bbox_inches = "tight", pad_inches = 0)
# dpi : dot per inch
# bbox_inches: if 'tight', try to figure out the tight bbox of the figure.
# pad_inches: amount of padding[填充] around the figure when bbox_inches is 'tight'.
plt.show()
![54f466fc68fe0cf4a823b0f06478c975.png](https://img-blog.csdnimg.cn/img_convert/54f466fc68fe0cf4a823b0f06478c975.png)
观察图1:原图的亮暗对比不明显,感性上,感觉整幅图像就是灰蒙蒙的.
通过绘制原图的灰度直方图,可以看到,像素基本集中在100~200之间,其它的几乎没有,因此可理性判断,该图像不能够很好的突出细节信息.
Q1: 怎么才能让图像看起来层次比较分明呢?(包含细节信息)
A1: 需要对灰度值进行相关的处理(各种均衡化算法),使图像的像素分布尽可能广泛,改善图像的对比度.
![b49de4a1edc8cb40a8e74f31ca501924.png](https://img-blog.csdnimg.cn/img_convert/b49de4a1edc8cb40a8e74f31ca501924.png)
1.直方图均衡化
首先需要明白一个概念:累积分布函数(CDF : Cumulative Distribution Function)
可参考:
0704:概率分布函数、概率密度函数zhuanlan.zhihu.com假设你明白了,累积分布函数的相关概念.
现在我们开始直接举例:
下图是一个8 x 8 的 灰度图像的灰度值.
![3a38c9319f0ae910904d191f2dc04d22.png](https://img-blog.csdnimg.cn/img_convert/3a38c9319f0ae910904d191f2dc04d22.png)
对灰度值的出现次数进行统计:
![f271958e1012e71d27f23009ed4b5efb.png](https://img-blog.csdnimg.cn/img_convert/f271958e1012e71d27f23009ed4b5efb.png)
更进一步:计算累积分布函数值,为简化,累积分布函数值为0的灰度值被省略.
![8f138d51e860466735d29e7555ab828c.png](https://img-blog.csdnimg.cn/img_convert/8f138d51e860466735d29e7555ab828c.png)
直方图均衡化的公式如下:
注:式(1) 中的round()是一个函数
a: 为整数,round(a)不变;
a: 为浮点数,round(a)圆整到离它最近的整数
a: 为浮点数,且距离两个整数一样近时,圆整到偶数.
比如:a = 1.5, 则round(a) = 2
本例中,
注:L为图像的灰度级数,图像为灰度图,所以位深度为8,故灰度级数共有2^8 = 256 级数.
均衡化后的灰度是多少呢?
用灰度78进行实验:
依次类推:可计算出原图中每个灰度均衡化后的灰度,如图4所示:
![210f5f78f2a5067e1612561ff4ff99d0.png](https://img-blog.csdnimg.cn/img_convert/210f5f78f2a5067e1612561ff4ff99d0.png)
注:最小值52变为了0,最大值154变为了255.
![fa0e5d52233cc9f97b21770e782d003e.png](https://img-blog.csdnimg.cn/img_convert/fa0e5d52233cc9f97b21770e782d003e.png)
代码实现:
① 原始图像的直方图及累积函数图像:
from cv2 import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("src.jpg", 0)
hist, bins = np.histogram(img.ravel(), 256, [0, 256])
cdf = np.cumsum(hist) #计算累积函数值
cdf_normalized = cdf * max(hist) / max(cdf)
plt.plot(cdf_normalized, color = "r")
plt.hist(img.ravel(), 256, [0, 256])
plt.legend(("cdf", "histogram"), loc = "upper left")
plt.show()
![99fad867cfee2474b75853a74aee0321.png](https://img-blog.csdnimg.cn/img_convert/99fad867cfee2474b75853a74aee0321.png)
我们可以看出来直方图大部分在灰度值较高的部分,而且分布很集中。而 我们希望直方图的分布比较分散,能够涵盖整个 x 轴。所以,我们就需要一个 变换函数帮助我们把现在的直方图映射到一个广泛分布的直方图中。这就是直方图均衡化要做的事情。
② 原始图像均衡化后的直方图与累积函数分布图
为便于与直方图均衡化算法原理相结合,我们分为以下几步来实验.
第一步:看一下原图像直方图纵坐标值
我们用程序跑一下,显示原图像直方图纵坐标值(也就是上面代码中hist数组里的值),如下图7所示:
![885e2196bb3733306cdd416f8148d3ff.png](https://img-blog.csdnimg.cn/img_convert/885e2196bb3733306cdd416f8148d3ff.png)
hist数组: 序号表示的是灰度, 数值表示灰度的个数.
同样:我们可以看原图像累积函数cdf的值,如下图8所示:
![82ce0deea7259cf469b3adf110aec474.png](https://img-blog.csdnimg.cn/img_convert/82ce0deea7259cf469b3adf110aec474.png)
Q1:cdf中含有大量的0值,我们上述的算法是要忽略0,那怎么办呢?
A1:使用numpy
# 构建 Numpy 掩膜数组,cdf 为原数组,当数组元素为 0 时,掩盖(计算时被忽略).
cdf_m = np.ma.masked_equal(cdf,0)
使用这个函数,效果如何呢?如图9所示:
![13f7abee9e4c3c7915f7581761e8162d.png](https://img-blog.csdnimg.cn/img_convert/13f7abee9e4c3c7915f7581761e8162d.png)
注:计算时,0会被忽略.
第二步:算法公式
其实比较容易实现,如下:
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
可以得到映射完后的灰度值,如图10所示:
![4e984c3b15e8bdbafad93f21b128b41e.png](https://img-blog.csdnimg.cn/img_convert/4e984c3b15e8bdbafad93f21b128b41e.png)
第三步:对掩盖的元素重新赋值0
图10所显示映射后的灰度值,需对被掩盖的元素重新赋值0
Q1:该怎么做?
# 对被掩盖的元素赋值,这里赋值为 0
cdf = np.ma.filled(cdf_m, 0).astype('uint8') # 数据类型为:uint8
使用这个函数后,效果如图11所示:
![4d171150042aeeb530b4cc6d204055ba.png](https://img-blog.csdnimg.cn/img_convert/4d171150042aeeb530b4cc6d204055ba.png)
现在就获得了一个表(里面的值为映射后的灰度值),我们可以通过查表得知与输入像素对应的输出像素 的值。我们只需要把这种变换应用到图像上就可以了。
img2 = cdf[img] # img为原图像
这个不知道大家理不理解,其实img的灰度值 成了cdf的索引,然后重新生成一幅新的图像(也就是均衡化后的图像).
最后写一个代码:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("src.jpg", 0)
hist, bins = np.histogram(img.ravel(), 256, [0, 256])
cdf = np.cumsum(hist)
# 构建 Numpy 掩模数组,cdf 为原数组,当数组元素为 0 时,掩盖(计算时被忽略)。
cdf_m = np.ma.masked_equal(cdf,0)
# 均衡化公式
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
# 对被掩盖的元素赋值,这里赋值为 0
cdf = np.ma.filled(cdf_m,0).astype('uint8')
img2 = cdf[img]
hist2, bins2 = np.histogram(img2.ravel(), 256, [0, 256])
cdf2 = np.cumsum(hist2)
cdf_equal_normalized = cdf2 * max(hist2) / max(cdf2)
plt.hist(img2.ravel(), 256, [0, 256])
plt.plot(cdf_equal_normalized, "r")
cv2.imwrite("equal.jpg", img2)
plt.savefig("histogram.jpg", dpi = 300, bbox_inches = "tight")
plt.show()
![024675fa6b84151f1bb128ea3b27128c.png](https://img-blog.csdnimg.cn/img_convert/024675fa6b84151f1bb128ea3b27128c.png)
参考:
https://bk.tw.lvfukeji.com/wiki/%E7%9B%B4%E6%96%B9%E5%9B%BE%E5%9D%87%E8%A1%A1%E5%8C%96bk.tw.lvfukeji.com是不是感觉上面的直方图均衡化算法有点难,其实OpenCV提供了直方图均衡化算法.
equ = cv2.equalizeHist(img)
一个函数就搞定了,哈哈,说这么多,其实就是在说一个函数,相关的代码如下:
import cv2
import numpy as np
img = cv2.imread("Origianl.jpg", 0)
equ = cv2.equalizeHist(img)
res = cv2.hstack((img, equ)) #stacking images side-by-side
cv2.imshow("demo", res)
cv2.waitKey()
cv2.destroyAllWindows()
效果如下:
![1708d7aacb1bec400178589eb3d95665.png](https://img-blog.csdnimg.cn/img_convert/1708d7aacb1bec400178589eb3d95665.png)
Q2: 大家有没有想过,为什么变换函数是形如CDF的形式,难道是凭空想出来的 ?
A2: 显然不是,下面我们一起来分析一下.
假设我们有一张图像
整个过程可按下图13的图示说明:图中右下方是
于是可以理解
上面公式可以理解为对应的区间内像素点总数不变. 为实现直方图均衡化,特殊地有:
其实在这里,我自己有一个疑问:区间与区间
积分求和,好像没有多大的关系啊,在数学中,积分就是求面积(无穷个小矩形的面积相加,这是比较好理解的),在物理中,拿速度与时间进行举例,积分就是路程(无穷个小区间的路程相加,就是整个区间的路程),而本题中,内的像素点总数是相等的,这点确实如此,但是,这跟
,这个我不清楚它代表的是什么?好像没有实际的意义.
不知道有没有人清楚,这个该怎么解释?
经查资料,其实这个积分可以理解为 概率,我们可以把灰度与灰度出现的次数(要进行相应的归一化处理),看成 概率密度函数,某一灰度出现的次数越多,代表它的概率密度越大(注意并不是概率越大,连续型随机变量,概率是要区间的,单说某一变量的概率并没有意义).
因为我们的目标是直方图均匀化,那么理想的有
便可以解得:
离散形式得:
![b5c9d99d359b50b734490060a6a0fdf9.png](https://img-blog.csdnimg.cn/img_convert/b5c9d99d359b50b734490060a6a0fdf9.png)
通过上面推导过程可以看出,映射函数
参考:
李新春:直方图均衡化zhuanlan.zhihu.com![57efe5495d6794e4dc61146a06a4c018.png](https://img-blog.csdnimg.cn/img_convert/57efe5495d6794e4dc61146a06a4c018.png)