一、前言
实际上很久以前,当我初次接触图像去雾技术时,最先实现的是基于中值滤波的图像去雾,并且也有一定的效果,在我的Imageshop的集成软件中的去雾方案就是这个的实现,不过那个效果没有本文好。
而基于双边滤波的方案,也是很早就听说过,前不久有朋友传给我一篇国内的双边滤波去雾的论文,总体思路和基于中值的类似,想想干脆把这两个放在一起做个比较吧。
二、算法的流程
算法的最基础的原理还是基于大气散射模型的,即:
已知条件就是输入图像I(X),求J(x);
在参考论文一种单幅图像去雾方法中是通过中值滤波的方式来去雾的,而论文基于双边滤波的实时图像去雾技术研究选用了双边滤波,如果你要实现代码,可能需要两篇论文结合起来看,因为在论文1中的描述没有讲清楚如何通过获得的雾浓度数据来获取无雾的图像。
简单的来说,算法的流程可描述如下:
1、定义F(X)=A(1-t(x)),称之为大气光幕或者为雾浓度。
2、计算,并使用和何博士论文中类似的方式计算全局大气光值A。
3、计算,即对M(x)进行中值滤波。
4、计算,注意式子中的绝对值。
5、计算,式中P为控制去雾程度的因子,取值范围[0,1]。
上面的很多算式是从不同论文里截图的,因此表达上有些前后不一致,但不影响高手理解其含义。
如果是采用双边滤波算子,则步骤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