zoukankan      html  css  js  c++  java
  • 信号处理——信号频域变换

    作者:桂。

    时间:2017-03-07  18:40:05

    链接:http://www.cnblogs.com/xingshansi/p/6516256.html 

    声明:欢迎转载,不过记得注明出处哦~


    前言

     本文主要介绍信号频域变换的常用方式,主要介绍基本的三种方法,实现时域数字信号的傅里叶变换:

      1)基本DFT变换;

      2)FFT变换

      3)逆序级联FFT

    三种方法实现方式略有差别,但本质完全相同。为了方便大家理解,本文给出基本MATLAB实现。内容的理论部分,多有借鉴他人,相应链接在最后一并给出。

     〇、写在前面

    关于信号时域连续、离散以及各自频域的对应关系,可以参考之前的一篇博文。为了便于理解,此处给一个DTFT—>DFT的解释。

    MATLAB代码:

    %DTFT
    clc;clear all;close all
    N=8;                          %原离散信号有8点
    n=[0:1:N-1]    ;              %原信号是1行8列的矩阵
    xn=0.5.^n;                    %构建原始信号,为指数信号
    
    w0=[-2*N:.1:2*N]*2*pi/N;       %方便观察,此处取4个周期-4*pi~4*pi  
    X0=xn*exp(-j*(n'*w0));         %求dtft变换,采用原始定义的方法,对复指数分量求和而得
    subplot(221)
    plot(n,xn,'r');hold on;xlabel('时间')
    stem(n,xn,'k');grid on;
    title('时域采样后的信号(指数信号)');ylabel('幅值')
    subplot(222);
    plot(w0,abs(X0),'k');ylabel('幅值');xlabel('角频率');%逼近连续,其实这么处理不正确,仅仅是方便理解
    title('DTFT变换');
    xlim([-4*pi,4*pi]);grid on;
    
    %DFT
    w=[-2*N:1:2*N]*2*pi/8;      
    X=xn*exp(-j*(n'*w));         %对DTFT进行频域采样
    subplot(223)
    stem(n,xn,'k');ylabel('幅值');xlabel('时间');grid on;
    title('频域采样后的信号(指数信号)');
    subplot(224);
    plot(w0,abs(X0),'r');hold on;
    stem(w,abs(X),'k');ylabel('幅值');xlabel('角频率')
    title('DFT变换');
    xlim([-4*pi,4*pi]);grid on;
    

      对应结果图:

    一、基本DFT变换

    DFT变换有:

    $F(k) = sum^{N-1}_{n=0} f(n) e^{frac{-j2pi kn}{N}}$

    写成矩阵的形式:

    对应的转换为VanderMonde矩阵,对应的MATLAB代码:

    clc;clear all;close all;
    fs = 200;
    f0 = 30;
    t = 0:1/fs:2;
    x = sin(2*pi*f0*t');
    N = length(x);
    %DFT
    X_DFT = exp(-j*2*pi/N).^([0:N-1]'*[0:N-1])*x;
    %绘图
    xf = linspace(0,fs,N);
    plot(xf,abs(X_DFT),'k');grid on;
    xlabel('频率(Hz)');
    ylabel('幅度');
    title('DFT结果');
    

      也可以将DFT的代码换为Vander矩阵形式,只需修改一句:

    % X_DFT1 = exp(-j*2*pi/N).^([0:N-1]'*[0:N-1])*x;
    %修改为下句
    X_DFT = fliplr(vander(exp(-j*2*pi/N).^([0:N-1]))).'*x;

    结果图:

    二、FFT变换

     DFT到FFT常用的有奇偶抽取/前后抽取,两种实现方式,原理此处不再堆砌,可以参考维基百科:

    给出对应的MATLAB代码:

    clc;clear all;close all;
    fs = 200;
    f0 = 30;
    t = 0:1/fs:2;
    x = sin(2*pi*f0*t');
    N = length(x);
    %FFT
    X_FFT = fft(x);
    %绘图
    xf = linspace(0,fs,N);
    plot(xf,abs(X_FFT),'k');grid on;
    xlabel('频率(Hz)');
    ylabel('幅度');
    title('FFT结果');
    

    结果图:

    如果严格按照FFT定义,则应该将序列补为$2^N$长度(事实上,内置fft自动完成此操作),代码修改为:

    clc;clear all;close all;
    fs = 200;
    f0 = 30;
    t = 0:1/fs:2;
    x = sin(2*pi*f0*t');
    N = length(x);
    %FFT
    Len = 2^nextpow2(length(x));
    X_FFT = fft(x,Len);
    %绘图
    xf = linspace(0,fs,Len);
    plot(xf,abs(X_FFT),'k');grid on;
    xlabel('频率(Hz)');
    ylabel('幅度');
    title('FFT结果');
    

      对应结果图:

    三、逆序级联FFT

    为了方便,假设信号$f(n)$长度为2的整次幂。对于数据量过大的情况,可以通过串/并的不断转化,对数据进行分流,从而利用多个芯片实现一个大芯片的任务(此处为个人理解,仅供参考)。给出对应的流图:

    以及具体的推导公式:

    步骤可以简化为:分段FFT,即公式[.]部分—>因子补偿,即公式{.}部分—>分段FFT,即公式最外层部分。

    给出具体的实现代码:

    clc;clear all;close all;
    fs = 200;
    f0 = 30;
    t = 0:1/fs:2;
    x = sin(2*pi*f0*t');
    Na = 2^nextpow2(length(x));
    sig = [x;zeros(Na-length(x),1)];
    N=16;M=Na/N;
    sr=zeros(M,N);
    for m=1:M
        sr(m,:)=sig((m-1)*N+1:m*N,1);
    end
    for n=1:N
        sr(:,n)=fft(sr(:,n));
    end
    P=zeros(M,N);
    for p=0:M-1
        for n=0:N-1
            P(p+1,n+1)=exp(-j*2*pi*n*p/M/N);
        end
    end
    sr=sr.*P;
    for m=1:M
        sr(m,:)=fft(sr(m,:));
    end
    %save result
    X_CaFFT = zeros(length(sig),1);
    for n=1:N
        X_CaFFT((n-1)*M+1:n*M,1)=sr(:,n);
    end
    %绘图
    xf = linspace(0,fs,Na);
    plot(xf,abs(X_CaFFT),'k');grid on;hold on;
    xlabel('频率(Hz)');
    ylabel('幅度');
    title('CaFFT结果');
    

      对应结果图:

    四、三种算法对比分析

    三种结果效果完全等价,分析其各自性能。

    对于快速运算中,一个蝶形运算,对应一个乘法,两次加法。

      DFT FFT CaFFT
    加法器 $N^2$ $N log_2N$ $N log_2N+N$
    乘法器 $N(N-1)$ $2Nlog_2N$ $2Nlog_2N$

    可见FFT所需资源最少,但如果数据量过大导致直接FFT,硬件无法满足条件,则CaFFT是最优的选择。

    参考:

    郑君里:《信号与系统》第二版;

    张大炜:一种新的级联FFT算法.

  • 相关阅读:
    C++学习之路: share_from_this<T>类的使用
    Linux学习: TCP粘包问题
    C++学习之路: 线程封装(基于对象编程)
    js数组方法
    React 性能优化
    HelloWorld
    设置表格边框的通用写法
    用于项目的SQL写法
    添加服务,用于定期执行某个程序或者应用程序(windows service)
    sql中除法,保留小数点位数
  • 原文地址:https://www.cnblogs.com/xingshansi/p/6516256.html
Copyright © 2011-2022 走看看