当前位置: 首页 > news >正文

【数字图像处理】小白也能懂,最浅显方式手撕直方图均衡化(附python实现)

文章目录

  • 1 概念
  • 2 原理
    • 2.1 数学原理
  • 3 python代码实现
  • 4 测试效果
  • 5 结论

1 概念

直方图均衡化,同伽马变换一样,也是增强图像对比度的一种工具。区别在于,直方图均衡化是一种自适应的工具,即自动工具。也就是说,我们只需要将一张待处理的图片丢给这个工具,就可以实现对比度的增强,而无需像伽马变换一样需要手动调整一个输入参数。

什么是直方图呢?这里以很简单的方式来解释。我们可以将直方图视为图形或绘图,它可以用来从而可以总体了解图像的强度分布。其x轴是像素的等级(根据图像的类型判断,大部分情况是0到255),而y轴是相应像素值的数量。换言之,其实就是一个统计图,用于统计图片中像素值为x的像素点有多少个。

如下是一个直方图的例子,这个图片是彩色的,但实际上是先将该图转化为对应的灰度图像再绘制的直方图。其实想要对彩色图像做直方图均衡化也很简单,只要分别对三个通道单独做一次,再将三个结果组合在一起即可。

![[Pasted image 20240930173713.png]]

2 原理

2.1 数学原理

直方图均衡化其实会用到一些简单的概率论知识。我尽量以比较通俗的方法来叙述,所以其中的一些表述可能不是很严谨,不过仅用于理解本例是足够了。(如果你对概率论比较了解可以直接看公式掠过废话)

ps:以下仅为理论部分的叙述,实际代码实现时还会有一些不同的地方,但其实本质是一样的,只不过先熟悉原理会更好理解。

首先第一步,肯定就是要统计出所有像素值分别有多少个,这个其实就是一个图像遍历的过程。

做完上一步后,我们需要求取每一个像素值的概率密度函数(PDF),什么是概率密度函数呢?其实很好理解,就是图像中像素值为x的点的个数对于图像像素点总数的比重。因为这里是离散的情况,所以非常好求。假设像素值为k的点在图像(假设图像总共有MN个像素)中有r个,那么该点的概率密度函数,或者说这些点所占据的个数比重就是 P D F = r M N PDF={r\over{MN}} PDF=MNr
之后还需要介绍一下累积分布函数(CDF)的概念,这个概念刚开始理解可能会不知所云,其实,跟PDF对照起来就很好理解。

PDF是针对一个特定的点计算的,也就是说是一对一的关系。
而CDF是指(在本例中),假如针对一个像素值x,求它的CDF,就是所有像素值≤x的点的PDF之和。
举一个例子,假设我现在要求像素值为5的PDF,即 P D F 5 PDF_5 PDF5 ,那么结果就是像素值为5的点在所有点中占据的比重。
而如果现在我要求 C D F 5 CDF_5 CDF5 ,根据上面的定义,结果就是所有像素值≤5的PDF之和。由于这里是离散的情况,且像素值的最低边界是0,所以这里计算起来并没有很复杂,无非就是
C D F 5 = P D F 0 + P D F 1 + P D F 2 + P D F 3 + P D F 4 + P D F 5 CDF_5=PDF_0+PDF_1+PDF_2+PDF_3+PDF_4+PDF_5 CDF5=PDF0+PDF1+PDF2+PDF3+PDF4+PDF5
不难知道,CDF是非递减的,也就是说, C D F x + 1 ≥ C D F X CDF_{x+1}≥CDF_X CDFx+1CDFX

得到CDF以后,就可以引出最核心的步骤了,也就是直方图均衡化的核心公式:
S x = ( L − 1 ) C D F x , x ∈ [ 0 , L − 1 ] S_x=(L-1)CDF_x\ ,\ \ \ \ x\in[0, L-1] Sx=(L1)CDFx ,    x[0,L1]
其中,L表示图像的像素等级数,对于256级的灰度图L就是256。 S x S_x Sx是映射的值,表示的是原图中像素值为x的点需要映射到这个值,其实就是一种变化关系。这个公式其实也跟概率论有关,如果不理解的话直接记忆即可,或者可以学习一下概率论相关的知识。

利用这个公式,将原图中的每个像素点进行映射输出以后,就得到了直方图均衡化的结果。可以发现,直方图均衡化其实是一种线性变化,所以这种方法也被叫做直方图线性变化。

3 python代码实现

上面说到,代码实现跟理论是由一些差别的。至于差别在哪,先上代码再说结论

def equalize_self(src_img):  """  :param src_img: 待处理图像  :return: 直方图均衡化后的新图像  """    height, width = src_img.shape[:2]  # 统计r_k  r_dict = [0 for i in range(256)]  for r in range(height):  for c in range(width):  r_dict[src_img[r, c]] += 1  # 计算累积分布函数(使用前缀和算法优化)  cdf = [0 for i in range(256)]  cdf[0] = r_dict[0]  for i in range(1, 256):  cdf[i] = cdf[i - 1] + r_dict[i]  # 计算映射关系,建立查找表  s = []  for i in range(256):  # 累积分布函数的最小值不一定是0,直接采取书上的公式会导致图片偏灰  # 对于每一个cdf,计算时将其减去cdf_min使最小的像素映射到0,得到的图片效果更接近库方法  s.append((cdf[i] - cdf[0]) * 255.0 / (height * width - cdf[0]))  # 查表修改原像素值  dst_img = np.zeros((height, width), dtype=np.uint8)  for r in range(height):  for c in range(width):  dst_img[r, c] = round(s[src_img[r, c]])  return dst_img

首先关注到s.append这一行,append内的参数其实就是上面所给的直方图均衡化的映射公式。

有趣的是,会发现这里的cdf[i]在参与运算时,还减去了cdf[0],这是为什么呢?其实,这是为了正确调整归一化的比例范围,确保映射结果的动态范围合理。

实际图像中的像素值并不总是从 0 开始,图像的最小像素值可能不是 0。因此,累积分布函数的最小值 C D F m i n CDF_{min} CDFmin也不一定是 0。

如果不减去 C D F m i n CDF_min CDFmin​,则累积分布函数 CDF 中的最小值不会映射为 0,而是映射为一个正值,导致图像中没有真正的黑色区域,会让图像看起来灰蒙蒙的,缺少足够的对比度。因此,通过减去 C D F m i n CDF_{min} CDFmin​,我们可以将图像中的最小灰度值映射为 0,从而增强对比度,最大化使用图像的动态范围。

注意到为什么这一项还除上了一串东西,其实这是一个运算简化的过程。在前面计算CDF时,我并没有先计算PDF,这是因为PDF都会除以一个公共项,那就是图像的像素总数。因此我们可以先算分子的部分,最后只进行一次除法就可以了。不过由于上述我们减去 C D F m i n CDF_{min} CDFmin,累积分布函数的起点变成 0,但其终点也相应地缩小了。因此,整个映射范围也需要缩小,以确保新的 CDF 被正确地归一化到 0 和 255 之间,即像素总数也要减去 C D F m i n CDF_{min} CDFmin

还有一个值得提的点,计算CDF时,可以使用前缀和算法优化,这样可以帮我们省去很多的重复计算,关于前缀和算法可以自行查阅相关资料了解。

4 测试效果

下面给出几组测试案例。每一组图像第一行是原图,第二行是上述实现的直方图均衡化处理,第三行是使用OpenCV提供的直方图均衡化方法处理,右侧绘制出了对应的直方图。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 结论

可以看到,直方图均衡化适用于处理一些灰度分布较集中的图像。通过直方图均衡化可以动态拉伸图像的灰度范围,并通过这种方式增强图像的对比度。如果待处理图像的灰度比较集中,或者是看上去对比度很低,不妨可以试试用直方图均衡化处理。

相关文章:

  • python 02 List
  • 药物临床试验机构备案信息数据库查询方法(支持数据下载)
  • Git常用方法——详解
  • 防止电脑电池老化,禁止usb或者ac接口调试时充电
  • STM32CubeMX工程printf问题
  • 什么是 Angular 开发中的 Dumb components
  • 【Git】克隆主项目,并同时克隆所有子模块
  • 动态规划(3)——dp多状态问题Ⅰ
  • 【Rockchip系列】importbuffer_T 接口
  • Tomcat服务与运用
  • kafka测试
  • SpringAOP学习
  • 企业微信群发工具:精准营销与高效沟通的新篇章
  • [云服务器15] 全网最全!手把手搭建discourse论坛,100%完成
  • Oracle Data Guard备库清理归档脚本
  • [PHP内核探索]PHP中的哈希表
  • 〔开发系列〕一次关于小程序开发的深度总结
  • JavaScript对象详解
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • Vue实战(四)登录/注册页的实现
  • 服务器从安装到部署全过程(二)
  • 观察者模式实现非直接耦合
  • 每天一个设计模式之命令模式
  • 如何利用MongoDB打造TOP榜小程序
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 微信支付JSAPI,实测!终极方案
  • 学习使用ExpressJS 4.0中的新Router
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • 用Python写一份独特的元宵节祝福
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • # 飞书APP集成平台-数字化落地
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • (007)XHTML文档之标题——h1~h6
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)(2.10) LTM telemetry
  • (BFS)hdoj2377-Bus Pass
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (Windows环境)FFMPEG编译,包含编译x264以及x265
  • (第30天)二叉树阶段总结
  • (六)Flink 窗口计算
  • (算法)N皇后问题
  • (算法)区间调度问题
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (五十)第 7 章 图(有向图的十字链表存储)
  • (转)负载均衡,回话保持,cookie
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET CORE Aws S3 使用
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .Net 垃圾回收机制原理(二)