65.9K
CodeProject 正在变化。 阅读更多。
Home

离散傅里叶变换用于频率分析

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2014年10月13日

CPOL

8分钟阅读

viewsIcon

32304

downloadIcon

513

在本文中,我们将探讨离散傅里叶变换的基础知识,以及它如何用作信号频率分析的工具。

引言

傅里叶分析通常关注函数的分析和合成。将信号分解为易于分析的组件,并从这些组件进行重构。

在本文中,我们将讨论离散时间信号的傅里叶分析。

离散时间信号仅在特定的时间点定义,并表示为具有连续值范围的实数序列。

信号的傅里叶级数表示

离散时间的复指数信号本质上是周期性的。

\( e^{j k\omega (n+N)} = e ^{j k \omega n} \)
其中  , \(\omega=\frac{2\pi}{N}\)

所有周期为 N 的离散时间复指数信号集为 \(\phi_k[n] =  e ^{j k \frac{2\pi}{N} n} , \forall k \in \mathcal{Z}\)

 

$\phi_{k+rN}[n] = e ^{j (k+rN) \frac{2\pi}{N} n}  = e ^{j k \frac{2\pi}{N} n}  * e ^{j r N \frac{2\pi}{N} n}$
$\phi_{k+rN}[n] = e ^{j k \frac{2\pi}{N} n} e ^{j r  2\pi n} =e ^{j k \frac{2\pi}{N} n}$

当 k 改变整数倍 N 时,我们得到相同的序列。

因此,只有 N 个 k 值可以定义唯一的离散时间复指数序列。

$ x[n]=\sum_{k} x[k] e^{j k \omega n} $

\(e^{j k \omega n}\) 仅在 k 的 N 个连续值范围内是唯一的,求和仅在此范围内考虑。

$ x[n]=\sum_{k=<N>} x[k] e^{j k \omega n} $

(x[n]) 具有周期 N。

离散时间傅里叶级数

我们考虑一个周期为 N 的任意周期信号 (x[n]),并且 (x[n]) 可以表示为:

$x[n]=\sum_{k} X[k] \phi_{k}[n] $

因此,我们需要确定 (x[n]) 是否可以表示为上述形式,如果可以,则找到使此表示有效的系数 (x[k])。

所有信号处理参考资料中都可以找到相同的理论推导。结果如下:

$ X[k] =\sum_{n=0}^{N-1}x[n] e^{- j k \frac{2\pi}{ N} n } $

${x[n] = \frac{1}{N}\sum_{k=0}^{N-1}X[k]\,e^{j k \frac{2\pi}{ N} n }}$

(X[k]) 称为傅里叶级数系数。这些系数也称为信号 (x[n]) 的频谱系数。

信号 (x[n]) 的频谱系数表示称为信号的傅里叶级数表示

这些系数指定了信号 (x[n]) 分解为 N 个谐波相关的复指数之和。

任何周期性的离散时间信号 (x[n]) 都可以使用傅里叶级数表示来表示,傅里叶级数表示使我们能够将任何周期信号表示为复指数的加权和。

让我们看一些例子来理解傅里叶级数表示能提供什么信息。

```
def FourierSeries(input,N=None):
    """ computes the fourier series coefficients of input signal
    
    Parameters
    -----------
    input : numpy array
            input discrete time signal
            
    Returns
    --------
    out : complex,list
          fourier series coefficients
    """
    
    N=len(input);

    w=2*cmath.pi/N;
    input=input[0:N];
    n=numpy.arange(0,N);    
    r=cexp(-1j*w*n);

    output = [complex(0)] * N    
    for k in range(N):        
        r=input*cexp(-1j*w*n*k)          
        output[k]=np.sum(r);
        
   
    return output;

```

我们考虑一个频率为 (f_{s}=10Hz) 的正弦信号,持续时间为 1 秒,并以 (F_{s}=150Hz) 对波形进行采样,然后观察信号的傅里叶级数系数。

在频率谱中,仅考虑余弦波形的单个周期,即 (Fs/f_{s}=15) 个样本。结果可以在子图 2 中看到。

如果我们获取完整的信号并计算傅里叶级数系数,结果可以在子图 3 中看到。

让我们考虑傅里叶级数的幅度图。

```
def FourierSinusoids(F,w,Fs,synthesis=None):    
    """ the function generates a discrete time sinusoid,computes
    the fourier series coefficients and plots the time and frequency
    data 
 
     Paraneters
     ----------
     F : numpy array
         sinuoidal frequency components
         
     w  : numpy array
          the weights associated with freuency components
         
     Fs : Integer
          sampling frequency
          
     synthesis : int
                 if 1 ,reconstructs signal and plots the original and reconstructed
                 signal
 
    """
    if synthesis==None:
        synthesis=0;
        
    Ts=1.0/Fs;   
    xs=numpy.arange(0,1,Ts) 
    
    signal=numpy.zeros(np.shape(xs));
    for i in range(len(F)):
        omega=2*np.pi*F[i];
        signal = signal+ w[i]*numpy.cos(omega*xs);
    #plot the time domain signal    
    subplot(2,1,1)
    plt.plot(range(0,len(signal)),signal)
    xlabel('Time')
    ylabel('Amplitude')
    title('time doman')
    #plt.ylim(-2, 2)
    
    #compute fourier series coefficients
    r1=FourierSeries(signal)
    a1=cabs(r1)
    
    if synthesis==0:
        #plot the freuency domain signal
        L=len(a1);
        fr=np.arange(0,L);
        subplot(2,1,2)
        plt.stem(fr,a1,'r') # plotting the spectrum
        xlabel('Freq (Hz)')
        ylabel('|Y(freq)|')
        title('complete signal')
        ticks=np.arange(0,L+1,25);
        plt.xticks(ticks,ticks);     
        show() 
        
    if synthesis==1:
        rsignal=IFourierSeries(r1);
        print np.allclose(rsignal, signal)    
        subplot(2,1,2) 
        plt.stem(xs,signal)
        xlabel('Time')
        ylabel('Amplitude')
        title('reconstructed signal')
        show() 

if __name__ == "__main__":     

    mode =0
    
    if mode==0:
        F=[10];
        F=np.array(F);
        w=numpy.ones(F.shape);
        #plot the time domain signal and fourier series component
        FourierSinusoids(F,w,150);
```

我们可以看到,在对应于 10Hz 的频率处观察到峰值。傅里叶级数系数的总数等于输入样本的总数。

现在我们考虑两个频率为 10Hz 和 15Hz 的正弦信号的组合。该信号的周期是 10 和 15 的 LCM,即 5Hz。

```
    if mode==1:
        F=[10,20];
        F=np.array(F);
        w=numpy.ones(F.shape);
        FourierSinusoids(F,w,150); 
```

现在我们继续添加频率分量。下面的信号周期为 1 秒。

```

    if mode==2:
        F=range(1,10);
        F=np.array(F);
        w=numpy.ones(F.shape);        
        FourierSinusoids(F,w,150);    
```

信号合成

在上一节中,我们看到了将周期信号分解为其频率分量的过程。

现在我们将讨论从傅里叶级数系数合成信号

${x[n] =\frac{1}{N}\sum_{k=0}^{N-1}X[k]\,e^{j k \frac{2\pi}{ N} n }}$

下方是原始信号和重构信号的图以及相应的 Python 代码。

```
def IFourierSeries(input):
    """ function reconstructs the signal from fourier series coefficients
    
    Parameters
    ---------
    input : cmath list
            fourier series coefficients    
    
    Returns
    -----------
    out : numpy arrary
          reconstructed signal
    
    """
    N=len(input);
    w=2*cmath.pi/N;
    k=numpy.arange(0,N);    
    output = [complex(0)] * N   
    for n in range(N):  
        r=input*cexp(-1j*w*n*k);
        output[n]=np.mean(r);

    print output.__class__    
    return output;

    if mode==3:
        F=range(1,10);
        F=np.array(F);
        w=numpy.ones(F.shape);
        FourierSinusoids(F,w,150,1); 
```

离散傅里叶变换

现在出现了一个问题,对于非周期信号,我们该怎么办?经过对离散时间傅里叶变换和频率域采样的大量理论分析,我们发现只需假设非周期信号的周期扩展,然后按照上述方法计算傅里叶级数。

周期信号的傅里叶级数系数也是周期性的,周期相同为 N。

$X[k+N]=X[k]$

如果我们考虑傅里叶级数系数的单个周期 N 个值,我们就得到一个有限长度的序列。
这被称为离散傅里叶变换 (DFT)。

因此,通过计算 DFT,我们得到单个周期的傅里叶级数系数。

选择信号的周期取决于我们。我们考虑一个长度为 150、占空比为 5 的非周期冲激信号。

我们考虑 N=150, 450 并观察结果。

当增加信号的周期时,我们可以看到频率域的分辨率增加。

当 $N -> \infty$ 时,频率域的采样点会越来越密集。如果频率域的采样点无限密集,则可以将其视为连续信号。

这就得到了信号的离散时间傅里叶变换表示

```
def FourierRect(N):     
        """ the function generates rectangular aperiodic pulse and computer the DFT coefficients
        
        Parameters
        ----------
        N : period of aperiodic signal
             
        """
        x = np.zeros((1,N))
        x[:,0:30]=1;
        x=x.flatten();
    
        
        #compute the DFT coefficients
        r1=FourierSeries(x)
        #magnitude of DFT coefficients
        a1=cabs(r1)

        #plot the time domain signal
        subplot(2,1,1)
        plt.plot(range(0,len(x)),x)
        xlabel('Time')
        ylabel('Amplitude')
        title('time doman')
        plt.ylim(-2,2);
        
        #plot the DFT coefficients
        L=len(a1);
        fr=np.arange(0,L);
        subplot(2,1,2)
        plt.stem(fr,a1,'r') # plotting the spectrum
        xlabel('Freq (Hz)')
        ylabel('|Y(freq)|')
        title('complete signal')
        ticks=np.arange(0,L+1,25);
        plt.xticks(ticks,ticks);     
        show() 

    if mode==4:
       FourierRect(150);

    if mode==5:
       FourierRect(150*3);        
```
$ X(k) = \sum x[n] exp(-j\omega k n)  $

相邻采样点之间的距离是 (\omega ),当 (w \rightarrow ;0) 时,采样点无限密集。

$X(w) = \sum x[n] exp(-j\omega  n)$

傅里叶变换本质上是连续的,不能用于数值计算。

离散傅里叶变换是信号的离散时间傅里叶变换的采样版本,并且是以适合在信号处理单元上进行数值计算的形式。

快速傅里叶变换 (FFT) 是一种计算离散傅里叶变换 (DFT) 及其逆运算的算法。它是计算信号 DFT 的有效方法。

我们将使用 Python 的 FFT 例程,并将其性能与朴素实现进行比较。

使用内置 FFT 例程: elapsed time was 6.8903e-05 seconds
使用朴素代码: elapsed time was 0.0653119 seconds

我们可以看到大约 1000 倍的性能提升。

朴素算法的复杂度为 (o(N^2)),FFT 算法的复杂度为 (O(N log N))。

```
    if mode==6:
        Fs=150;
        F=range(1,10);
        F=np.array(F);        
        w=numpy.ones(F.shape);

        
        Ts=1.0/Fs;   
        xs=numpy.arange(0,1,Ts) 
    
        signal=numpy.zeros(np.shape(xs));
        for i in range(len(F)):
            omega=2*np.pi*F[i];
            signal = signal+ w[i]*numpy.cos(omega*xs);        
            
            
        start_time = time.time()
        FourierSeries(signal)
        end_time = time.time()
        print("Elapsed time naive algo  %g seconds" % (end_time - start_time)) 

        start_time = time.time()
        fft(signal)
        end_time = time.time()
        print("Elapsed time of fft algo  %g seconds" % (end_time - start_time)) 
```

循环卷积与 DFT

序列的循环移位

\(x[n]\) 表示有限长度的时域序列。

当我们进行 DFT 时,时域实际上是构建了信号的周期扩展。

因此,信号的时域移位实际上意味着信号的循环移位。

$x[n] \Leftrightarrow X[k] $
$x[(n-n_{o})_{N}]  \Leftrightarrow e^{-j k \omega n_{o}} X[k]$

信号处理中最基本应用之一是线性卷积。

它可用于表示离散时间 LTI 系统的输出、相关和互相关、
滤波以及许多其他信号处理操作。

离散时间系统的线性卷积表示为:

$y[n]=\sum_{i} x[i] h[n-i]$

信号 (x[n]) 和 (h[n]) 是有限长度的离散时间信号。

两个长度分别为 M 和 L 的序列的线性卷积会产生一个长度为 M+L-1 的序列。

一个相关的离散时间傅里叶变换性质是:

$Y\left(\Omega\right) = X\left(\Omega\right)H\left(\Omega\right)$

我们考虑两个序列,计算它们的 DFT,相乘,然后进行逆 DFT。

`时域的循环卷积导致频域中 DFT 系数的乘法。`

我们取序列 `[1,1,1,1,1]` 和 `[1,2,3,4]`。

The result of linear convolution is `[ 1  3  6 10 10  9  7  4 ]`
The result of inverse of product of DFT coefficients is `[ 10.  10.  10.  10.  10.]`

我们对信号进行 N 点 DFT,N=max(M,L)。

...|1 1 1 1 1|1 1 1 1 1|1 1 1 1 1|....
...|1 2 3 4 0|1 2 3 4 0|1 2 3 4 0|....

Result 
...|10|10|10| ...

进行 DFT 会导致时域的周期性,现在我们像往常一样进行卷积。\(h[n-n_{o}]\) 将被循环移位。

...|1 1 1 1 1|1 1 1 1 1|1 1 1 1 1|....
...|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4|....

Result 
...|10|10|10| ...

 

...|1 1 1 1 1|1 1 1 1 1|1 1 1 1 1|....
...|4 0 1 2 3 |4 0 1 2 3|4 0 1 2 3|....

Result 
...|10|10|10| ...

经过 N 次移位后,序列会重复。

因此,我们执行 N 次移位等同于序列在时域的周期进行卷积。

Circular convolution
[ 10.  10.  10.  10.  10.]

循环移位是 DFT 引入的周期性的结果,并导致循环卷积运算。

我们可以将此归因于频率域采样引入的时间混叠。

采样定理指出,为了避免频率域的混叠,采样率必须大于被采样信号最高频率的两倍。

我们之前提到 DFT 是 DTFT 的采样版本。问题是如何提高采样率以避免时域混叠。

我们发现对序列进行零填充会导致傅里叶级数的采样点更密集。这相当于在频率域增加了 DTFT 的采样率。

这让我们直观地认为,如果我们对序列进行零填充,它将导致信号的 DTFT 采样率增加。

为了使循环卷积的结果等于线性卷积,我们只需将序列零填充到 M+L-1 的长度。

...|1 1 1 1 1 0 0 0 0 |1 1 1 1 1 0 0 0 0 |
...|1 2 3 4 0 0 0 0 0 |1 2 3 4 0 0 0 0 0 |

result : 10

...|1 1 1 1 1 1 0 0 0 |1 1 1 1 1 0 0 0 0 |
...|0 1 2 3 4 0 0 0 0 |0 1 2 3 4 0 0 0 0 |

result : 10

...|1 1 1 1 1 1 0 0 0 |1 1 1 1 1 0 0 0 0 |
...|0 0 1 2 3 4 0 0 0 |0 0 1 2  3 4 0 0 0 |

result : 9

因此,我们可以看到,由于零填充,循环移位的分量为零,得到的结果等同于线性卷积结果。

上述示例的代码如下。

```
    if mode == 7:
        x=np.array([1,1,1,1,1]);
        h=np.array([1,2,3,4,0]);
        r=np.convolve(x,h)
        print 'Linear convolution'
        print r

        #take 5 point DFT of the sequence
        f1=fft(x,5);
        f2=fft(h,5);
        s=ifft(f1*f2);
        print 'Circular convolution'
        print abs(s)
        
        #take 9 point DFT of sequence
        f1=fft(x,9);
        f2=fft(h,9);
        s=ifft(f1*f2);
        print 'zero padded Circular convolution'
        print r
```

代码

所有示例的代码都可以在 GitHub 存储库中找到:
https://github.com/pi19404/pyVision/tree/master/pySignalProc/tutorial/fourierSeries.py

您可以在文件中将 mode 变量从 1-7 更改,以生成文章中显示的所有图。

文件 fourierSeries.py 也可以从以下链接下载:

© . All rights reserved.