如何更改 pyplot.specgram x 和 y 轴缩放比例?

How to change pyplot.specgram x and y axis scaling?

我以前从未接触过音频信号,对信号处理知之甚少。尽管如此,我需要使用 matplotlib 库中的 pyplot.specgram 函数来表示音频信号。这是我的做法。

import matplotlib.pyplot as plt
import scipy.io.wavfile as wavfile

rate, frames = wavfile.read("song.wav")
plt.specgram(frames)

我得到的结果是下面这个漂亮的频谱图:

当我查看 x 轴和 y 轴时,我认为它们是 frequencytime 域,我无法得到我的围绕频率从 0 缩放到 1.0 和时间从 0 缩放到 80k 的事实。 它背后的直觉是什么,更重要的是,如何以人类友好的格式表示它,例如频率为 0 到 100k,时间以秒为单位?

  • 首先,频谱图是作为时间函数的信号频谱内容的表示 - 这是频域表示时域波形(例如正弦波,您的文件"song.wav"或其他任意波-即作为时间函数的振幅)。

  • 频率值(y 轴,赫兹)完全取决于波形的采样频率 ("song.wav"),范围从“0”到 "sampling frequency / 2" ,上限为 "nyquist frequency" 或 "folding frequency" (https://en.wikipedia.org/wiki/Aliasing#Folding)。 matplotlib specgram函数在没有特别指定的情况下会自动确定输入波形的采样频率,定义为1/dt,dt为波形离散样本之间的时间间隔。您可以将选项 Fs='sampling rate' 传递给 specgram 函数以手动定义它是什么。如果您自己弄清楚并将这些变量传递给 specgram 函数,您将更容易了解正在发生的事情

  • 时间值(x 轴,秒)完全取决于您 "song.wav" 的长度。如果您使用较大的 window 长度来计算每个光谱切片,您可能会注意到一些空白或填充(想想 - 垂直排列并水平平铺以创建光谱图图像的单个光谱)

  • 为了使坐标轴在图中更直观,使用 x 轴和 y 轴标签,您还可以使用类似于 [=12] 的方法缩放坐标轴值(即更改单位) =]

带回家的信息 - 尽量让您的代码更详细一些:请参阅下面的示例。

    import matplotlib.pyplot as plt
    import numpy as np

    # generate a 5Hz sine wave
    fs = 50
    t = np.arange(0, 5, 1.0/fs)
    f0 = 5
    phi = np.pi/2
    A = 1
    x = A * np.sin(2 * np.pi * f0 * t +phi)

    nfft = 25

    # plot x-t, time-domain, i.e. source waveform
    plt.subplot(211)
    plt.plot(t, x)
    plt.xlabel('time')
    plt.ylabel('amplitude')

    # plot power(f)-t, frequency-domain, i.e. spectrogram
    plt.subplot(212)
    # call specgram function, setting Fs (sampling frequency) 
    # and nfft (number of waveform samples, defining a time window, 
    # for which to compute the spectra)
    plt.specgram(x, Fs=fs, NFFT=nfft, noverlap=5, detrend='mean', mode='psd')
    plt.xlabel('time')
    plt.ylabel('frequency')
    plt.show()

5Hz_spectrogram:

正如其他人指出的那样,您需要指定采样率,否则您将获得归一化频率(0 到 1 之间)和样本索引(0 到 80k)。幸运的是,这很简单:

plt.specgram(frames, Fs=rate)

扩展 Nukolas 的答案并结合我的 Changing plot scale by a factor in matplotlibmatplotlib intelligent axis labels for timedelta 我们不仅可以得到频率轴上的kHz,还可以得到时间轴上的分秒。

import matplotlib.pyplot as plt
import scipy.io.wavfile as wavfile

cmap = plt.get_cmap('viridis') # this may fail on older versions of matplotlib
vmin = -40  # hide anything below -40 dB
cmap.set_under(color='k', alpha=None)

rate, frames = wavfile.read("song.wav")
fig, ax = plt.subplots()
pxx, freq, t, cax = ax.specgram(frames[:, 0], # first channel
                                Fs=rate,      # to get frequency axis in Hz
                                cmap=cmap, vmin=vmin)
cbar = fig.colorbar(cax)
cbar.set_label('Intensity dB')
ax.axis("tight")

# Prettify
import matplotlib
import datetime

ax.set_xlabel('time h:mm:ss')
ax.set_ylabel('frequency kHz')

scale = 1e3                     # KHz
ticks = matplotlib.ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale))
ax.yaxis.set_major_formatter(ticks)

def timeTicks(x, pos):
    d = datetime.timedelta(seconds=x)
    return str(d)
formatter = matplotlib.ticker.FuncFormatter(timeTicks)
ax.xaxis.set_major_formatter(formatter)
plt.show()

结果: