快速傅里叶变换 java_二维快速傅里叶变换的java实现
图像处理与模式识别课作业,没学过信号与系统(哭晕)。
恶补了两天冈萨里斯的书,写一下实现原理及过程
看了网络上很多版本的概念讲解,道理我都懂,但是在将算法迁移到傅里叶变换的实现上时,出现了一些问题。接下来我会简要介绍快速傅里叶变换是如何加速的,着重写如何将加速算法应用到傅里叶变换上。
二维快速傅里叶变换原理介绍
1.1普通的二维傅里叶变换
二维傅里叶变换的公式如下:
\[F(u, v) = \sum^{M-1}_{x=0}\sum^{N-1}_{y=0}f(x, y)e^{-j2\pi({ux}/M+{vy}/N)}
\]
对上述表达式调整和分顺序,先对x求和,再对y求和。我们可以将二维离散傅里叶变换公式转化为两次一维离散傅里叶变换。一维离散傅里叶变换公式如下:
\[F(u)=\sum^{N-1}_{x=0}f(x)e^{{{-j2\pi}ux}/N}\quad u = 0,1,2...N-1
\]
从变换公式中我们可以看出来,由于我们要求N个\(F(u)\),每个\(F(u)\)需要进行N次运算。使用这样算法的时间复杂度是\(O(n^2)\)的。这样的算法在N特别大时,会比较慢。在这里引入快速傅里叶变换的加速算法。
1.2快速傅里叶变换原理
相信大家在看过别的博客内容后已经掌握了快速傅里叶变换的原理。我在这里就简要再提一下。快速傅里叶变换其中一个重要的依据就是函数的点值对表示。其实我们对离散函数求得的傅里叶变换的结果就是一个点值对表示(毕竟我们没有求出傅里叶变换函数的系数)。
一个n次多项式函数,注意:这里的n是2的整数幂,如果一个函数的最高次没有达到2的整数幂,我们可以认为此项系数为0,不影响函数的值
\[A(x) = a_0 + a_1x+a_2x^2+...+a_{n-1}x^{n-1}
\]
在奇偶项合并后,我们可以得到
\[A(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})\\
A(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+x(a_1+a_3x^2+...+a_{n-1}x^{n-2})\\
A(x)=A_{even}(x^2)+xA_{odd}(x^2)
\]
我们将\(w^k_n\quad k\in(0, 1, 2,...,n-1)\)带入A(x)。可以得到A(x)的点值对表示。但是这种点值对的计算方法,仍然是\(O(n^2)\)的。如何将计算加速呢?这里就用到我们取巧的\(w^k_n\)上了。
简要介绍一下\(w^k_n\),它是正整数1的n次单位根。具体值为:
\[w^k_n=e^{i{2\pi}k/n}=cos(2\pi k/n)+i*sin(2\pi k/n)
\]
从虚数相乘等于在圆上的旋转,我们可以看出它确实是1的n次单位根的集合。它有几个特殊的性质
\[w^{k+n/2}_n=e^{i2\pi (k+n/2)/n}=e^{i2\pi k/n}*e^{i\pi}=e^{i2\pi k/n}*(cos(\pi)+i*sin(\pi))=-e^{i2\pi k/n}=-w^k_n\\
w^{k+n}_n=e^{i2\pi k/n}*e^{i2\pi}=e^{i2\pi k/n}=w^k_n
\]
所以我们在计算点值对的时候,有这样一种关系:
\[A(w^k_n)=A_{even}((w^k_n)^2)+w^k_n*A_{odd}((w^k_n)^2)
-->A(w^k_n)=A_{even}(w^{2k}_n)+w^k_n*A_{odd}(w^{2k}_n)\\
A(w^{k+n/2}_n)=A_{even}((w^{k+n/2}_n)^2)+w^{k+n/2}_n*A_{odd}((w^{k+n/2}_n)^2)-->
A(w^{k+n/2}_n)=A_{even}(w^{2k+n}_n)-w^k_n*A_{odd}(w^{2k+n}_n)-->A(w^k_n)=A_{even}(w^{2k}_n)-w^k_n*A_{odd}(w^{2k}_n)
\]
整理一下,结果如下
\[A(w^k_n)=A_{even}(w^{2k}_n)+w^k_n*A_{odd}(w^{2k}_n)\\
A(w^{k+n/2}_n)=A_{even}(w^{2k}_n)-w^k_n*A_{odd}(w^{2k}_n)
\]
则我们求出\(A_{even}(w^{2k}_n)\)和\(A_{odd}(w^{2k}_n)\)就可以得到两个值。根据我们算法课学的分治+递归的思想。递归层数为logn次,每层计算时间复杂度为\(O(n)\),则总的时间复杂度为\(O(nlogn)\)。这样我们就做到了快速计算。
1.3将算法应用到傅里叶变换
之前我们大致了解了傅里叶变换的原理,那么如何将这个算法加速我们傅里叶变换的计算呢?一维离散傅里叶变换的公式为:
\[F(u)=\sum^{N-1}_{x=0}f(x)e^{{{-j2\pi}ux}/N}\quad u = 0,1,2...N-1
\]
是不是看到了\(e^{{{-j2\pi}ux}/N}\)? 注意:我们接下来使用的\(w^k_n\)=\(e^{-j2\pi k/n}\)。可以证明,这并不影响我们之前介绍的性质。
则我们的傅里叶变换公式可以写成
\[F(u)=\sum^{N-1}_{x=0}f(x)w^{ux}_n
\]
那么我们应用加速的地方就出现了
\[F(u)=\sum_{t=0}^{\frac{n}{2}-1}f_{even}(t)w^{2ut}_n+w^u_n * \sum_{t=0}^{\frac{n}{2}-1}f_{odd}(t)w^{2ut}_n\\
F(u+n/2)=\sum_{t=0}^{\frac{n}{2}-1}f_{even}(t)w^{2ut}_n-w^u_n * \sum_{t=0}^{\frac{n}{2}-1}f_{odd}(t)w^{2ut}_n
\]
是不是和我们之前看到的算法原理的地方很像?是的,我们快速傅里叶变换就是利用这个公式做的。利用这个公式,一次就可以计算出两个值,加速了傅里叶变换计算的层数。同样的,这个算法共有logn层,每层的时间复杂度为\(O(n)\)。整体算法的时间复杂度为\(O(nlogn)\)。
这里还有一个trick,即蝶形算法。我们在这里举个蝶形算法的例子。假设有下列一组数
\[f(0)\quad f(1)\quad f(2)\quad f(3)\quad f(4)\quad f(5)\quad f(6)\quad f(7)
\]
第一次划分奇偶后,我们得到
\[[f(0)\quad f(2)\quad f(4)\quad f(6)]\quad [f(1)\quad f(3)\quad f(5)\quad f(7)]
\]
第二次划分奇偶后,结果
\[[f(0)\quad f(4)]\quad [f(2)\quad f(6)]\quad [f(1)\quad f(5)]\quad [f(3)\quad f(7)]
\]
第三次到递归终点,我们看一下上诉编号的二进制码
\[000\quad 100\quad 010\quad 110\quad 001\quad 101\quad 011\quad 111
\]
将二进制反序
\[000\quad 001\quad 010\quad 011\quad 100\quad 101\quad 110\quad 111
\]
是不是就成了一个自然数列?我们可以根据这个蝶形的方式,先将\(f(x)\)排列好,然后以循环的方式计算最终的结果。
核心java代码
import java.util.Comparator;
import java.util.ArrayList;
public class FFT {
private ArrayList data;
private ArrayList buff;
public FFT(ArrayList list, int pow){
data = new ArrayList<>();
for(int i = 0; i < list.size(); i++){
sortData tmp = new sortData(list.get(i), reverse(i, pow));
data.add(tmp);
}
Comparator comparator = (o1, o2) -> Integer.compare(o2.getRevNum(), o1.getRevNum());
data.sort(comparator);
buff = new ArrayList<>();
}
public ArrayList trans(){
recursion(data.size());
return buff;
}
private void recursion(int half){
for (sortData tmpData : data) {
// init the array
Complex tmp = new Complex(1.0);
tmp.multiply(tmpData.getValue());
buff.add(tmp);
}
int count = 2;
while(half >= count) {
for (int i = 0; i < data.size(); i = i + count) {
for (int j = i; j < i + count / 2; j++) {
Complex tmp1 = buff.get(j);
Complex tmp2 = buff.get(j + count / 2);
Complex tmpComplex = tmp1.complexClone();
Complex w = root(count, 1, j - i);
tmp2.multiply(w);
tmpComplex.add(tmp2);
tmp1.subtract(tmp2);
buff.set(j, tmpComplex);
buff.set(j + count / 2, tmp1);
}
}
count = count * 2;
}
}
private int reverse(int value, int pow){
int res = Integer.reverse(value);
res = res >> (Integer.SIZE - pow);
res = res & ((1 << pow) - 1);
return res;
}
private Complex root(int k, int x, int u){
double real = Math.cos(Math.PI * 2 * u * x / k);
double imaginary = -1 * Math.sin(Math.PI * 2 * u * x / k);
return new Complex(real, imaginary);
}
}
class sortData{
private Complex value;
private int revNum;
public sortData(Complex o, int revNum){
value = o;
this.revNum = revNum;
}
public int getRevNum(){
return revNum;
}
public Complex getValue(){
return value;
}
}
recursion函数是计算构造方法中传入的ArrayList的傅里叶变换,计算结果存在buff中。
reverse函数是利用蝶形算法的原理,计算出每个采样点二进制的反转结果。
root函数是计算1的n次单位结果\(w^{ux}_k\)。
Complex是一个复数类,具体内容如下。
public class Complex {
private double real;
private double imaginary;
public Complex(){
real = 0;
imaginary = 0;
}
public Complex(double num){
real = num;
imaginary = 0;
}
public Complex(double num1, double num2){
real = num1;
imaginary = num2;
}
public Complex complexClone(){
return new Complex(real, imaginary);
}
public void add(Complex o){
real = real + o.real;
imaginary = imaginary + o.imaginary;
}
public void subtract(Complex o){
real = real - o.real;
imaginary = imaginary - o.imaginary;
}
public void multiply(Complex o){
double tmp = real * o.real - imaginary * o.imaginary;
imaginary = real * o.imaginary + imaginary * o.real;
real = tmp;
}
public void absLog(){
real = Math.sqrt(real * real + imaginary * imaginary)/50;
if(real > 255){
real = 255;
}
imaginary = 0;
}
public int getReal(){
return (int)real;
}
@Override
public String toString(){
if (imaginary == 0) return real + "";
if (real == 0) return imaginary + "i";
if (imaginary < 0) return real + " - " + (-imaginary) + "i";
return real + " + " + imaginary + "i";
}
}
填坑完毕,转发需注明出处。
快速傅里叶变换 java_二维快速傅里叶变换的java实现相关推荐
- 图像傅里叶变换(二维离散傅里叶变换)
图像傅里叶变换 二维离散傅里叶变换是将图像从空间域转至频域,在图像增强.图像去噪.图像边缘检测.图像特征提取.图像压缩等等应用中都起着极其重要的作用.理论基础是任意函数都可以表示成正弦函数的线性组合的 ...
- C++实现二维快速傅里叶变换(FFT)
上一篇文章里,我根据DFT公式用C++实现了二维离散傅里叶变换.但跑一张300*300的图片都要好几分钟,速度实在太慢了.我研究了下快速傅里叶变换,在网上找了一些资料,然后用C++实现了二维快速傅里叶 ...
- 傅里叶变换 一维和二维快速傅里叶变换(代码和性能的优化)
1.介绍. 在类FourierUtils的fftProgress方法中,有这个代码段,我们可以将Complext.euler(flag * i)提前计算好,设置大小为2次幂N,如果没有的话,也要调节到 ...
- 【OpenCV 例程200篇】73. 二维连续傅里叶变换
[OpenCV 例程200篇]73. 二维连续傅里叶变换 欢迎关注 『OpenCV 例程200篇』 系列,持续更新中 欢迎关注 『Python小白的OpenCV学习课』 系列,持续更新中 2.1 二维 ...
- C++实现二维离散傅里叶变换
在上一篇文章<C++实现一维离散傅里叶变换>中,我们介绍了一维信号傅立叶变换的公式和C++实现,并阐述了频域幅值的意义. 一维傅立叶变换只适用于一维信号,例如音频数据.心脑电图等. 在图像 ...
- 第4章 Python 数字图像处理(DIP) - 频率域滤波5 - 二变量函数的傅里叶变换、图像中的混叠、二维离散傅里叶变换及其反变换
目录 二变量函数的傅里叶变换 二维冲激及其取样性质 二维连续傅里叶变换对 二维取样和二维取样定理 图像中的混叠 二维离散傅里叶变换及其反变换 二变量函数的傅里叶变换 二维冲激及其取样性质 两个连续变量 ...
- Python 二维离散傅里叶变换
Python 二维离散傅里叶变换 文章目录 Python 二维离散傅里叶变换 需要的库 计算两张图片的PSNR 二维离散傅里叶变换 二维离散傅里叶逆变换 频域平移 绘制频域图像 需要的库 import ...
- 傅里叶变换 二维离散傅里叶变换
1.介绍. DFT:(Discrete Fourier Transform)离散傅里叶变换是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其DTFT的频域采样.在形式上,变换两端(时域 ...
- java 简单快速的生成二维码(带图片)
**使用java+zxing 简单快速的生成二维码.** 效果图: 首先需要引入两个jar文件: <dependency><groupId>commons-codec</ ...
最新文章
- 中文文本中的关键字提取算法总结
- 自然语言处理数据集免费资源开放(附学习资料)
- markdown如何设置图片大小_Gitee(码云)实现免费 Markdown 图床
- LeetCode 1869. 哪种连续子字符串更长
- 大学计算机在线答题,大学计算机基础网上考试答题卷理论部分(含答案)
- 训练不出结果_训练赛惨败SKT?FPX直播透露拿冠军原因!Karsa再谈离开RNG?
- Spring boot :使用 Swagger 2 构建 RESTful APIs
- TCP/IP之路由算法
- 38..Node.js工具模块---底层的网络通信--Net模块
- 数组元素替换_LeetCode基础算法题第183篇:一维数组的重新洗牌
- DnsFookup:一款功能强大的DNS重绑定工具
- c语言常量ascii码表,C语言常用转义符和ASCII码表
- 2021年中国MEMS话筒市场趋势报告、技术动态创新及2027年市场预测
- C语言:输出奇数和偶数的个数
- 计算机老是重启进不了桌面,电脑无限重启进不桌面
- 消息队列之Kafka从入门到小牛
- 12306对抢票软件“下手”了
- 王者荣耀上官婉儿的语录
- nlp对语料进行分类_如何使用nlp对推文进行分类
- 场景建模都用到哪些三维软件,入门学习选择3DsMax还是Maya