基于中值滤波或双边滤波方式的图像去雾效果的研讨。

一、前言 

 实际上很久以前,当我初次接触图像去雾技术时,最先实现的是基于中值滤波的图像去雾,并且也有一定的效果,在我的Imageshop的集成软件中的去雾方案就是这个的实现,不过那个效果没有本文好。

     而基于双边滤波的方案,也是很早就听说过,前不久有朋友传给我一篇国内的双边滤波去雾的论文,总体思路和基于中值的类似,想想干脆把这两个放在一起做个比较吧。

二、算法的流程

 算法的最基础的原理还是基于大气散射模型的,即:

  已知条件就是输入图像I(X),求J(x);

     在参考论文一种单幅图像去雾方法中是通过中值滤波的方式来去雾的,而论文基于双边滤波的实时图像去雾技术研究选用了双边滤波,如果你要实现代码,可能需要两篇论文结合起来看,因为在论文1中的描述没有讲清楚如何通过获得的雾浓度数据来获取无雾的图像。

     简单的来说,算法的流程可描述如下:

  1、定义F(X)=A(1-t(x)),称之为大气光幕或者为雾浓度。

     2、计算

,并使用和何博士论文中类似的方式计算全局大气光值A。

     3、计算

,即对M(x)进行中值滤波。

     4、计算

,注意式子中的绝对值。

     5、计算

,式中P为控制去雾程度的因子,取值范围[0,1]。

     6、通过式子

获得去雾后的图像。

     上面的很多算式是从不同论文里截图的,因此表达上有些前后不一致,但不影响高手理解其含义。

     如果是采用双边滤波算子,则步骤3和4中的median运算符需修改为bilaterfilter,其他的步骤一样。

  算法的原理我讲不清,反正看的越多越迷糊了。

三、算法的效果

   算法的效果还是有些意外,有些图获得了相当不错的效果。

  原图

去雾图

对应的大气光幕

  原图

去雾图

对应的大气光幕

  原图

去雾图

对应的大气光幕

  原图

去雾图

对应的大气光幕

  原图

去雾图

对应的大气光幕

  原图

去雾图

                           对应的大气光幕

   这里的大气光幕和何凯明的论文中的透射率图不是同一个概念,因此不具有可比性。

   在大气光幕的公式中,我们看到有全局大气光A的影响,但是上述计算F(X)的过程确没有涉及到A,很是无语啊。

   从效果上看,我所列举的这些例子都还是不错的,特别是第一幅图,用何凯明的暗通道我一直没有调出这种效果。

   上述都是用中值滤波做的效果,在部分图像对应大气光幕图上可以看出,图像的边缘处有一些小圆弧,这些都是矩形半径中值滤波的明显痕迹,而基于双边滤波的我也实践过,并没有像参考论文2说的那样有多少改进,感觉彼此彼此,而且有些图还会出现突变,因此我认为写这些论文纯粹是为了发论文。 

四、代码实现细节

    在代码实现上,个人感觉没有什么难点,先求暗通道,然后就是几个中值滤波或者是双边滤波,求全局大气光的过程还涉及到最小值滤波,主要的代码如下:

void _stdcall HazeRemovalBasedOnMedianBlur(unsigned char * Scan0, int Width,int Height,int Stride,int DarkRadius,int MedianRadius,int P)
{
    int  X, Y, Diff,Min,F;
    unsigned char* Pointer, *DarkP, *FilterP,* FilterPC;
    unsigned char * DarkChannel = (unsigned char*)malloc(Width * Height);
    unsigned char * Filter = (unsigned char*)malloc(Width * Height);
    unsigned char * FilterClone = (unsigned char*)malloc(Width * Height);

    for (Y = 0; Y < Height; Y++)
    {
        Pointer = Scan0 + Y * Stride;
        DarkP = DarkChannel + Y * Width;             // 由实际图像计算得到的图像暗通道     
        for (X = 0; X < Width; X++)
        {
            Min = *Pointer;
            if (Min > *(Pointer + 1)) Min = *(Pointer + 1);
            if (Min > *(Pointer + 2)) Min = *(Pointer + 2);
            *DarkP = (unsigned char)Min;
            DarkP++;
            Pointer += 3;
        }
    }
    memcpy(Filter, DarkChannel, Width * Height);                        // 求全局大气光A时会破坏DarkChannel中的数据

    MinValue(DarkChannel, Width, Height,Width,DarkRadius);                // 求取暗通道值

    // 利用暗通道来估算全局大气光值A
    int Sum, Value,Threshold = 0;
    int SumR = 0, SumG = 0, SumB = 0, AtomR, AtomB, AtomG, Amount = 0;
    int* Histgram = (int*)calloc(256 , sizeof(int));    
    for (Y = 0; Y < Width * Height; Y++) Histgram[DarkChannel[Y]]++;
    for (Y = 255, Sum = 0; Y >= 0; Y--)
    {
        Sum += Histgram[Y];
        if (Sum > Height * Width * 0.01)
        {
            Threshold = Y;                                        // 选取暗通道值中前1%最亮的像素区域为候选点
            break;
        }
    }
    AtomB = 0; AtomG = 0; AtomR = 0;
    for (Y = 0, DarkP = DarkChannel; Y < Height; Y++)
    {
        Pointer = Scan0 + Y * Stride;
        for (X = 0; X < Width; X++)
        {
            if (*DarkP >= Threshold)                            //    在原图中选择满足候选点的位置的像素作为计算全局大气光A的信息                        
            {
                SumB += *Pointer;
                SumG += *(Pointer + 1);
                SumR += *(Pointer + 2);
                Amount++;
            }
            Pointer += 3;
            DarkP++;
        }
    }
    AtomB = SumB / Amount;
    AtomG = SumG / Amount;
    AtomR = SumR / Amount;

    memcpy(DarkChannel,Filter, Width * Height);                        // 恢复DarkChannel中的数据
    MedianBlur(Filter,Width,Height,Width,MedianRadius,50);          // 步骤1:使用中值滤波平滑,这样处理的重要性是在平滑的同时保留了图像中的边界部分,但是实际这里用中值滤波和用高斯滤波效果感觉差不多
    memcpy(FilterClone, Filter, Width * Height);

    DarkP = DarkChannel;
    FilterP = Filter;
    for (Y = 0; Y < Height * Width; Y++)              //利用一重循环来计算提高速度
    {
        Diff = *DarkP - *FilterP;                    //通过对|DarkP -FilterP |执行中值滤波来估计的局部标准差,这样可以保证标准差估计的鲁棒性
        if (Diff < 0) Diff = -Diff;
        *FilterP = (unsigned char)Diff;
        DarkP++;
        FilterP++;
    }
    MedianBlur(Filter,Width,Height,Width,MedianRadius,50);

    FilterPC = FilterClone;
    FilterP = Filter;
    for (Y = 0; Y < Height * Width; Y++)
    {
        Diff = *FilterPC - *FilterP;                    // 步骤2:然后考虑到有较好对比度的纹理区域可能没有雾, 这部分区域就不需要做去雾处理
        if (Diff < 0) Diff = 0;                            // 这里可以这样做是因为在最后有个max(....,0)的过程,
        *FilterP = (unsigned char)Diff;
        FilterPC++;
        FilterP++;
    }

    DarkP = DarkChannel;
    FilterP = Filter;

    for (Y = 0; Y < Height * Width; Y++)
    {
        Min = *FilterP * P / 100;
        if (*DarkP > Min) 
            *FilterP = Min;                                // 获得满足约束条件的大气光幕
        else
            *FilterP = *DarkP;
        DarkP++;
        FilterP++;
    }

    FilterP = Filter;
    for (Y = 0;Y < Height; Y++)
    {
        Pointer = Scan0 + Y * Stride;
        for (X = 0; X < Width; X++)
        {
            F = *FilterP++;
            if (AtomB != F) 
                Value = AtomB *(*Pointer - F) /( AtomB - F);
            else
                Value=*Pointer;
            *Pointer++ = Clamp(Value);
            if (AtomG != F) 
                Value =  AtomG * (*Pointer - F) /( AtomG-F);
            else
                Value =  *Pointer;
            *Pointer++ = Clamp(Value);
            if (AtomR != F) 
                Value =  AtomR *(*Pointer - F) /( AtomR-F);
            else
                Value =  *Pointer;
            *Pointer++ = Clamp(Value);
        }
    }
    free(Histgram);
    free(Filter);
    free(DarkChannel);
    free(FilterClone);
}

  关于中值滤波或者双边滤波的快速算法,可以在本人博客中找到大量的相关信息。

     在程序的耗时上,主要还是2次中值处理上,借助于C++的一些优化(比如内嵌SSE代码,C#做不到)中值的速度也相当快了,我用1024*768的灰度图测试耗时约为60ms(未考虑用多线程,因为那个程序用多线程编码上会复杂不少),对彩色图用这种方式去雾,I3CPU上1024*768的总耗时约为140ms,想要实时,换换I7的CPU试试吧(传说中我的那篇实时去雾的文章的算法在I3上20ms,I7上有测试表明只要3到4ms)。

     由于算法的最后一步的公式问题,在某些参数情况下图像会出现黑快或者白块,目前该问题尚未解决。 有兴趣对改算法进行进一步测试的同学可自己研究下。

     相关测试代码下载:

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

*********************************作者: laviewpbt   时间: 2013.12.5   联系QQ:  33184777  转载请保留本行信息*************************

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏c#开发者

about store RecordField submit emptystring issue

operate screenshot When click save button submit to change,trace store before...

3447
来自专栏菩提树下的杨过

基于sliverlight + wcf的web 文字版IM 示例

演示地址: http://task.24city.com/default.html 预览界面: ? 一、布局 采用Grid布局,5行2列 第一行:为登录/注册信...

3226
来自专栏木宛城主

曾今的代码系列——自己的分页控件+存储过程实现分页

项目里面的测试代码,仅供参考 LoginByAjax <title>Ajax登陆</title> <script src="Scripts/c...

1855
来自专栏c#开发者

xmldocument内嵌入另一个xmldocument,xmlnode的方法

string xmlstr1 = @"<root><head>myHead</head><body></body></root>"; s...

2705
来自专栏菩提树下的杨过

Silverlight:利用异步加载Xap实现自定义loading效果

关键点: 1.利用WebClient的DownloadProgressChanged事件更新下载进度 2.下载完成后,分析Xap包的程序集Assembly信息 ...

18610
来自专栏.net core新时代

数据字典生成工具之旅(5):DocX组件读取与写入Word

      由于上周工作比较繁忙,所以这篇文章等了这么久才写(预告一下,下一个章节正式进入NVelocity篇,到时会讲解怎么使用NVelocity做一款简易的...

3848
来自专栏跟着阿笨一起玩NET

treeview 绑定文件夹和文件

451
来自专栏技术之路

sqlserver 的事务和c#的事务

sql的事务 1 sql 2 create database model 3 go 4 use model 5 go 6 create table ...

1919
来自专栏张善友的专栏

通过SmtpClient发送Exchange会议邮件

看到C#中调用Outlook API 发起会议 ,这个完全可以用SMTP方式实现的,下面我的项目中使用的代码: 对于.NET而言,从2.0开始,发邮件已经是一件...

1939
来自专栏hbbliyong

socket 通信 多线程调用窗体(委托)的几个知识点,记录在案,以备查阅

1.socket 通信传输汉字的方法:Encoding.GetEncoding("GB2312").GetString(Receivebyte) 发送接收都这样...

2737

扫码关注云+社区