将 FFT 频谱幅度归一化为 0dB

Normalizing FFT spectrum magnitude to 0dB

我正在使用 FFT 从音频文件中提取每个频率分量的幅度。实际上,Audacity 中已经有一个名为 Plot Spectrum 的功能可以帮助解决这个问题。取这个由3kHz正弦和6kHz正弦组成的example audio file,频谱结果如下图。您可以看到峰值位于 3KHz 和 6kHz,没有额外的频率。

现在我需要实现相同的功能并在 Python 中绘制相似的结果。在 rfft 的帮助下,我接近 Audacity 结果,但在获得此结果后我仍有问题需要解决。

  1. 第二张图中振幅的物理意义是什么?
  2. 如何像 Audacity 中那样将振幅归一化为 0dB?
  3. 为什么6kHz以上的频率幅度这么大(≥90)?我可以将这些频率调整到相对较低的水平吗?

相关代码:

import numpy as np
from pylab import plot, show
from scipy.io import wavfile

sample_rate, x = wavfile.read('sine3k6k.wav')
fs = 44100.0

rfft = np.abs(np.fft.rfft(x))
p = 20*np.log10(rfft)
f = np.linspace(0, fs/2, len(p))

plot(f, p)
show()

更新

我将 Hanning window 与整个长度信号相乘(正确吗?)并得到这个。裙子的振幅大部分在40以下

并将 y 轴缩放为分贝,如 所述。结果更接近 Audacity。我可以将低于-90dB 的振幅处理得低到可以忽略不计吗?

更新代码:

fs, x = wavfile.read('input/sine3k6k.wav')
x = x * np.hanning(len(x))

rfft = np.abs(np.fft.rfft(x))
rfft_max = max(rfft)
p = 20*np.log10(rfft/rfft_max)
f = np.linspace(0, fs/2, len(p))


关于赏金

使用上面更新中的代码,我可以以分贝为单位测量频率分量。最高可能值为 0dB。但是该方法仅适用于特定的音频文件,因为它使用了该音频的 rfft_max。我想像 Audacity 一样在一个标准规则中测量多个音频文件的频率分量。

我也在 Audacity 论坛上 started a discussion,但我仍然不清楚如何实现我的目的。

在对 Audacity 源代码进行一些逆向工程后,这里有一些答案。首先,他们使用 Welch algorithm 来估计 PSD。简而言之,它将信号拆分为重叠的段,应用一些 window 函数,应用 FFT 并对结果进行平均。主要是因为这有助于在存在噪音时获得更好的结果。无论如何,这里提取必要的参数后是近似于 Audacity 的频谱图的解决方案:

import numpy as np
from scipy.io import wavfile
from scipy import signal
from matplotlib import pyplot as plt

segment_size = 512

fs, x = wavfile.read('sine3k6k.wav')
x = x / 32768.0  # scale signal to [-1.0 .. 1.0]

noverlap = segment_size / 2
f, Pxx = signal.welch(x,                        # signal
                      fs=fs,                    # sample rate
                      nperseg=segment_size,     # segment size
                      window='hanning',         # window type to use
                      nfft=segment_size,        # num. of samples in FFT
                      detrend=False,            # remove DC part
                      scaling='spectrum',       # return power spectrum [V^2]
                      noverlap=noverlap)        # overlap between segments

# set 0 dB to energy of sine wave with maximum amplitude
ref = (1/np.sqrt(2)**2)   # simply 0.5 ;)
p = 10 * np.log10(Pxx/ref)

fill_to = -150 * (np.ones_like(p))  # anything below -150dB is irrelevant
plt.fill_between(f, p, fill_to )
plt.xlim([f[2], f[-1]])
plt.ylim([-90, 6])
# plt.xscale('log')   # uncomment if you want log scale on x-axis
plt.xlabel('f, Hz')
plt.ylabel('Power spectrum, dB')
plt.grid(True)
plt.show()

一些必要的参数说明:

  • wave 文件被读取为 16 位 PCM,为了与 Audacity 兼容,它应该被缩放为 |A|<1.0
  • segment_size 对应于 Audacity 的 GUI 中的 Size
  • 默认window类型是'Hanning',你可以根据需要更改它。
  • 重叠在 Audacity 代码中是 segment_size/2
  • 输出 window 被设计为遵循 Audacity 风格。他们扔掉了第一个低频箱并削减了 -90dB
  • 以下的一切

What's physical meaning of the amplitude in the second picture?

它基本上是频率仓中的能量。

How to normalize the amplitude to 0dB like the one in Audacity?

您需要选择一些参考点。以分贝为单位的图表总是与某些事物相关。当你 select 最大能量箱作为参考时,你的 0db 点就是最大能量(显然)。可以将具有最大幅度的正弦波设置为参考能量。请参阅 ref 变量。正弦信号中的功率只是 RMS 的平方,要获得 RMS,您只需将振幅除以 sqrt(2)。所以比例因子只是 0.5。请注意 log10 之前的因子是 10 而不是 20,这是因为我们处理的是信号的功率而不是幅度。

Can I treat the amplitude below -90dB so low that it can be ignored?

是的,低于 -40dB 的任何值通常都可以忽略不计