如何更改 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 轴时,我认为它们是 frequency 和 time 域,我无法得到我的围绕频率从 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 matplotlib
和
matplotlib 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()
结果:
我以前从未接触过音频信号,对信号处理知之甚少。尽管如此,我需要使用 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 轴时,我认为它们是 frequency 和 time 域,我无法得到我的围绕频率从 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 matplotlib 和 matplotlib 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()
结果: