将FFT频谱幅度标准化为0dB

时间:2018-06-27 07:51:56

标签: python fft frequency spectrum audacity

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

enter image description here

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

enter image description here

  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()

更新

我将汉宁窗与整个长度信号相乘(对吗?)。裙子的大部分振幅都在40以下。

enter image description here

按照@Mateen Ulhaq所说,将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))

enter image description here


关于赏金

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

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

1 个答案:

答案 0 :(得分:4)

在对Audacity源代码进行一些反向工程之后,这里有一些答案。首先,他们使用Welch algorithm来估计PSD。简而言之,它将信号拆分为重叠的段,应用一些窗口函数,应用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()

一些必要的参数说明:

  • 波形文件被读取为16位PCM,为了与Audacity兼容,应将其缩放为| A | <1.0
  • segment_size对应于Audacity的GUI中的Size
  • 默认窗口类型为“汉宁”,您可以根据需要进行更改。
  • 重叠是segment_size/2,如Audacity代码中一样。
  • 输出窗口的框架遵循Audacity样式。他们扔掉了第一个低频箱,并将所有低于-90dB的东西都剪掉了

enter image description here

  

第二张图片中振幅的物理含义是什么?

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

  

如何像Audacity中一样将幅度归一化为0dB?

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

  

我可以将-90dB以下的幅度处理得如此之低以至于可以忽略吗?

是的,任何低于-40dB的声音通常被认为是可疏忽的