提高 Python 中的频谱图分辨率?

信息处理 fft 频谱图 Python
2022-01-06 00:10:58

我正在使用specgram()函数 inmatplotlib在 Python 中生成语音波文件的频谱图,但输出的质量始终远低于我的普通转录软件 Praat 可以生成的质量。例如,以下调用:

specgram(
    fromstring(spf.readframes(-1), 'Int16'),
    Fs=framerate,
    cmap=cm.gray_r,
)

生成这个:

在此处输入图像描述

在 Praat 时,使用以下设置处理相同的音频样本:

  • 视野范围:0-8000Hz
  • 窗口长度:0.005s
  • 动态范围:70dB
  • 时间步数:1000
  • 频率步长:250
  • 窗口形状:高斯

生成这个:

在此处输入图像描述

我究竟做错了什么?我试过摆弄所有specgram()参数,但似乎没有什么能提高分辨率。我几乎没有使用 FFT 的经验。

2个回答

这是 matplotlib.specgram 参数

matplotlib.mlab.specgram(x, 
                         NFFT=256, 
                         Fs=2, 
                         detrend=<function detrend_none at 0x1dd6410>, 
                         window=<function window_hanning at 0x1e0b1b8>, 
                         noverlap=128, 
                         pad_to=None, 
                         sides='default', 
                         scale_by_freq=None)

问题描述中提供的参数需要转换为可比较的 mpl.specgram 参数。下面是一个映射示例:

View range: 0-8000Hz            Fs=16000
Window length: 0.005s           NFFT = int(Fs*0.005) = 80
                                noverlap = int(Fs*0.0025) = 40
Dynamic range: 70dB             n/a
Time steps: 1000                n/a
Frequency steps: 250            
Window shape: Gaussian          default window is hanning change to gaussian

如果您使用 8ms,您将获得 2 FFT (128) 的幂。以下是他们网站上对 Praat 设置的描述

查看范围 (Hz):要显示的频率范围。标准是底部为 0 Hz,顶部为 5000 Hz。如果此最大频率高于声音的奈奎斯特频率(即采样频率的一半),则频谱图中的某些值将为零,并且较高的频率将以白色绘制。如果您以 44100 Hz 录制声音并将查看范围设置为 0 Hz 到 25000 Hz,您可以看到这一点。

窗口长度:分析窗口的持续时间。如果这是 0.005 秒(标准),Praat 为每一帧使用位于该帧中心之前 0.0025 秒和之后 0.0025 秒之间的声音部分(对于高斯窗口,Praat 实际上使用的比这多一点)。窗口长度决定了频谱分析的带宽,即纯正弦波频谱图中水平线的宽度(见下文)。对于高斯窗口,-3 dB 带宽为 2*sqrt(6*ln(2))/(π*窗口长度),或 1.2982804 / 窗口长度。要获得broad-band' spectrogram (bandwidth 260 Hz), keep the standard window length of 5 ms; to get a窄带频谱图(带宽 43 Hz),请将其设置为 30 毫秒(0.03 秒)。其他窗口形状给出的值略有不同。

动态范围 (dB):所有大于动态范围 dB 的值低于最大值(可能在动态压缩之后,请参阅高级频谱图设置...)将绘制为白色。中间的值具有适当的灰色阴影。因此,如果频谱图中最高峰的高度为 30 dB/Hz,动态范围为 50 dB(这是标准值),则低于 -20 dB/Hz 的值将以白色绘制,而介于-20 dB/Hz 和 30 dB/Hz 将以各种灰度绘制。

链接到 Praat 设置

OP的问题可能与Praat specgram和mpl(matplotlib)specgram之间的对比度差异有关。Praat 具有影响对比度的动态范围设置。mpl 函数没有类似的设置/参数。mpl.specgram 确实返回了功率水平的二维数组(频谱图),动态范围可以应用于返回数组并重新绘制。

以下是用于创建以下图的代码片段。该示例是约 1 分 15 秒的语音,带有 20Hz-8000Hz 的啁啾声。

import numpy
import pylab
import wave
import array
pylab.close('all')
w1 = wave.open('example_no_noise.wav')
w2 = wave.open('example_noise.wav')
# hmmm, probably a better way to do this, scipy.io function?
x1 = numpy.array(array.array('h', w1.readframes(w1.getnframes())))
x2 = numpy.array(array.array('h', w2.readframes(w2.getnframes())))
x1 = x1 / (2.**(16-1))  # normalize
x2 = x2 / (2.**(16-1))  # normalize
Fs = 16000.
NFFT = int(Fs*0.005)  # 5ms window
noverlap = int(Fs*0.0025)
pylab.figure(1)
pylab.specgram(x1, NFFT=NFFT, Fs=Fs, noverlap=noverlap, 
               cmap=pylab.get_cmap('Greys'))
pylab.title('Full 1m15s example min noise')
pylab.figure(2)
pylab.specgram(x2, NFFT=NFFT, Fs=Fs, noverlap=noverlap, 
               cmap=pylab.get_cmap('Greys'))
pylab.title('Full 1m15s example more noise')
pylab.figure(3); n=2100*176;
pylab.specgram(x2[n:n+256*256], NFFT=NFFT, Fs=Fs, noverlap=noverlap, 
               cmap=pylab.get_cmap('Greys'))
pylab.title('Full ~4s example min noise')
pylab.figure(4); pylab.plot(x1[n:n+256*256])

这似乎是一个时间/频率分辨率问题。您的 Praat 图的频率分辨率较差(您甚至无法清楚地看到谐波)和更好的时间分辨率。尝试将窗口大小 (NFFT) 减小到 16000 x 0.05 = 80 个样本。我建议在 pad_to(128 或 256)中使用更大的 2 幂。