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

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直方图均衡化是一种用于图像增强的技术,它可以通过调整图像的灰度级分布来增强图像的对比度和亮度。直方图均衡化的基本思想是将图像的灰度级分布变成均匀分布,从而使得图像的对比度增强。在Python中,可以使用OpenCV、NumPy和Matplotlib等库来实现直方图均衡化。具体实现步骤如下: 1. 读取图像并将其转换为灰度图像。 2. 统计图像中各个灰度级的像素个数,得到原始图像的灰度直方图。 3. 计算每个灰度级的累积分布函数,并将其映射到新的灰度级上。 4. 使用新的灰度级对原始图像进行重映射,得到直方图均衡化后的图像。 下面是一个简单的Python代码示例,用于实现直方图均衡化: import cv2 import numpy as np # 读取图像并将其转换为灰度图像 img = cv2.imread('img.jpg') img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 统计图像中各个灰度级的像素个数,得到原始图像的灰度直方图 hist, bins = np.histogram(img_gray.ravel(), 256, [0, 256]) # 计算每个灰度级的累积分布函数,并将其映射到新的灰度级上 cdf = hist.cumsum() cdf_normalized = cdf * hist.max() / cdf.max() # 使用新的灰度级对原始图像进行重映射,得到直方图均衡化后的图像 img_eq = np.interp(img_gray.ravel(), bins[:-1], cdf_normalized).reshape(img_gray.shape) # 显示原始图像和直方图均衡化后的图像 cv2.imshow('Original Image', img_gray) cv2.imshow('Equalized Image', img_eq) cv2.waitKey(0) cv2.destroyAllWindows()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值