Python中利用FFT(快速傅里叶变换)进行频谱分析

Posted Mobius8086

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python中利用FFT(快速傅里叶变换)进行频谱分析相关的知识,希望对你有一定的参考价值。

本文将从实例的角度出发讲解fft函数的基本使用,不包含复杂的理论推导。

一、基本条件

要对一个信号进行频谱分析,首先需要知道几个基本条件。

  1. 采样频率fs
  2. 信号长度N(信号的点数)

采样频率fs:根据采样定理可知,采样频率应当大于等于被测信号里最高频率的2倍,才能保证不失真,但是实际情况下,我们可能并不知道最高频率是多少,所以这个就是根据一定的经验或者搜索得到的,比如本次所使用到的ECG(心电)信号,最高频率一般不高于100Hz,于是我们设置采样频率为500(原本200Hz就够了,但是实际工程一般会将标准放大3~5倍,以便留有一定的裕量)。

信号长度N:这个一般很容易获得,因为我们经过采样得到的信号都是离散信号,如果是一维的,只需要使用len函数就可以直接获得信号的点数。

二、设计代码实例

在Python的第三方库文件中,numpy和scipy都有fft的函数,本文使用scipy中的fft函数。

from scipy.fftpack import fft

打开fft函数的源文件,可以看到如下内容:

def fft(x, n=None, axis=-1, overwrite_x=False):
    """
    Return discrete Fourier transform of real or complex sequence.

    The returned complex array contains ``y(0), y(1),..., y(n-1)``, where

    ``y(j) = (x * exp(-2*pi*sqrt(-1)*j*np.arange(n)/n)).sum()``.

    Parameters
    ----------
    x : array_like
        Array to Fourier transform.
    n : int, optional
        Length of the Fourier transform. If ``n < x.shape[axis]``, `x` is
        truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
        default results in ``n = x.shape[axis]``.

阅读一下可以得到输入参数的信息:

x:待进行FFT的信号序列。

n:可选,傅里叶变换的点数,如果n的长度小于序列x长度,则x将会被截断(truncated),如果n大于序列长度,则序列将会补零,默认是两者相等。

于是,我们开始对fft函数根据自己的需要进行函数封装编写:

def FFT(Fs, data):
    """
    对输入信号进行FFT
    :param Fs:  采样频率
    :param data:待FFT的序列
    :return:
    """
    L = len(data)  # 信号长度
    N = np.power(2, np.ceil(np.log2(L)))  # 下一个最近二次幂,也即N个点的FFT
    result = np.abs(fft(x=data, n=int(N))) / L * 2  # N点FFT
    axisFreq = np.arange(int(N / 2)) * Fs / N  # 频率坐标
    result = result[range(int(N / 2))]  # 因为图形对称,所以取一半
    return axisFreq, result

代码解读:

L是输入序列的长度,很容易理解并获得。

N为大于序列长度的第一个2的幂次数,比如3之后第一个2的幂次数为,5之后的第一个2的幂次数为,因为FFT变换一般选择2的幂次进行,但是计算机计算的话,我们也可以不必如此费事,反正不是我们自己手算。

傅里叶变换的核心代码在第三行,abs是取模运算,因为FFT返回的数据是复数,为了便于绘图,需要求模进行,至于为什么后面又除以L又乘以2,是为了保证变换前后的幅值和变换前一致,比如的信号,如果不进行这一步的话,得到的FFT结果在频率为1的那个“柱子”幅度就不是2,而是一个很大的数,会发生变化,至于为什么,这里理论不去深究。

第四行代码时获取绘图所用的频率x轴,Fs/N是频率分辨率,乘以N个点就可以获得一个序列,这个序列就是每个频率分布点的x轴坐标点,N/2是为了截取一半,我们知道FFT绘制出来的图形是左右对称的,因此这里只取前一半,下面一行代码也是一样,取前一半。

举例说明:通过手动设计被测信号和采样频率等条件,掌握对库函数fft的使用和理解,以便迁移到实际工程中。下面这个例子中,我们设计了一个信号频率为390和2000Hz的正弦叠加信号,该信号的样子如图1所示,对其进行FFT之后如图2所示。

import numpy as np
from scipy.fftpack import fft
import matplotlib.pyplot as plt


def FFT(Fs, data):
    """
    对输入信号进行FFT
    :param Fs:  采样频率
    :param data:待FFT的序列
    :return:
    """
    L = len(data)  # 信号长度
    N = np.power(2, np.ceil(np.log2(L)))  # 下一个最近二次幂,也即N个点的FFT
    result = np.abs(fft(x=data, n=int(N))) / L * 2  # N点FFT
    axisFreq = np.arange(int(N / 2)) * Fs / N  # 频率坐标
    result = result[range(int(N / 2))]  # 因为图形对称,所以取一半
    return axisFreq, result


if __name__ == '__main__':
    Fs = 10000  # 采样频率
    f1 = 390  # 信号频率1
    f2 = 2000  # 信号频率2
    t = np.linspace(0, 1, Fs)  # 生成 1s 的时间序列
    # 给定信号
    y = 2 * np.sin(2 * np.pi * f1 * t) + 5 * np.sin(2 * np.pi * f2 * t)
    # 第一步,对没有添加噪声的信号进行FFT,验证分析是否正确
    x, result = FFT(Fs, y)

    # 绘图
    fig1 = plt.figure(figsize=(16, 9))
    plt.title('original data')
    plt.plot(t, y)
    plt.xlabel('time/s')
    plt.ylabel('Amplitude')
    plt.grid()

    fig2 = plt.figure(figsize=(16, 9))
    plt.title('FFT')
    plt.plot(x, result)
    plt.xlabel('Frequency/Hz')
    plt.ylabel('Amplitude')
    plt.grid()
    plt.show()

输出结果如下:

图1. 原始信号
图2. 对信号进行FFT

结果分析:从FFT的结果我们可以看到,在频率为390到2000Hz的地方有两个凸起的“高峰”,说明原信号在此处的频率占据比重较大,而且FFT的结果也满足原信号的幅值大小,即390Hz信号的幅度为2,2000Hz的信号幅度为5。


如果我们对信号进行加噪,可以查看FFT分析结果。

噪声信号选择随机信号,注意,点数要和被分析的信号的点数保持一致。

# 0-1 之间的随机噪声乘以10倍,也即0-10之间的噪声
noise1 = 10*np.random.random(10000)  
图3. 没有加入噪声的FFT频谱
图4.加入噪声之后的FFT频谱

 从图中可以看到的是,加入的噪声信号有直流成分(0Hz),其他横坐标上密密麻麻分布的是一些零碎的频率信号。

参考文章:

Python 中 FFT 快速傅里叶分析 - 知乎

以上是关于Python中利用FFT(快速傅里叶变换)进行频谱分析的主要内容,如果未能解决你的问题,请参考以下文章

傅里叶变换通俗解释及快速傅里叶变换的python实现

Delphi如何在三轴加速器的频谱分析中使用FFT(快速傅里叶变换)算法

fft窄带高分辨率算法

FFT(快速傅里叶变换)

Matlab---傅里叶变换---通俗理解

Matlab---傅里叶变换---通俗理解