FFT的原理及matlab实现

公式推导

离散傅里叶变换公式:
F(k)=N1n=0f(n)ei2πnk/N=N1n=0f(n)WnkN
由周期性:
Wn(N+k)N=ei2πn(k+N)/N=ei2πnk/Nei2πn=ei2πnk/N=WnkN
共轭性:
Wn(k+N/2)N=ei2πn(k+N/2)/N=ei2πnk/Neiπn=WnkN
规模性(等比例性):
Wn(k/m)N/m=ei2πn(k/m)/(N/m)=WnkN

前提:假设N能够被2整除
f(n) 分为偶数序列 f1(n) 和奇数序列 f2(n) ,有:
F(k)=N/21n=0f(2n)ei2π2nk/N+N/21n=0f(2n+1)ei2π(2n+1)k/N
=N/21n=0f(2n)ei2πnk/(N/2)+ei2πk/NN/21n=0f(2n+1)ei2πnk/(N/2)
=F1(k)+WkNF2(k)
根据共轭性有:
F(k+N/2)=F1WkNF2

这样就可以实现分治法递归了:

分治法代码

前提,输入是2的整数次幂,二维变换只是做横纵两次一维变换而已

function F=my_fft(x)
%% 判断是否是向量
sz=size(x);
if sz(1)>1&&sz(2)>1||numel(sz)>2
    F=-1;
    return;
end
%% 
N=max(sz(1:2));
if N==1
    F=x;
    return;
end
if sz(1)>1
    F=zeros(N,1);
end
if sz(2)>1
    F=zeros(1,N);
end
for k=1:N/2
    [F(k),F(k+N/2)]=my_fft_ele(x,N,k);
end


function [Fk,Fkn]=my_fft_ele(x,N,k) %输入信号x,信号总长度N,频域坐标k
    if N==1
        Fk=x;
        Fkn=x;
        return;
    else
        x1=x(1:2:N-1);%奇数
        x2=x(2:2:N);%偶数
        F1=my_fft_ele(x1,N/2,k);
        F2=my_fft_ele(x2,N/2,k);
        Wkn=exp(-i*2*pi*(k-1)/N);
        Fk=F1+Wkn*F2;
        Fkn=F1-Wkn*F2;
    end
function F=my_ifft(x)
%% 判断是否是向量
sz=size(x);
if sz(1)>1&&sz(2)>1||numel(sz)>2
    F=-1;
    return;
end
%% 
N=max(sz(1:2));
if N==1
    F=x;
    return;
end
if sz(1)>1
    F=zeros(N,1);
end
if sz(2)>1
    F=zeros(1,N);
end
for k=1:N/2
    [F(k),F(k+N/2)]=my_ifft_ele(x,N,k);
end
F=F./N;


function [Fk,Fkn]=my_ifft_ele(x,N,k) %输入信号x,信号总长度N,频域坐标k
    if N==1
        Fk=x;
        Fkn=x;
        return;
    else
        x1=x(1:2:N-1);%奇数
        x2=x(2:2:N);%偶数
        F1=my_ifft_ele(x1,N/2,k);
        F2=my_ifft_ele(x2,N/2,k);
        Wkn=exp(i*2*pi*(k-1)/N);
        Fk=F1+Wkn*F2;
        Fkn=F1-Wkn*F2;
    end
function F=my_fft2(x)
sz=size(x);
r=sz(1);
c=sz(2);
F=zeros(r,c);
for k=1:r
    F(k,:)=my_fft(x(k,:));
end
for k=1:c
    F(:,k)=my_fft(F(:,k));
end
function F=my_ifft2(x)
sz=size(x);
r=sz(1);
c=sz(2);
F=zeros(r,c);
for k=1:r
    F(k,:)=my_ifft(x(k,:));
end
for k=1:c
    F(:,k)=my_ifft(F(:,k));
end

递归算法的问题
由于要不断地出栈入栈以及划分原序列,消耗了大量的时间

蝶形算法

(不能直接上传图片真的蛋疼,还得先分享再用浏览器打开,复制图片得到链接)
FFT的原理及matlab实现_第1张图片

通过上面两个公式反推,得到蝶形图,每一级(第k级)都是长度为$2^k$序列的傅里叶变换结果。
初始的序列需要一个二进制反序排列,即将原序列的序号转换成二进制数,然后将其反序的数与其交换。
网上的 O(log(N)) 的算法太神棍看不懂,先给出自己写的 O(N) 的排序算法, O(log(N)) 的算法在使用的代码里:

F=x;
K=log2(N);
C=zeros(1,N);
for I=0:N-1
    nx=dec2bin(I,K);%转换为二进制字符串
    nx=nx(end:-1:1);%逆序
    nv=bin2dec(nx);%转换为十进制数
    if nv~=I&C(I+1)==0 %交换
        T=F(I+1);
        F(I+1)=F(nv+1);
        F(nv+1)=T;
        C(I+1)=1;
        C(nv+1)=1;
    end
end

利用变换核的 比例性质,指数函数只需要算一次
代码如下:

function F=myfft(x,N)
if ~ismatrix(x)
    F=-1;
    return;
end
%% 
if N==1
    F=x;
    return;
end

%% 二进制逆序排序
F=x;
LH=N/2; 
J=LH; 
N1=N-2;  
for I=1:1:N1    
    if I1);         
        F(I+1)=F(J+1);        
        F(J+1)=T;     
    end
    K=LH; %K是比较位数的指示,一开始是二进制最高位,如果J的K位为1,则跳0,比较下一位     
    while J>=K   %是0则不跳,退出循环      
        J=J-K;         
        K=K/2;     
    end
    J=J+K; %K位跳1 还是不明白逻辑,太神棍了这段代码
end

Wmn=exp(-i*2*pi*(0:N/2-1)/N);
n=1;
n2=1;
group=N;
for k=1:K
    n2=n;
    n=n*2;
    group=group/2;
    for j=1:group %组数
        st=(j-1)*n+1;
        for m=st:(st+n2-1)
            W1=Wmn((m-st)*N/n+1);
            W=W1*F(m+n2);           
            F(m+n2)=F(m)-W;
            F(m)=F(m)+W;
        end
    end
end
function F=myifft(x,N)
if ~ismatrix(x)
    F=-1;
    return;
end
%% 
if N==1
    F=x;
    return;
end

%% 二进制逆序排序
F=x;
LH=N/2; 
J=LH; 
N1=N-2;  
for I=1:1:N1    
    if I1);         
        F(I+1)=F(J+1);        
        F(J+1)=T;     
    end
    K=LH; %K是比较位数的指示,一开始是二进制最高位,如果J的K位为1,则跳0,比较下一位     
    while J>=K   %是0则不跳,退出循环      
        J=J-K;         
        K=K/2;     
    end
    J=J+K; %K位跳1 还是不明白逻辑,太神棍了这段代码
end
Wmn=exp(i*2*pi*(0:N/2-1)/N);
n=1;
n2=1;
group=N;
for k=1:K
    n2=n;
    n=n*2;
    group=group/2;
    for j=1:group %组数
        st=(j-1)*n+1;
        for m=st:(st+n2-1)
            W1=Wmn((m-st)*N/n+1);
            W=W1*F(m+n2);           
            F(m+n2)=(F(m)-W);
        end
    end
end
F=F./N;
function F=myfft2(x,M,N)
F=zeros(M,N);
for k=1:M
    F(k,:)=myfft(x(k,:),N);
end
for k=1:N
    F(:,k)=myfft(F(:,k),M);
end
function F=myifft2(x,M,N)
F=zeros(M,N);
for k=1:M
    F(k,:)=myifft(x(k,:),N);
end
for k=1:N
    F(:,k)=myifft(F(:,k),M);
end
clc;
clear;
close all;
x=imread('temp\lena.jpg');
x=mat2gray(rgb2gray(x));
[M,N]=size(x);

start1=tic;%计时
F1=fft2(x);
end1=toc(start1)
start1=tic;
X1=ifft2(F1);
end1=toc(start1)

start2=tic;%计时
F2=myfft2(x,M,N);
end2=toc(start2)
star2=tic;%计时
X2=myifft2(F2,M,N);
end2=toc(start2)

diffF=F1-F2;
diffX=X1-X2;
diffF(find(diffF<10^-11))=0;
diffX(find(diffX<10^-11))=0;

figure;
imshow(mat2gray(log(abs(fftshift(F1))))),title('fft2');
figure;
imshow(mat2gray(log(abs(fftshift(F2))))),title('myfft2');
figure;
imshow(mat2gray(abs(X1))),title('ifft2');
figure;
imshow(mat2gray(abs(X2))),title('myifft2');

sum(sum(diffF))/(M*N)
sum(sum(diffX))/(M*N)

测试效果:

傅里叶频谱图
FFT的原理及matlab实现_第2张图片

Matlab fft2正变换时间
0.0082s
Matlab fft2逆变换时间
0.0094s
myfft2正变换时间
0.6737s
myifft2逆变换时间
1.6599s
正变换误差
5.5511e-17
逆变换误差
0

考虑到matlab的for循环效率大约是C语言的几十分之一,则和Matlab的fft相比,消耗的时间在一个数量级上。

你可能感兴趣的:(算法与数据结构,算法)