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

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

举个例子:

import cv2
from matplotlib import pyplot as pltimg = 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()

图1 原图及对应的直方图

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

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

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

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

图2 像素范围拉伸

1.直方图均衡化

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

可参考:

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

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

现在我们开始直接举例:

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

图3 灰度值

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

表1 灰度值的频率统计

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

表2 累积分布函数值

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

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

本例中,

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

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

用灰度78进行实验:

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

图4 均衡化后的灰度

注:最小值52变为了0,最大值154变为了255.

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

代码实现:

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

from cv2 import cv2
import numpy as np
from matplotlib import pyplot as pltimg = 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()

图6 原图及对应的直方图与累积分布图

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

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

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

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

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

图7 原图像的hist数组

hist数组:序号表示的是灰度,数值表示灰度的个数.

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

图8 原图像的cdf函数值

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

A1:使用numpy

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

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

图9 Numpy掩膜数组

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

第二步:算法公式

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

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

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

图10 映射后的灰度值

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

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

Q1:该怎么做?

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

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

图11 赋值0

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

img2 = cdf[img]  # img为原图像

这个不知道大家理不理解,其实img的灰度值 成了cdf的索引,然后重新生成一幅新的图像(也就是均衡化后的图像).

最后写一个代码:

import cv2
import numpy as np
from matplotlib import pyplot as pltimg = 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()

图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 npimg = cv2.imread("Origianl.jpg", 0)equ = cv2.equalizeHist(img)
res = cv2.hstack((img, equ))  #stacking images side-by-sidecv2.imshow("demo", res)cv2.waitKey()
cv2.destroyAllWindows()

效果如下:

图13 OpenCV的直方图均衡化

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

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

假设我们有一张图像

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

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

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

于是可以理解

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

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

其实在这里,我自己有一个疑问:区间

与区间
内的像素点总数是相等的,这点确实如此,但是,这跟

积分求和,好像没有多大的关系啊,在数学中,积分就是求面积(无穷个小矩形的面积相加,这是比较好理解的),在物理中,拿速度与时间进行举例,积分就是路程(无穷个小区间的路程相加,就是整个区间的路程),而本题中,

,这个我不清楚它代表的是什么?好像没有实际的意义.

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

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

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

便可以解得:

离散形式得:

图13 直方图均衡化示意图

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

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

参考:

李新春:直方图均衡化​zhuanlan.zhihu.com

ybai62868:说说直方图均衡化​zhuanlan.zhihu.com

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

  1. python中execute函数_在excel中调用python函数

    效果: 通过excel引用在py文件中写好的load_settle()函数,可以快捷的获取对应的历史结算价. 使用方法: 1.首先安装office,我用的是2016版本. 2.安装python,推荐使 ...

  2. VS2017 QT/C++ 调用python函数传图像

    原文:VS2019 C++ 调用python函数/类对象的方法_ 蜗牛在听雨的博客-CSDN博客_c++调用python函数 1.c++调用python类(传图像参数) ,编译出错,解决方法: 因为需 ...

  3. c调用python脚本如何获取结果_使用C++调用Python代码的方法详解

    一.配置python环境问题 1.首先安装Python(版本无所谓),安装的时候选的添加python路径到环境变量中 安装之后的文件夹如下所示: 2.在VS中配置环境和库 右击项目->属性-&g ...

  4. C++调用Python函数

    From: http://www.flatws.cn/article/program/c/2010-08-24/9677.html Python代码在实现某些功能的时候非常方便,如果能够将Python ...

  5. C++和Python混合编程:C++调用Python函数

    文章目录 一.C++直接运行python代码的控制台Demo 二.环境配置 三.C++调用Python函数 C++传入Python的参数格式转换 C++调用Python[有参有返回值]函数 C++运行 ...

  6. Excel单元格使用xlwings包调用python函数的公式,截取子网页(标题)的试验 问题求助CSDN

    Excel单元格使用xlwings包调用python函数的公式,截取子网页(标题)的试验 问题求助CSDN Python 环境:python3.7 的conda上的py3环境 Excel 2010 E ...

  7. C++回调函数中调用Python函数出现的死锁问题调试及解决

    一.查找死锁原因: 1.使用gdb exe指令进入gdb命令行,再输入r运行可执行文件 gdb /home/sdhm/catkin_ws/devel/lib/gpd_ros/gpd_server GN ...

  8. python函数默认参数位置_二十二、Python函数参数类型(位置、关键字、默认、不定长参数)...

    调用函数时可使用的参数类型 在调用Python函数时可使用的参数类型主要有以下几种: 必要参数(位置参数) 关键字参数 默认参数 不定长参数 必要参数(位置参数) 在Python中, 必要参数必须以正 ...

  9. html怎么调用python,使用HTML调用python函数

    我在python中有一个显示名称列表的函数.使用HTML调用python函数 def search(): with open('business_ten.json') as f: data=f.rea ...

  10. 实践在C++中调用Python函数

    目标 目标是在C++中调用Python函数,给定输入,得到输出. 主要参考: 如何实现 C/C++ 与 Python 的通信? - 知乎 C++调用Python脚本中的函数 - Achimesir - ...

最新文章

  1. js中的装饰器执行顺序
  2. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php
  3. django链接mysql网页显示数据_使用Django连接mysql数据库并显示在网页上
  4. Android之Unexpected error while executing: am start -n “***.Activity“-a android.intent.action.MAIN
  5. 腾讯再发股票吸引人才,受益2.57万名员工,人均超8万港元
  6. PHP5与MySQL数据库操作
  7. 微服务架构的 10个 最佳实践 !
  8. python的datetime模块用法_Python3.5内置模块之time与datetime模块用法实例分析
  9. sql server 2008安装的时候选NT AUTHORITY\NEWORK SERVICE 还是选 NT AUTHORITY\SYSTEM ?
  10. Learn OpenGL(三)——顶点着色器(Vertext Shader)
  11. 2016北京集训测试赛(十三) Problem B: 网络战争
  12. [zz]世界第一只计算机bug和Debug
  13. 人工智能、机器学习、神经网络、深度学习之间的关系
  14. 计算机网络试题及答案(史上最全)
  15. java做一个客房管理系统定制_基于JAVA的酒店客房管理系统的设计与实现
  16. 协同级CRM能帮助企业带来哪些管理提升?
  17. 为什么会有这么多中间表?
  18. 【VMware vSphere】Veeam备份
  19. 淘宝客微信机器人搭建教程分享
  20. 零基础入门学习HTML(下)

热门文章

  1. 深入浅出Spring Boot,你和大神的差距,就只有这份文档的距离
  2. Ansible script模块使用示例
  3. 安装Putty远程终端连接工具
  4. docker-compose执行报错(selinux所致):write /proc/self/attr/keycreate: permission denied
  5. 【视频】vue指令之@click及其stop修饰符
  6. python3 import导入模块
  7. python编程少儿游戏编程_少儿编程课堂|python – 用游戏学编程
  8. android os于8.1区别,Android-x86 8.1-rc2发布 运行于x86 PC上的安卓OS
  9. Qt三种方式实现FTP上传功能
  10. go读取excel_Excelize 2.3.0 发布,Go 语言 Excel 文档基础库