FFT之后那些事情2
n年前发过一篇文章,FFT之后那些事情,介绍了fft
之后幅值要怎么处理的问题,这里对这个问题进行进一步详细介绍,实现一个完全正确的频谱,尤其是0频率和Nyquist频率分量(最后一个频率)要如何正确的处理,否则你做出来的频谱,总会和正确的频谱有一点点差距
首先一个完整的频谱(工程上使用的频谱)代码是这样的:
本文只讨论幅值谱的处理方式
下面就以这个频谱分析的函数,介绍为何要做那么多特殊处理
def spectrum_analysis(waveform, sampling_rate, fftsize=None):
'''
频谱分析
:param waveform: 输入波形数据,可以是一维数组或列表
:param sampling_rate: 采样率
:param fftsize: FFT变换的窗口大小,默认为None,表示使用波形的长度
:return: 频谱分析结果,包含频率、振幅和相位三个部分
:rtype:
'''
if fftsize is None:
fftsize = len(waveform) # 如果没有指定fftsize或者fftsize为None,则使用波形的长度
# 如果fftsize大于波形长度,对波形进行补零
if fftsize > len(waveform):
waveform = np.pad(waveform, (0, fftsize - len(waveform)))
# 如果fftsize小于波形长度,截取波形
elif fftsize < len(waveform):
waveform = waveform[:fftsize]
# 对波形执行FFT变换
fft_values = np.fft.rfft(waveform)
# 获取FFT结果的频率值
freq = np.fft.rfftfreq(fftsize, 1.0 / sampling_rate)
# 计算频谱的振幅
amplitudes = np.abs(fft_values) / fftsize # 先不进行乘以2的操作
# 对非直流分量和非Nyquist分量的振幅乘以2
# 直流分量是第一个元素,如果fftsize是偶数,Nyquist分量是最后一个元素
if fftsize % 2 == 0: # 偶数个点
amplitudes[1:-1] *= 2 # 忽略直流分量和Nyquist分量
else: # 奇数个点,没有Nyquist分量
amplitudes[1:] *= 2 # 只忽略直流分量
return freq,amplitudes
这里有两个地方说明:
1、fftsize这个参数,既然已经有波形了,为何还要设置一个fftsize?
2、幅值的特殊处理,为什么要取模乘以2再除以长度,以及对于直流分量(freq[0])和Nyquist频率分量为何要单独处理
fftsize有何用?波形长度难道不行吗
所有fft函数都可以指定一个fftsize参数,如NumPy的FFT模块中,np.fft.rfft 函数的 n 参数允许你指定FFT变换的点数。这个参数对于控制FFT的计算精度和性能是有用的,你可以设置比原来波形短,也可以设置比原来波形长,也可以和波形长度一样,它主要有如下作用:
1. 裁剪或补零输入:
当FFT长度小于输入波形长度时,裁剪输入可以减少FFT需要处理的数据量,从而提高计算速度。但这样做可能会丢失波形中的一些信息。
当FFT长度大于输入波形长度时,通过补零来增加数据点的数量。这样做通常不会增加频谱分辨率(因为实际的信号信息没有增加),但可以使频谱看起来更加平滑,并可能有助于在图形化展示时更清楚地识别出峰值。
2. 控制频率分辨率:
FFT将信号从时域转换到频域,结果是一系列表示不同频率分量的复数。这些复数的模表示该频率分量的幅度,而幅角表示相位。
FFT的长度(即点数n)决定了频率分量的数量。更长的FFT会产生更多的频率分量,从而提供更精细的频率分辨率。但要注意,增加FFT长度并不一定会增加频谱的精度或信息量,它只是将现有的频谱信息分布到更多的点上。
3. 性能优化:
许多FFT库(如FFTW、KissFFT等)都针对特定长度的FFT进行了优化,特别是当长度为2的幂时。在这些情况下,FFT算法可以利用分治策略(如Cooley-Tukey FFT算法)更高效地计算变换。
4. 避免频谱泄漏:
如果信号包含周期性成分(如正弦波),且FFT的长度恰好是该周期的整数倍,则FFT结果将在该频率处产生一个尖锐的峰值,而其他频率处的值将接近零。这有助于更准确地识别和测量信号中的周期性成分。
如果FFT长度不是信号周期的整数倍,则会导致频谱泄漏,即信号的能量会泄漏到其他频率上,使得频谱分析变得困难。选择合适的FFT长度(特别是与信号周期相匹配的长度)可以减少这种泄漏。
因此,上面的频谱分析函数提供了fftsize,可以根据需求来传入fftsize
幅值为什么要取模乘以2再除以长度,以及对于直流分量(freq[0])和Nyquist频率分量为何要单独处理
计算频谱的振幅时,使用rfft值取模、乘以二再除以fftsize的做法是基于FFT(快速傅里叶变换)的性质和信号处理的需要。
FFT返回的是复数数组,每个复数表示对应频率分量的幅度和相位。在计算频谱振幅时,通常只关心幅度信息,因此需要对每个复数取模(即计算复数的绝对值),这样得到的就是该频率分量的振幅。
为何要乘以2
对于实数信号的FFT分析,由于信号的对称性(实数信号的FFT结果是共轭对称的),我们通常只需要考虑正频率部分(对于np.fft.rfft来说,它只返回正频率部分的结果)。为了补偿由于只考虑正频率部分而丢失的能量,我们要将振幅乘以2(除了直流分量和Nyquist频率分量,它们不是成对出现的)。这样做是为了保证总能量在时域和频域之间保持一致。
直流分量(freq[0])和Nyquist频率分量为何要单独处理
在FFT(快速傅里叶变换)分析中,对于实数信号的频谱,通常只考虑正频率部分,因为实数信号的FFT结果是共轭对称的。然而,在振幅的计算中,需要特别注意两个特殊的频率分量:直流分量(DC分量,对应于频率为0的分量)和Nyquist分量(如果采样点数为偶数,则存在;对应于采样频率的一半)。为啥要特别注意呢,因为这两个不是对称的,就是因为不是对称,因此需要特殊处理
如果不进行特殊处理,直接将除了直流分量以外的所有振幅乘以2,那么直流分量和Nyquist分量的振幅将会被错误地加倍,从而导致频谱分析的结果不准确。
直流分量
直流分量是信号中的平均值或常数部分,在FFT结果中对应于频率为0的点。由于直流分量不是成对出现的(没有对应的负频率分量),因此在计算振幅时不需要将其振幅乘以2。
Nyquist频率分量
当采样点数为偶数时,FFT结果中会包含一个Nyquist分量,其频率为采样频率的一半。和直流分量一样,Nyquist分量也是独特的,没有对应的负频率分量。因此,在计算振幅时,同样不应该将其振幅乘以2。
Nyquist分量,也被称为Nyquist频率成分,是在进行数字信号处理时,特别是进行离散傅里叶变换(DFT)或快速傅里叶变换(FFT)时遇到的一个特殊频率成分。Nyquist分量是以美国电信工程师Harry Nyquist的名字命名的,他首先提出了采样定理,该定理说明了为了无失真地还原一个模拟信号,采样频率必须至少是信号中最高频率成分的两倍。这个两倍的频率就被称为Nyquist频率,说白了就是信号中接近或等于采样频率一半的成分。
在FFT分析中,如果采样点数为偶数,Nyquist分量会出现在FFT结果的最后一个点,其对应的频率正好是采样频率的一半,也就是Nyquist频率。这个分量代表了信号中接近或等于Nyquist频率的成分。由于FFT的周期性,Nyquist分量没有对应的负频率分量,因此在处理FFT结果时需要对它进行特殊处理。如果不正确处理,可能会导致频谱分析中的误差。