【算法随记五】使用FFT变换自动去除图像中严重的网纹。
这个课题在很久以前就已经有所接触,不过一直没有用代码去实现过。最近买了一本《机器视觉算法与应用第二版》书,书中再次提到该方法:使用傅里叶变换进行滤波处理的真正好处是可以通过使用定制的滤波器来消除图像中某些特定频率,例如这些特定频率可能代表着图像中重复出现的纹理。
在网络上很多的PS教程中,也有提到使用FFT来进行去网纹的操作,其中最为广泛的是使用PS小插件FOURIER TRANSFORM,使用过程为:打开图像--进行FFT RGB操作,然后定位到红色通道,选取通道中除了最中心处的之外的白点区域,然后填充黑色,在返回综合通道,点击IFFT RGB,就OK了,
原图 FFR RGB 频谱图
用于消除与纹理对应的频率的滤波器 IFFT RGB处理的结果图
针对这一幅,我曾尝试在PS中用其他的方法来去背景纹理,可是一般去网的同时也把相片模糊了,只有FFT去网纹插件能完美去掉相片的网纹而且不损伤画质。
这个插件有个特性,他要求输入必须是3通道或者4通道的图,但是用他处理完成后的图虽然表面上看还是3通道还是4通道的,但是他已经失去了彩色信息了,我们注意到他在进行FFT RGB操作后,RGB三个通道中,R通道保存了频谱图,G通道了保存了相位图,B通道为固定值128,频谱和相位组合在一起,只能回复一个通道的信息,因此处理后的图也只能是一个颜色了,这是这个插件的缺陷或者说作为插件的必然性。
按照这个思路,如果用户提供了用于消除与纹理对应的频率的滤波器,则该过程的一个大概算法流程如下所示:
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 <= ) || (Height <= )) return IM_STATUS_INVALIDPARAMETER;
if ((Channel != ) && (Channel != )) return IM_STATUS_INVALIDPARAMETER; if (Channel == )
{
Complex *Data = (Complex*)malloc(Width * Height * sizeof(Complex)); if (Data == NULL) return IM_STATUS_OUTOFMEMORY; for (int Y = ; Y < Height; Y++)
{
unsigned char *LinePS = Src + Y * Stride; // 填充FFT变换的复数数据
Complex *LinePD = Data + Y * Width;
for (int X = ; X < Width; X++)
{
LinePD[X].Real = LinePS[X];
LinePD[X].Imag = ;
}
}
IM_FFT2D(Data, Data, Width, Height, false, , ); // FFT变换
IM_FFTShift(Data, Data, Width, Height); // 平移中心到图像的中心
for (int Y = ; Y < Height; Y++) // FFT变换的结果乘以用于消除与纹理对应的频率的滤波器
{
unsigned char *LinePS = Mask + Y * Stride;
Complex *LinePD = Data + Y * Width;
for (int X = ; 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, , ); // FFT逆变换 for (int Y = ; Y < Height; Y++) // 转换成图像
{
Complex *LinePS = Data + Y * Width;
unsigned char *LinePD = Dest + Y * Stride;
for (int X = ; X < Width; X++)
{
LinePD[X] = IM_ClampToByte(LinePS[X].Real);
}
}
free(Data);
}
else
{ }
return IM_STATUS_OK; }
这个过程也是非常简单的。
对于彩色的图像,可以把他们先劈成3个独立的通道,然后调用上述单通道的处理方法,然后在合成。
不过这个方法还是有限制的,他能处理的对象是有非常严重网纹的图像,我们测试过对于普通的身份证照片、摩尔纹等是起不到去除作用的,从频谱上来说,就是要在频谱上能看到分布在四周处有一些很明显的独立的亮点。这些亮点就对应着纹理的频率。
上面的过程需要人工的参与,我们这里进行一下扩展,尝试下对这类图像进行自动的纹理去除。这里的核心是找到纹理的频率,也就是那些白色独立的亮点。
我们看上面的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 <= ) || (Height <= )) return IM_STATUS_INVALIDPARAMETER;
if (Channel != ) 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 = ;
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, , false); // 先膨胀下(最大值),注意膨胀和腐蚀函数不支持InPlace操作
if (Status != IM_STATUS_OK) goto FreeMemory;
Status = IM_Erode(Dest, Temp, Width, Height, Stride, , 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的中值,这样可能可以减少部分垂直或者水平的核心能力被删除
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下。
【算法随记五】使用FFT变换自动去除图像中严重的网纹。的更多相关文章
- Matlab实现Hough变换检測图像中的直线
Hough变换的原理: 将图像从图像空间变换至參数空间.变换公式例如以下: 变换以后,图像空间与參数空间存在下面关系: 图像空间中的一点在參数空间是一条曲线,而图像空间共线的各点相应于參数空间交于一点 ...
- 查找图像中椭圆轮廓的快速随机hough变换
查找图像中椭圆轮廓的快速随机hough变换 图像中椭圆轮廓的查找在视频监控等领域有着广泛的应用,经典hough变换给我们提供了一种查找各种图形轮廓的方法,特别是在直线查找方面具有非常高的精确度.但是由 ...
- SSE图像算法优化系列十一:使用FFT变换实现图像卷积。
本文重点主要不在于FFT的SSE优化,而在于使用FFT实现快速卷积的相关技巧和过程. 关于FFT变换,有很多参考的代码,特别是对于长度为2的整数次幂的序列,实现起来也是非常简易的,而对于非2次幂的序列 ...
- SENet(Squeeze-and-Excitation Networks)算法笔记---通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征
Momenta详解ImageNet 2017夺冠架构SENet 转自机器之心专栏 作者:胡杰 本届 CVPR 2017大会上出现了很多值得关注的精彩论文,国内自动驾驶创业公司 Momenta 联合机器 ...
- java实现FFT变换(转)
源:java实现FFT变换 /************************************************************************* * Compilati ...
- 安装fftw到window(vs2010)及使用fftw库函数实现4096点fft变换计算
Windows下FFTW库的安装: 1. 从网站http://www.fftw.org/install/windows.html上下载最新的预编译文件: 32-bit version: fftw ...
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- 搜索引擎算法研究专题五:TF-IDF详解
搜索引擎算法研究专题五:TF-IDF详解 2017年12月19日 ⁄ 搜索技术 ⁄ 共 1396字 ⁄ 字号 小 中 大 ⁄ 评论关闭 TF-IDF(term frequency–inverse ...
- Java实现 蓝桥杯 算法训练 第五次作业:字符串排序
试题 算法训练 第五次作业:字符串排序 问题描述 输入一个小写字符串,按从小到大的顺序输出. 输入格式 bcaed 输出格式 abcde 顶格输出,中间没有空格 样例输入 一个满足题目要求的输入范例. ...
随机推荐
- visual studio code 应用到.net core 实战
鉴于visual studio 2019 近期动不动卡顿与切分支后F12等功能失效的问题,开始考虑用visual studio code 代替他,对,你没有看错,就是代替visual studio 这 ...
- 渗透之路基础 -- SQL进阶(盲注和报错注入)
SQL注入之盲注 实战过程中,大多情况下很少会有回显,这个时候就要去使用盲注技术 盲注,Blind SQL Injection,听这名字就感觉整个过程就是一个盲目的过程 当注入时,没有任何提示的时候, ...
- DEVOPS基础
转自:http://www.scrumcn.com/agile/scrum-knowledge-library/agile-development.html#tab-id-7 DevOps是一组过程. ...
- Navicat for mysql建立连接
1. 安装Navicat for MySQL. 2. 点击连接->MySQL,打开SSH,填写主机名.端口.用户名.密码. 3. 连接->打开常规,设置连接名(可以自由指定).主机名.端口 ...
- mysql之innodb日志管理
本文从两个方面进行阐述,常规的日志文件(不区分存储引擎),第二就是讲innodb存储引擎的事务日志. 一.基本日志文件 1.1.基本日志文件分类:错误日志(error log)慢查询日志日志(slow ...
- SVN分支与主干合并
1.主干合并到分支 1在本地trunk中先update一下,有冲突的解决冲突,保证trunk和repository已经完全同步, 2.在/branches /MyProject上右键,依次选择”Tor ...
- F#周报2019年第37期
新闻 宣告ML.NET 1.4的预览版及更新模型构建器 .NET展示会:一系列的活动! Octopus入门版:对于小团队免费 宣告.NET Core 3.0预览版9 使用IntelliCode更简单地 ...
- Android中px dpi dip density densityDpi 的相关说明
转自:http://www.cnblogs.com/wader2011/archive/2011/11/29/2267490.html 概念解释 名词 解释 Px (Pixel像素) 不同设备显示效果 ...
- GC判断哪些内存需要回收
GC的问题,主要分为:1.哪些内存需要回收?2.什么时候回收?3.如何回收?这里主要讲第一个问题. 1.哪些内存需要回收? 垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”, ...
- C#中将表示颜色的string转换成Color
场景 在Winform中需要存储某控件的Color属性,存储的是string字符串, 然后再对控件进行赋值时需要将string转换成Color. 实现 myPane.YAxis.Color = Sys ...