直方图均衡--对比度调整和去雾效果

  • Post author:
  • Post category:其他


基于直方图插值的局部自动对比度/色阶算法用于图像增强效果的试验。 – Imageshop

for each Tile in Image 
{
   Calcuate( HistGram);
   ClipHistGram(HistGram, ClipLimit);
   MakeMapping(HistGram);
}
for each Tile in Image 
    for each Pixel in Tile
        use bilinear interpolation between four adjacent HistGram info to generate new Pixel

原始的直方图均衡化算法的核心在上述 MakeMapping(HistGram)函数中得以体现,

void MakeMapping(int* Histgram)
{
    int I, Sum = 0, Amount = 0;
    for (I = 0; I < 256; I++) Amount += Histgram[I];
    for (I = 0; I < 256; I++)
    {
        Sum += Histgram[I];
        Histgram[I] = Sum * 255/ Amount;     // 计算分布
    }
}

上述Histgram[I] = Sum *  255 / Amount;   一句就是HE算法的核心,就直方图数据重新分布。

我们回顾一下PS的调整菜单,除了直方图均衡化是一键式菜单(即点击无可调参数界面,实际上直翻图均衡化还是有的,在有选区的情况下回弹出一个框),还有另外三个常用的一键操作,即:自动色阶、自动对比度以及自动颜色。从本质上讲,这三个算法同直方图均衡化一样,在内部也是一个直方图重新分布和像素重新映射的过程,因此,如果把这里的MakeMapping函数总映射过程替换他们三者中的某一种会是什么情况和效果呢, 这其实是了解了CLAHE算法的原理后,很自然的能拓宽和联想到的。

关于自动色阶和自动对比度的原理,我在

调整图像- 自动对比度、自动色阶算法

一文中已经有了较为详细的实现,而关于自动颜色的原理,目前为止我似乎没有发现有任何人对其进行了详细的解释。我在Imageshop中也只是做了一种简单的模拟,这里就不提了。

以自动色阶为例,上述MakeMapping函数的形式可能如下所示:

void MakeMapping(int* Histgram,float CutLimit=0.01)
{
    int I, Sum = 0, Amount = 0;
    const int Level = 256;
    for (I = 0; I < Level; I++) Amount += Histgram[I];
    int MinB,MaxB;
    for (I = 0; I < Level; I++)
    {
        Sum = Sum + Histgram[I];
        if (Sum >= Amount * CutLimit )
        {
            MinB = I;                              
            break;
        }
    }  
    Sum = 0;
    for(I = Level-1; I >= 0; I--)
    {
        Sum = Sum +Histgram[I];
        if (Sum >= Amount * CutLimit )
        {
            MaxB = I ;                            
            break;
        }   
    }
        
    if (MaxB!=MinB)
    {
        for (I = 0; I < Level; I++)
        {
            if (I<MinB)
                Histgram[I] =0;
            else if(I>MaxB)
                Histgram[I]=255;
            else
                Histgram[I] = 255* (I - MinB) / (MaxB - MinB) ;     
        }
    }
    else
    {
        for (I = 0; I < Level; I++) Histgram[I]=MaxB;        //     必须有,不然会有一些图像平坦的部位效果出错
    }
}

注意在这个函数里我增加了CutLimit参数,这个参数名和CLAHE的一样,实际上是因为自动色阶这种工作方式,实际上就是对直方图的一种裁剪,因此CLAHE算法的ClipHistGram过程就可以舍去了,而把CutLimit作为自动色阶的一个调节参数就是顺其自然的一个事情了。

在上述代码代码中,if (MaxB!=MinB)的判断主要是防止出现除以0的错误,同时在这种情况发生时,必须把直方图中的所有数据都设置为MaxB(其实这种情况发生时,原始直方图数据中必然是大部分都等于MaxB,但可能还是有部分是不同的,如果不赋值为MaxB,处理的结果图像中会出现莫名其妙的纹理图)。

把上述代码替换掉CLAHE算法中相应的部分,保持插值等代码不动,可获得如下效果:


原图    块大小为200,CutLimit =0.01 处理后结果

由上面的图可以看出,处理前后的增强效果还是很明显的,整个图像显得更清晰。

根据上述代码分析,这样处理的效果肯定是原先图像中的黑的部分更黑,白的部分更白,因此,对比度更加宣明。为了能控制整个对比度调节的程度,我们新增加一个参数,用来调节在最后隐射阶段的最大值。我这里做了如下的处理:

void MakeMapping(int* Histgram,float CutLimit=0.01,float Contrast = 1)
{
    int I, Sum = 0, Amount = 0;
    const int Level = 256;
    for (I = 0; I < Level; I++) Amount += Histgram[I];
    int MinB =0 ,MaxB=255;
    int Min = 0,Max=255;
    for (I = 0; I < Level; I++)
    {
        if  (Histgram[I]!=0) 
        {
            Min = I ;
            break;
        }
    }

    for(I = Level-1; I >= 0; I--)
    {
        if  (Histgram[I]!=0) 
        {
            Max = I ;
            break;
        }
    }
    for (I = 0; I < Level; I++)
    {
        Sum = Sum + Histgram[I];
        if (Sum >= Amount * CutLimit )
        {
            MinB = I;                              
            break;
        }
    }
  
    Sum = 0;
    for(I = Level-1; I >= 0; I--)
    {
        Sum = Sum +Histgram[I];
        if (Sum >= Amount * CutLimit )
        {
            MaxB = I ;                            
            break;
        }   
    }
    int Delta = (Max - Min) * Contrast * 0.5  ;
    Min = Min - Delta;
    Max = Max +    Delta ;
    if (Min    < 0) Min = 0;
    if (Max > 255) Max = 255;

    if (MaxB!=MinB)
    {
        for (I = 0; I < Level; I++)
        {
            if (I<MinB)
                Histgram[I] =Min;
            else if(I>MaxB)
                Histgram[I]=Max;
            else
                Histgram[I] = (Max-Min)* (I - MinB) / (MaxB - MinB) + Min ;     
        }
    }
    else
    {
        for (I = 0; I < Level; I++) Histgram[I]=MaxB;        //     必须有,不然会有一些图像平坦的部位效果出错
    }
}

首先分析获得原始块中的最大值和最小值,然后再这个的基础上按照设定的参数向黑和白两个方向同等程度扩展,这样就避免了无论什么情况下的分布都直接扩展到0-255内。有效的抑制了噪音的放大。

修改后,我们看看一些效果:



原图 CutLimit =0.01,Contrast=1           CutLimit =0.05,Contrast=1

分析:上面这幅图原始图像整体就比较亮,因此,在Contrast=1的时候,很多块调整后的Min=0,Max也等于255了,因此继续增加Contrast参数,图像的效果基本没有什么变化了。而增加CutLimit值使得图像的映射表由两个极端向中间靠拢,图像会稍微显得浓烈一些。

原图 CutLimit =0.01,Contrast=1           CutLimit =0.01,Contrast=2.5

而上面这幅图像,则由于整体比较暗,增加Contrast的效果就比较明显了,当Contrast=2.5,图像顶部的一些细节信息也能清晰的表达出来。

另外,分析原始代码的双线性插值部分可知,在四周边缘处,特备是离边缘小于TileX/2或小于TileY/2的部分,由于其临近信息的缺失,实际上是没有进行双线性插值的,这样对于部分图像,边缘处显得有点不自然,弥补的方式就是在处理前对图像进行扩展,分别向四周扩展TileX/2和TileY/2大小,当然扩展部分的数据需要按照镜像的方式填充数据。

在贴一些这个算法的处理效果:









(以上转自http://www.tuicool.com/articles/JRZRvu)

二、


一、自适应直方图均衡化(Adaptive histgram equalization/AHE)


1.简述


自适应直方图均衡化(AHE)用来提升图像的对比度的一种计算机图像处理技术。和普通的直方图均衡算法不同,AHE算法通过计算图像的局部直方图,然后重新分布亮度来来改变图像对比度。因此,该算法更适合于改进图像的局部对比度以及获得更多的图像细节。


不过,AHE有过度放大图像中相同区域的噪音的问题,另外一种自适应的直方图均衡算法即限制对比度直方图均衡(CLAHE)算法能有限的限制这种不利的放大。


2. 算法的解释


普通的直方图均衡算法对于整幅图像的像素使用相同的直方图变换,对于那些像素值分布比较均衡的图像来说,算法的效果很好。然后,如果图像中包括明显比图像其它区域暗或者亮的部分,在这些部分的对比度将得不到有效的增强。


AHE算法通过对局部区域执行响应的直方图均衡来改变上述问题。该算法首先被开发出来适用于改进航天器驾驶舱的显示效果。其最简单的形式,就是每个像素通过其周边一个矩形范围内的像素的直方图进行均衡化。均衡的方式则完全同普通的均衡化算法:变换函数同像素周边的累积直方图函数(CDF)成比例。


图像边缘的像素需要特殊处理,因为边缘像素的领域不完全在图像内部。这个通过镜像图像边缘的行像素或列像素来解决。直接复制边缘的像素进行扩充是不合适的。因为这会导致带有剑锋的领域直方图。


3. AHE的属性


  • 领域的大小是该方法的一个参数。领域小,对比度得到增强,领域大,则对比度降低。

  • 当某个区域包含的像素值非常相似,其直方图就会尖状化,此时直方图的变换函数会将一个很窄范围内的像素映射到整个像素范围。这将使得某些平坦区域中的少量噪音经AHE处理后过度放大。


二、限制对比度自适应直方图均衡(Contrast Limited Adaptive histgram equalization/CLAHE)


1.简述


CLAHE同普通的自适应直方图均衡不同的地方主要是其对比度限幅。这个特性也可以应用到全局直方图均衡化中,即构成所谓的限制对比度直方图均衡(CLHE),但这在实际中很少使用。在CLAHE中,对于每个小区域都必须使用对比度限幅。CLAHE主要是用来克服AHE的过度放大噪音的问题。


这主要是通过限制AHE算法的对比提高程度来达到的。在指定的像素值周边的对比度放大主要是由变换函数的斜度决定的。这个斜度和领域的累积直方图的斜度成比例。CLAHE通过在计算CDF前用预先定义的阈值来裁剪直方图以达到限制放大幅度的目的。这限制了CDF的斜度因此,也限制了变换函数的斜度。直方图被裁剪的值,也就是所谓的裁剪限幅,取决于直方图的分布因此也取决于领域大小的取值。


通常,直接忽略掉那些超出直方图裁剪限幅的部分是不好的,而应该将这些裁剪掉的部分均匀的分布到直方图的其他部分。如下图所示。



这个重分布的过程可能会导致那些倍裁剪掉的部分由重新超过了裁剪值(如上图的绿色部分所示)。如果这不是所希望的,可以不带使用重复不的过程指导这个超出的部分已经变得微不足道了。


2. 通过插值加快计算速度


如上所述的直接的自适应直方图,不管是否带有对比度限制,都需要对图像中的每个像素计算器领域直方图以及对应的变换函数,这使得算法及其耗时。


而插值使得上述算法效率上有极大的提升,并且质量上没有下降。首先,将图像均匀分成等份矩形大小,如下图的右侧部分所示(8行8列64个块是常用的选择)。然后计算个块的直方图、CDF以及对应的变换函数。这个变换函数对于块的中心像素(下图左侧部分的黑色小方块)是完全符合原始定义的。而其他的像素通过哪些于其临近的四个块的变换函数插值获取。位于图中蓝色阴影部分的像素采用双线性查插值,而位于便于边缘的(绿色阴影)部分采用线性插值,角点处(红色阴影处)直接使用块所在的变换函数。


这样的过程极大的降低了变换函数需要计算的次数,只是增加了一些双线性插值的计算量。


CLAHE算法的源代码参考:



View Code


上面的代码中对于各块之间的插值部分的编码技巧很值得学习和参考。


以上描述均翻译自:

http://en.wikipedia.org/wiki/CLAHE#Contrast_Limited_AHE


Karel Zuiderveld提供的代码:



View Code


应该修正为:



View Code


同时,各位也可以参考下matlab的adapthisteq.m文件,该文件的代码基本是严格按照 Karel Zuiderveld作者的原文写的,贴出如下:



View Code


参考上述代码,我分别用VB和C#实现了该算法,提供个编译好的文件给有兴趣研究该算法的朋友看看效果(不提供源代码的):



http://files.cnblogs.com/Imageshop/CLAHE.rar


截面如下:


其中AHE算法可以认为是裁剪限幅为1的CLAHE算法,CLHE是水平网格和垂直网格都为1的算法。


均衡分布方式和ALPHA的解释可参考matlab的代码.


CLAHE算法很多时候比直接的直方图均衡化算法的效果要好很多,比如:




原始图像                                                        普通的直方图均衡化                                                  CALHE


可以看出,在图像的上部,CALHE有效的抑制了噪音的增强。


再举一些例子看看效果





原始图像                                                        普通的直方图均衡化                                                  CALHE




原始图像                                                        普通的直方图均衡化                                                  CALHE









原始图像                                                        普通的直方图均衡化                                                  CALHE


对于彩色图像,matlab的那个函数不支持,我这里采用了两种方式处理,一种是各通道分开处理,一种是每个通道都放在一起,对于彩色的图像似乎放在一起处理的效果要比分开处理好很多。


比如界面中那个图像,如果各通道放在一起处理,效果如下:


而通道分开处理,则各通道的颜色不很匹配:


对于处理速度,这个函数由于只有一些分开+插值计算,速度很快。如果图像不能倍分成整块,一般需要扩充边缘,matlab的处理方式是上下左右四条边对称镜像扩充。这种方式很合理。


实例工程是用VB6编写的,由于VB不支持指针,在速度比C#之类的语言大概慢了50%左右。但是也还是很快的了。


2013.10.20 补充



这个函数的编码是需要一定的时间和能力的,为此,我用C++编制了一个DLL,并用C#给出了调用的过程,供有需要的朋友使用。

[DllImport("AdaptHistEqualize.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern void AdaptHistEqualize(byte *Scan0, int Width, int Height, int Stride,int TileX , int TileY , double CutLimit, bool SeparateChannel);


C++的速度是相当的惊人的,处理1024*768的图像时间在20-30ms以内。



C#示例代码下载:

http://files.cnblogs.com/Imageshop/AdaptHistEqualizeTest.rar

(转自http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html)