df768e610e2b4a10a9565fe56f1cea15.png

在网上看到很多关于傅里叶变换的内容, 但是没找到具体工程上完整的一个例子

例如把一个纹理转化为频谱图和相位 然后利用频谱和相位在转化回来

于是就自己做一个好了

如果有不对之处请使劲喷

然后如果你比较熟悉只想看工程部分的内容, 可以酌情跳过

先看一眼结果:

dbd000c40795267225eababc864d349a.png

放出Git 地址:

https://github.com/lingzerg/LingzergDemo​github.com

在看一眼流程:

19905c4d08d8f5787be87414521c3607.png

然后我们慢慢解释

什么是信号?


先把思路捋清楚

讲傅里叶变换的定义有很多, 推荐大家各种搜一下

比如3B1B的傅里叶变换:

https://www.bilibili.com/video/BV1pW411J7s8​www.bilibili.com

比如这篇文章:

https://zhuanlan.zhihu.com/p/19763358​zhuanlan.zhihu.com
9d5f96aee04839b557f32c321d3f8be1.png

我简单描述下, 正常情况下 我们看到的信号是这样的:

这种信号你可以想象是声音, 在时间轴上连续不断变换,自然界中的信号我们认为他是连续的, 比如这样:

4a618e48ca1a3cdc685bb578921d0cc9.png
横轴t就是时间, 所以这个叫做时域

这就是一个信号在时间轴上不断变化, 所以这种信号我们叫一个信号的时域表示

然后为了让计算机可以记录描述这种信号,我们会这样:

59a6793578830cd13ab96be1163bd53d.png
对信号进行离散采样

以一个固定间隔对信号进行采样, 得到的数据包含这个信号在当前采样点的振幅和频率

公式描述是:

f是频率(frequency), A是振幅(Amplitude), phase是相位,x是时间, 得到的是振幅

所以我们只要是讨论计算机领域的信号, 就一定是离散的

推荐大家去坐标系中手动拉一拉这些变量感受下这些变量的作用:

Desmos | Graphing Calculator​www.desmos.com
e7fc4dd68ecd74ff35acd0f9ca46143e.png

什么是傅里叶变换?


我们拿到一个信号,有时候想过滤掉高频部分,例如通话降噪,还有一些不想要的低频部分

这时我们可以用复数坐标系去描述一个信号, 让信号的另外一个特征暴露出来,以便于我们处理

复数坐标系:

8cb0a3a11eb0738c80551c342d690e39.png

使用复数平面描述信号有什么好处呢?

在复数和实数构建的坐标系中,信号的时间周期就变成了一个环, 信号采样就是不断围绕这个圈采样

模长就是振幅,

那问题就在于,我们如何把一个我们抽样得到的信号样本转换成复数形式?

先把欧拉公式扔这里:

然后欧拉公式实际上是逆时针旋转的,而正变换是顺时针的

所以我们需要改成

而逆变换就要换回来

接着把频率

和时间
带入欧拉公式另一侧, 除以
是因为N次采样

在乘以2π 转成弧度:

就如同公式里的描述:

而这个又可以展开写一篇了, 所以详细描述我推荐诸位看我上面发的哪两个链接

最后我们反正知道把一个信号的采样结果带入离散傅里叶变换的公式就可以得到一组复数的描述

在这个公式里 实际上 x 就是时间点, u就是频率,

就是每个单位时间的采样点采样得到的振幅,

最后会得到一个复数, 而逆变换就是反过来:

就是傅里叶变换得到的复数

二维傅里叶变换?


一维傅里叶变换和二维的差别,就在于二维还要在套一层,你需要遍历整个二维空间

二维的离散逆傅里叶变换:

而在图像处理中, 二维傅里叶变换上任意一个像素可以认为是一整个平面波:

d3b69ea14c7c26e4736097fe494edcef.png

还可以把图片上的每个像素看成一个在x轴上方的信号,

比如这样:

f5953ea84a9bf5c7112dd0e9d0ec5189.png
一个sin(x)+1的信号

接着将每个采样点带入公式就可以得到每个像素对应的复数

我开始做傅里叶变换的时候非常奇怪

我感觉傅里叶变换和逆变换的公式之间并没有频域和相位啊

那这个流程到底是什么?

我先上一张图然后慢慢解释

d229aed64e39834196ec0d851682fb61.png

这个基本上就是傅里叶变换的完整流程了

也就是说, 傅里叶变换原始公式里是只有复数的,但是通过复数我们可以得到每个像素点的膜长, 还有弧度:

你不做这一步, 直接把复数的实部和虚部分别保存在纹理的r和g通道也是完全没问题的

但是这就失去了对二维图片进行傅里叶变换的意义

我们只有获取了频域和时域,才能根据频域和时域处理图片

我看过很多demo, 都是直接保存复数,而没有对频域和相位进行独立的转换

那我们怎么在unity里实现呢?


首先我选择用compute shader来实现这个

因为感觉CS比较帅把

224104d93d62aa29094c6c591a1ed176.png

在Cshader中我们创建一个复数的结构体,并写好常用方法:

// Complex

然后我们修改一下CS的核心, 按照公式做积分,详细描述在注释中:

[

这时候我们就得到了频谱图和相位图,你可以在这里对频谱或者相位进行一些操作

例如我注释掉那个 //clamp(spectrum/TextureSize,0,0.5); 可以过滤一些高频信息

或者用各种滤波器在这里处理频谱图, 频谱图代表图片的灰度信息,可以理解为亮度, 而相位代表位置信息.

然后用逆变换还原回去 我们就直接还原回去:

void 

最后给一点tips:

  • 原始变换公式和逆变换公式其实是完全可以脱离频域和相位的,所以可以直接把复数的实部虚部分开存到两个通道里,让变换流程正常,验证程序没问题
  • 频域其实就是复数的模长,相位就是atan(i/r),所以频域和相位逆变换就是利用三角函数还原复数
  • 如果只用复数做变换,那其实是没有意义的,有个频域图和相位图,才有进一步处理合成的空间
  • atan() 的取值是
    , atan2()的取值是
    , 在频域中,我们显然需要的是后者,我开始用的是前者, 卡了好久, 以为算法有问题
  • 因为变换中几乎是没办法debug的,因为你没办法知道过程中的值到底是多少(当然不排除利用矩阵硬算),所以有一组已经验证的复数作为参考可以方便很多, 可以用极简图形先验证频谱正常,在做逆变换,很多图像可以直接看出频谱图应该是什么样,比如以下这个图形:

f291f38657bddc899838a68f4ca18f8d.png
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐