不调用python函数实现直方图均衡化_直方图均衡化(HE)

前面我们已经讲过图像的直方图,那图像的直方图均衡化又是干嘛的呢?

顾名思义:其实对直方图进行均衡化,哈哈感觉自己说的就是废话...

举个例子:

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
图1 原图及对应的直方图

观察图1:原图的亮暗对比不明显,感性上,感觉整幅图像就是灰蒙蒙的.

通过绘制原图的灰度直方图,可以看到,像素基本集中在100~200之间,其它的几乎没有,因此可理性判断,该图像不能够很好的突出细节信息.

Q1: 怎么才能让图像看起来层次比较分明呢?(包含细节信息)

A1: 需要对灰度值进行相关的处理(各种均衡化算法),使图像的像素分布尽可能广泛,改善图像的对比度.

b49de4a1edc8cb40a8e74f31ca501924.png
图2 像素范围拉伸

1.直方图均衡化

首先需要明白一个概念:累积分布函数(CDF : Cumulative Distribution Function)

可参考:

0704:概率分布函数、概率密度函数​zhuanlan.zhihu.com

假设你明白了,累积分布函数的相关概念.

现在我们开始直接举例:

下图是一个8 x 8 的 灰度图像的灰度值.

3a38c9319f0ae910904d191f2dc04d22.png
图3 灰度值

对灰度值的出现次数进行统计:

f271958e1012e71d27f23009ed4b5efb.png
表1 灰度值的频率统计

更进一步:计算累积分布函数值,为简化,累积分布函数值为0的灰度值被省略.

8f138d51e860466735d29e7555ab828c.png
表2 累积分布函数值

直方图均衡化的公式如下:

注:式(1) 中的round()是一个函数
a: 为整数,round(a)不变;
a: 为浮点数,round(a)圆整到离它最近的整数
a: 为浮点数,且距离两个整数一样近时,圆整到偶数.
比如:a = 1.5, 则round(a) = 2

本例中,

注:L为图像的灰度级数,图像为灰度图,所以位深度为8,故灰度级数共有2^8 = 256 级数.

均衡化后的灰度是多少呢?

用灰度78进行实验:

依次类推:可计算出原图中每个灰度均衡化后的灰度,如图4所示:

210f5f78f2a5067e1612561ff4ff99d0.png
图4 均衡化后的灰度
注:最小值52变为了0,最大值154变为了255.

fa0e5d52233cc9f97b21770e782d003e.png
图5 原始图像与均衡化后图像的对比图

代码实现:

① 原始图像的直方图及累积函数图像:

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
图6 原图及对应的直方图与累积分布图

我们可以看出来直方图大部分在灰度值较高的部分,而且分布很集中。而 我们希望直方图的分布比较分散,能够涵盖整个 x 轴。所以,我们就需要一个 变换函数帮助我们把现在的直方图映射到一个广泛分布的直方图中。这就是直方图均衡化要做的事情。

② 原始图像均衡化后的直方图与累积函数分布图

为便于与直方图均衡化算法原理相结合,我们分为以下几步来实验.

第一步:看一下原图像直方图纵坐标值

我们用程序跑一下,显示原图像直方图纵坐标值(也就是上面代码中hist数组里的值),如下图7所示:

885e2196bb3733306cdd416f8148d3ff.png
图7 原图像的hist数组
hist数组: 序号表示的是灰度, 数值表示灰度的个数.

同样:我们可以看原图像累积函数cdf的值,如下图8所示:

82ce0deea7259cf469b3adf110aec474.png
图8 原图像的cdf函数值

Q1:cdf中含有大量的0值,我们上述的算法是要忽略0,那怎么办呢?

A1:使用numpy

# 构建 Numpy 掩膜数组,cdf 为原数组,当数组元素为 0 时,掩盖(计算时被忽略).
cdf_m = np.ma.masked_equal(cdf,0) 

使用这个函数,效果如何呢?如图9所示:

13f7abee9e4c3c7915f7581761e8162d.png
图9 Numpy掩膜数组

注:计算时,0会被忽略.

第二步:算法公式

其实比较容易实现,如下:

cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min()) 

可以得到映射完后的灰度值,如图10所示:

4e984c3b15e8bdbafad93f21b128b41e.png
图10 映射后的灰度值

第三步:对掩盖的元素重新赋值0

图10所显示映射后的灰度值,需对被掩盖的元素重新赋值0

Q1:该怎么做?

# 对被掩盖的元素赋值,这里赋值为 0 
cdf = np.ma.filled(cdf_m, 0).astype('uint8')  # 数据类型为:uint8

使用这个函数后,效果如图11所示:

4d171150042aeeb530b4cc6d204055ba.png
图11 赋值0

现在就获得了一个表(里面的值为映射后的灰度值),我们可以通过查表得知与输入像素对应的输出像素 的值。我们只需要把这种变换应用到图像上就可以了。

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
图12 均衡化后的图像与对应的直方图

参考:

https://bk.tw.lvfukeji.com/wiki/%E7%9B%B4%E6%96%B9%E5%9B%BE%E5%9D%87%E8%A1%A1%E5%8C%96​bk.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
图13 OpenCV的直方图均衡化

Q2: 大家有没有想过,为什么变换函数是形如CDF的形式,难道是凭空想出来的 ?

A2: 显然不是,下面我们一起来分析一下.

假设我们有一张图像

,其直方图分布
,我们想利用一个函数映射:
,将图像
变为图像
,即对图像
每个像素点施加
变换,就会得到图像
的直方图分布为
.

整个过程可按下图13的图示说明:图中右下方是

图像的灰度直方图分布,图中右上方是单调非线性变换函数
,左上方通过映射得到的图像
的直方图分布,其中有
,
.

于是可以理解

的作用是将
图像里面像素点灰度为
的全部变为
,那么则有:

上面公式可以理解为对应的区间内像素点总数不变. 为实现直方图均衡化,特殊地有:

其实在这里,我自己有一个疑问:区间
与区间
内的像素点总数是相等的,这点确实如此,但是,这跟
积分求和,好像没有多大的关系啊,在数学中,积分就是求面积(无穷个小矩形的面积相加,这是比较好理解的),在物理中,拿速度与时间进行举例,积分就是路程(无穷个小区间的路程相加,就是整个区间的路程),而本题中,
,这个我不清楚它代表的是什么?好像没有实际的意义.

不知道有没有人清楚,这个该怎么解释?
经查资料,其实这个积分可以理解为 概率,我们可以把灰度与灰度出现的次数(要进行相应的归一化处理),看成 概率密度函数,某一灰度出现的次数越多,代表它的概率密度越大(注意并不是概率越大,连续型随机变量,概率是要区间的,单说某一变量的概率并没有意义).

因为我们的目标是直方图均匀化,那么理想的有

,其中
:图像的像素点个数,
:灰度级数,灰度图像的位深为
. 那么可以得到:

便可以解得:

离散形式得:

b5c9d99d359b50b734490060a6a0fdf9.png
图13 直方图均衡化示意图

通过上面推导过程可以看出,映射函数

和CDF是密切存相关. 并且,推导过程在是连续上作的处理,而实际上图像灰度级为256,故是一种以离散情况近似连续分布,如果灰度级别足够高,产生的结果会是真正均匀分布的直方图,然而实际得到的直方图往往不是均匀分布的,而是近似均匀分布. 同时,假设A图像的灰度直方图变化剧烈,且某些灰度区间不存在像素点,这会造成CDF剧烈变化 ,从而导致最后产生的B图像的直方图也有较大的不均匀性. 但,实际运用过程中,得到的图像能近似均匀分布已经能达到比较好的对比度增强效果.

参考:

李新春:直方图均衡化​zhuanlan.zhihu.com
57efe5495d6794e4dc61146a06a4c018.png
ybai62868:说说直方图均衡化​zhuanlan.zhihu.com
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值