这个课题在好久之前就已经有所接触,不过一直没有用代码去实现过。最近买了一本《机器视觉算法与应用第二版》书,书中再次提到该方法:使用傅里叶变换进行滤波处理的真正好处是能够经过使用定制的滤波器来消除图像中某些特定频率,例如这些特定频率可能表明着图像中重复出现的纹理。算法
在网络上不少的PS教程中,也有提到使用FFT来进行去网纹的操做,其中最为普遍的是使用PS小插件FOURIER TRANSFORM,使用过程为:打开图像--进行FFT RGB操做,而后定位到红色通道,选取通道中除了最中心处的以外的白点区域,而后填充黑色,在返回综合通道,点击IFFT RGB,就OK了, 网络
原图 FFR RGB 频谱图ide
用于消除与纹理对应的频率的滤波器 IFFT RGB处理的结果图 函数
针对这一幅,我曾尝试在PS中用其余的方法来去背景纹理,但是通常去网的同时也把相片模糊了,只有FFT去网纹插件能完美去掉相片的网纹并且不损伤画质。测试
这个插件有个特性,他要求输入必须是3通道或者4通道的图,可是用他处理完成后的图虽然表面上看仍是3通道仍是4通道的,可是他已经失去了彩色信息了,咱们注意到他在进行FFT RGB操做后,RGB三个通道中,R通道保存了频谱图,G通道了保存了相位图,B通道为固定值128,频谱和相位组合在一块儿,只能回复一个通道的信息,所以处理后的图也只能是一个颜色了,这是这个插件的缺陷或者说做为插件的必然性。spa
按照这个思路,若是用户提供了用于消除与纹理对应的频率的滤波器,则该过程的一个大概算法流程以下所示:插件
int IM_TextureRemoval(unsigned char *Src, unsigned char *Mask, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE; if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER; if ((Channel != 1) && (Channel != 3)) return IM_STATUS_INVALIDPARAMETER; if (Channel == 1) { Complex *Data = (Complex*)malloc(Width * Height * sizeof(Complex)); if (Data == NULL) return IM_STATUS_OUTOFMEMORY; for (int Y = 0; Y < Height; Y++) { unsigned char *LinePS = Src + Y * Stride; // 填充FFT变换的复数数据
Complex *LinePD = Data + Y * Width; for (int X = 0; X < Width; X++) { LinePD[X].Real = LinePS[X]; LinePD[X].Imag = 0; } } IM_FFT2D(Data, Data, Width, Height, false, 0, 0); // FFT变换
IM_FFTShift(Data, Data, Width, Height); // 平移中心到图像的中心
for (int Y = 0; Y < Height; Y++) // FFT变换的结果乘以用于消除与纹理对应的频率的滤波器
{ unsigned char *LinePS = Mask + Y * Stride; Complex *LinePD = Data + Y * Width; for (int X = 0; X < Width; X++) { LinePD[X].Real *= LinePS[X] * IM_INV255; LinePD[X].Imag *= LinePS[X] * IM_INV255; } } IM_IFFTShift(Data, Data, Width, Height); // 在反中心化
IM_FFT2D(Data, Data, Width, Height, true, 0, 0); // FFT逆变换
for (int Y = 0; Y < Height; Y++) // 转换成图像
{ Complex *LinePS = Data + Y * Width; unsigned char *LinePD = Dest + Y * Stride; for (int X = 0; X < Width; X++) { LinePD[X] = IM_ClampToByte(LinePS[X].Real); } } free(Data); } else { } return IM_STATUS_OK; }
这个过程也是很是简单的。3d
对于彩色的图像,能够把他们先劈成3个独立的通道,而后调用上述单通道的处理方法,而后在合成。code
不过这个方法仍是有限制的,他能处理的对象是有很是严重网纹的图像,咱们测试过对于普通的身份证照片、摩尔纹等是起不到去除做用的,从频谱上来讲,就是要在频谱上能看到分布在四周处有一些很明显的独立的亮点。这些亮点就对应着纹理的频率。对象
上面的过程须要人工的参与,咱们这里进行一下扩展,尝试下对这类图像进行自动的纹理去除。这里的核心是找到纹理的频率,也就是那些白色独立的亮点。
咱们看上面的FFT频谱图,这种显示基本上都是对直接进行FFT变换后的浮点数据进行对数变换后,在线性映射到0到255范围内的,有进行了log操做,数据压缩了不少,致使频谱图的对比度不是很强,也不利于咱们分隔出那些亮点,若是咱们不记性这种操做,而是直接绝对值Clamp显示,大概能获得下面的效果:
这种效果的FFT图很明显更有利于纹理特征的提取。
下面的步骤就是:OSTU二值化 -- 》膨胀 --》 腐蚀 -- 》 反色 ---》中心核保留 -- 》中值 获得纹理频率的滤波器。整个效果以下图:
二值化 膨胀(半径2) 腐蚀(半径2)
反色 保留中心区域 中值(半径1)
稍微分析下原理吧(也不必定科学)。
首先二值化,没啥好说的。 二值后,咱们看到白色部分有不少零碎的部分,特别是图像的中心区域的零碎化对最后的效果有很是很差的影响(咱们必须保持中心部分没啥变化),因此后续使用了开操做来改善效果,先膨胀后腐蚀。 接着咱们反色一下,由于后续的滤波器是非中心区域的白色部分是要变为黑色的,第五步,也是比较核心的步骤,咱们须要把中心部分的黑色部分变为白色,由于这部分保留着图像的大部分信息, 这里咱们能够采用基于4领域的区域生长法,由于在频谱中的中心点,这一点二值后确定是白色的,在反色后就是白色,就以这一点为种子点,向四周进行区域生长,这样就能够把中心处的黑色反色过来,而其余地方的黑色保持不变。
第五步的中值,或者能够用其余模糊来代替,也是有点必要的,对于有些图像,通过前面的处理后,有些核心的线(垂直或者水平方向)也被标记为黑色的了,正在处理完成的图像中会带来本来没有的新条纹。
原图 频谱图
去除中值滤波后的滤波器 对应的结果(有瑕疵)
增长中值后的滤波器 对应的结果
上述过程先关的函数以下所示:
// 根据频谱图预估纹理的频谱蒙版区域,支持InPlace操做
int IM_GetTextureMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE; if ((Width <= 0) || (Height <= 0)) return IM_STATUS_INVALIDPARAMETER; if (Channel != 1) return IM_STATUS_INVALIDPARAMETER; int Status = IM_STATUS_OK; unsigned char *Temp = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); if (Temp == NULL){ Status = IM_STATUS_OUTOFMEMORY; goto FreeMemory; } int Threshold = 0; Status = IM_GetOSTUThreshold(Src, Width, Height, Stride, Threshold); // 使用OSTU方法二值化
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_Threshold(Src, Temp, Width, Height, Stride, Threshold); // 二值化
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_Dilate(Temp, Dest, Width, Height, Stride, 2, false); // 先膨胀下(最大值),注意膨胀和腐蚀函数不支持InPlace操做
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_Erode(Dest, Temp, Width, Height, Stride, 2, false); // 而后在腐蚀(最小值),恢复原来的差很少大小,可是这样中心区域不相邻的点就少了不少
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_Invert(Temp, Dest, Width, Height, Stride); // 这个时候的图,纹理的频谱和其余核心能量区域都仍是白色,为后续的处理须要先反色
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_InvertCenter(Dest, Temp, Width, Height, Stride); // 把中心的能量区域保留(白色),其余的纹理的频谱删除(黑色)
if (Status != IM_STATUS_OK) goto FreeMemory; Status = IM_MedianBlur(Temp, Dest, Width, Height, Stride, 1, 50); // 执行半径为1的中值,这样可能能够减小部分垂直或者水平的核心能力被删除
if (Status != IM_STATUS_OK) goto FreeMemory; FreeMemory: if (Temp != NULL) free(Temp); return Status; }
咱们注意到,上面的操做对纹理处频率处对应的滤波器系数都为0了,也就是这一块的信息所有被消除了,固然实际操做时也能够稍微羽化一下,对最后的结果影响不大。
《任何未通知的转载或转发,都是猪狗不如的做为》。
根据上述的步骤,有选择性的处理了几幅图,结果以下所示:
能够看出,虽然能再必定程度上去除网纹,可是也就有一些去除的不彻底,这主要仍是由于自动提取的滤波器仍是不够准确,要想获取更为理想的结果,必须手动的予以修缮。
对于常规的图片,或者说纹理信息不明显的图,及时执行了上面的去纹理,图片也基本上没有什么变化,由于按照上述方法获得的滤波器基本都为白色。
本文算法的测试例程见 : http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar,位于菜单FFT-->TextureRemoval下。