图解直方图均衡化及其Python实现

在理解直方图均衡化的过程中,参考了一些书籍和博客,让人困惑的是,笔者对于直方图的理解还是停留在表面,并没有深入理解其内涵。因此,本文拟结合图片对直方图的概念进行阐述,并给出其Python实现,最后对她背后所蕴含的一些科学思维,谈谈自己的一些看法。

什么是直方图?

对于一副灰度图像I,她的每一个像素点I(x,y)都有一个灰度值,一般情况下可能的灰度取值有2^8=256个(0,1,...,255)。如果我们统计出灰度值r在I中出现的次数n,并对其进行归一化(n/N,N是所有灰度值出现次数的总和),这样我们就可以得到像素r在I中出现的概率p(r)。如果对每一个可能的灰度取值r都做同样的处理,我们可以得到如图1左侧所示的概率分布曲线,该曲线就是我们常说的直方图。

图1 直方图均衡化目标

什么是直方图均衡化?

通常情况下一副图像I的直方图如图1左侧所示,每一个灰度值r出现的概率不是相等的,这样会导致图像的一些细节信息不够突出,而直方图均衡化就是对灰度值r进行如下变换s=T(r),使得变换后的灰度分布如图1右侧所示(也就是说,每一个灰度值出现的概率是想同的),这样能够发现一些原先肉眼很难发现的细节,如图2所示(读者可以自己体会下)。说到这里,一般也就结束了,但是我们真的理解了吗?如何更好的理解呢?下面简要介绍一下笔者的理解方式。

假设我们有四个灰度级a,b,c,d,做个类比的话可以将她们理解为四个描述情感的词喜、怒、哀、乐。一般情况下我们得到的直方图可认为是p(a)=0.5,p(b)=0.5,p(c)=0,p(d)=0,而均衡化后的直方图是p(a)=0.25,p(b)=0.25,p(c)=0.25,p(d)=0.25. 如果我们以均衡化前的词描述人的情感则只有喜、怒,而均衡化后的词则有喜、怒、哀、乐。试问,哪种情形能够描述人更细微的情感变化呢?这也是笔者认为直方图均衡化之后能够描述更多图像细节的原因。

更进一步,如果一般情况下我们得到的直方图是p(a)=0.4,p(b)=0.4,p(c)=0.1,p(d)=0.1,那这按我们的理解意味什么呢?意味着大部分情况用喜、怒描述人的情感,而哀、乐几乎不用,当然了,如果能够自由运用喜、怒、哀、乐来描述人的情感是最好的哦。

图解直方图均衡化及其Python实现

图2 直方图均衡化示意图(左图变换前,右图变换后)

直方图均衡化的数学原理

笔者曾经是个数学控,总是认为凡事都应该从数学的基本原理出发,然而运用数学推理得出我们期望的结论。然而此刻,觉得自己的想法从根本上是错误的。下面以直方图均衡化数学原理为例进行说明。

我们直方图均衡化的目标如图1所示,那么怎么做到呢?这中间的约束条件又是什么呢(世上没有绝对的自由)?

可以设r原始图像灰度级,s均衡化后图像灰度级,不失一般性可以假设r的取值范围为[0,1]。那么什么是必须满足的约束条件呢?均衡化前后灰度级的意义不能变,也就是说变换前灰度级的含义是由黑到白,那么变换后也应该如此。此外,最好变换前后灰度级的取值范围一致。为使变换后的灰度仍保持从黑到白的单一变化顺序,且变换范围与原先一致,以避免整体变亮或变暗。必须规定:

(1)变换函数T(r)在r属于[0,1]范围内是单值、单调增函数;

(2)对于r属于[0,1],s=T(r)也属于[0,1]

图解直方图均衡化及其Python实现

图3 直方图均衡化原理示意图

这两条规定是满足我们均衡化要求的数学化描述(如果要应用数学的话,就应该将我们要求、目标等转化为数学语言进行描述)。

直方图均衡就是通过灰度变换函数s=T(r)将原图像直方图p (r)改变为均匀分布的直方图p(s)。由图3可知,在满足两条规定的前提下,因为s=T(r)(也就是说,r确定,s确定),结合概率论可知p (r)dr=p(s)ds又因为直方图均衡化后,有:p(s) = 1, s属于[0,1],因此,ds=p(r)dr。两边取积分有

图解直方图均衡化及其Python实现

这样我们就找到了s、r之间的映射关系。

直方图均衡化Python实现

由s、r之间的映射关系

图解直方图均衡化及其Python实现

可知,对于一副输入灰度图像I,我们首先计算其p(r),然后计算每一个灰度级r对应的

def histeq(img,nbr_bins=256):
    """ Histogram equalization of a grayscale image. """
        # 获取直方图p(r)    imhist, bins = histogram(img.flatten(), nbr_bins, normed = True)
        # 获取T(r)    cdf = imhist.cumsum() # cumulative distribution function
    cdf = 255 * cdf /cdf[-1] 
    
    # 获取s,并用s替换原始图像对应的灰度值
    result = interp(img.flatten(),bins[:-1],cdf)
    
    return result.reshape(img.shape),cdf

一些思考

笔者以前有这样一种误解:以为所有的理论都可以从基本的数学原理推导得出,于是在碰到新问题的时候,总是试图搞清楚各个数学公式的来源及其物理含义,而忽略了这些数学运作背后的目的是什么!这样的一个缺点就是,今天能够理解的理论,过了几天就不理解了。

现在笔者的一些体会是,如果按照以下思路理解问题,能够起到事半功倍的效果(有点夸张的成分在里面):首先搞清楚问题的来源、其次知晓解决问题的目的是什么、然后将问题数学化描述(时刻注意约束条件)、最后求解该数学问题。

总结:通常情况下,如果我们能够将前三个步骤走完,最后的求解问题通过借助于现有软件、算法,应该是水到渠成的事。(本人非数学专业,对怎么求解一个数学问题,通常只关心输入、输出、以及约束条件)

参考文献:

《Programming Computer Vision with Python》

相关推荐