FFT归一化

6

我知道这个问题已经被问了很多次,但是不知怎么的我无法使它正常工作。我创建了一个振幅为1的440 Hz正弦波。现在,在FFT之后,440 Hz处的频谱有一个明显的峰值,但是它的值并不正确。我期望看到0 dB,因为我处理的是振幅为1的正弦波。相反,计算出来的功率远高于0 dB。我使用的公式很简单:

for (int i = 0; i < N/2; i++) 
{  
    mag = sqrt((Real[i]*Real[i] + Img[i]*Img[i])/(N*0.54)); //0.54 correction for a Hamming Window

    Mag[i] = 10 * log(mag) ;      
}

我应该指出,时域中的总能量等于频域中的能量(Parseval定理),所以我知道我的FFT程序是正确的。任何帮助都非常感激。

2
正确计算分贝的方法是20*log10,如果在执行FFT之前使用汉明窗口可能会导致分贝刻度不正确。 - ederwander
3个回答

7

我在工作中又遇到了这个问题。似乎很多软件例程/书籍在FFT的规范化上有些松散。 我的最佳总结是:能量需要被保留——这就是Parseval定理。而且在Python编码时,你很容易失去一个元素而不知道它。请注意,numpy.arrays索引不包括最后一个元素。

a = [1,2,3,4,5,6]
a[1:-1] = [2,3,4,5]
a[-1] = 6

以下是我用于正确标准化FFT的代码:

# FFT normalization to conserve power
    
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal
    
    
sample_rate = 500.0e6
time_step = 1/sample_rate
    
carrier_freq = 100.0e6
    
    
# number of digital samples to simulate
num_samples = 2**18 # 262144
t_stop = num_samples*time_step
t = np.arange(0, t_stop, time_step)
    
# let the signal be a voltage waveform,
# so there is no zero padding
carrier_I = np.sin(2*np.pi*carrier_freq*t)
    
#######################################################
# FFT using Welch method
# windows = np.ones(nfft) - no windowing
# if windows = 'hamming', etc.. this function will
# normalize to an equivalent noise bandwidth (ENBW)
#######################################################
nfft = num_samples  # fft size same as signal size 
f,Pxx_den = scipy.signal.welch(carrier_I, fs = 1/time_step,\
                        window = np.ones(nfft),\
                        nperseg = nfft,\
                        scaling='density')
    
#######################################################
# FFT comparison    
#######################################################
    
integration_time = nfft*time_step
power_time_domain = sum((np.abs(carrier_I)**2)*time_step)/integration_time
print 'power time domain = %f' % power_time_domain
    
# Take FFT.  Note that the factor of 1/nfft is sometimes omitted in some 
# references and software packages.
# By proving Parseval's theorem (conservation of energy) we can find out the 
# proper normalization.
signal = carrier_I 
xdft = scipy.fftpack.fft(signal, nfft)/nfft 
# fft coefficients need to be scaled by fft size
# equivalent to scaling over frequency bins
# total power in frequency domain should equal total power in time domain
power_freq_domain = sum(np.abs(xdft)**2)
print 'power frequency domain = %f' % power_freq_domain
# Energy is conserved
    
    
# FFT symmetry
plt.figure(0)
plt.subplot(2,1,1)
plt.plot(np.abs(xdft)) # symmetric in amplitude
plt.title('magnitude')
plt.subplot(2,1,2) 
plt.plot(np.angle(xdft)) # pi phase shift out of phase
plt.title('phase')
plt.show()
    
xdft_short = xdft[0:nfft/2+1] # take only positive frequency terms, other half identical
# xdft[0] is the dc term
# xdft[nfft/2] is the Nyquist term, note that Python 2.X indexing does NOT 
# include the last element, therefore we need to use 0:nfft/2+1 to have an array
# that is from 0 to nfft/2
# xdft[nfft/2-x] = conjugate(xdft[nfft/2+x])
Pxx = (np.abs(xdft_short))**2 # power ~ voltage squared, power in each bin.
Pxx_density = Pxx / (sample_rate/nfft)  # power is energy over -fs/2 to fs/2, with nfft bins
Pxx_density[1:-1] = 2*Pxx_density[1:-1] # conserve power since we threw away 1/2 the spectrum
# note that DC (0 frequency) and Nyquist term only appear once, we don't double those.
# Note that Python 2.X array indexing is not inclusive of the last element.
    
    
    
Pxx_density_dB = 10*np.log10(Pxx_density)
freq = np.linspace(0,sample_rate/2,nfft/2+1) 
# frequency range of the fft spans from DC (0 Hz) to 
# Nyquist (Fs/2).
# the resolution of the FFT is 1/t_stop
# dft of size nfft will give nfft points at frequencies
# (1/stop) to (nfft/2)*(1/t_stop)
    
plt.figure(1)
plt.plot(freq,Pxx_density_dB,'^')
plt.figure(1)
plt.plot(f, 10.0*np.log10(Pxx_den))
plt.xlabel('Freq (Hz)'),plt.ylabel('dBm/Hz'),#plt.show()
plt.ylim([-200, 0])
plt.show()

3

许多常见(但不是全部)FFT库通过FFT结果的长度来缩放单位幅度正弦波的幅值。这样做是为了维持Parseval等式,因为相同幅度但更长的正弦波总能量比较短的正弦波更大。

如果您不想在使用这些库时按FFT长度进行缩放,则在计算分贝值之前除以长度即可。


谢谢,hotpaw2。我已经在做这个了。不过,我也注意到流行的音频编辑器之间存在不一致性,所以似乎FFT归一化/缩放是相对随意的(或者至少没有很好地标准化)。 - dsp_user

0

规范化可以通过许多不同的方式进行 - 取决于窗口、样本数量等。

常见技巧:对已知信号进行FFT,并通过峰值的值进行归一化。例如,在上面的示例中,您的峰值为123 - 如果您希望它为1,则将其(以及使用此算法获得的所有结果)除以123。


1
我的输入数据都在-1到1的范围内。在《科学家和工程师数字信号处理指南》中,作者详细解释了执行FFT归一化所需的步骤。不幸的是,这本书的这部分对我来说有点模糊。如果您感兴趣,可以查看(http://www.dspguide.com/ch10.htm)。谢谢您的帮助。 - dsp_user
我的观点是,“缩放”只是一种乘法——由于信号处理中经常存在各种校准和转换(1000输入意味着伏特、毫伏、W/m^2等),因此FFT库实现任何形式的缩放都没有太大意义,所以它们通常不会实现任何形式的缩放,而是留给用户自己去处理。我描述的方法效果很好——为了提高速度,你可以只计算一次1.0/123.0,然后进行乘法运算(除法比乘法慢得多...)。 - Floris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接