如何拉伸 matplotlib 频谱图的 x 轴?

How do I stretch the x-axis of a matplotlib spectrogram?

抱歉,如果这是一个非常明显的问题。我正在使用 matplotlib 生成一些频谱图,用作机器学习模型中的训练数据。频谱图是音乐的短片,我想模拟随机加速或减慢歌曲以创建数据变化。我在下面展示了生成每个频谱图的代码。我临时修改了它以生成 2 张从歌曲中的同一点开始的图像,一张有变化,一张没有,以便比较它们,看看它是否按预期工作。

from pydub import AudioSegment
import matplotlib.pyplot as plt
import numpy as np

BPM_VARIATION_AMOUNT = 0.2
FRAME_RATE = 22050
CHUNK_SIZE = 2
BUFFER = FRAME_RATE * 5

def generate_random_specgram(track):
    # Read audio data from file
    audio = AudioSegment.from_file(track.location)
    audio = audio.set_channels(1).set_frame_rate(FRAME_RATE)
    samples = audio.get_array_of_samples()
    start = np.random.randint(BUFFER, len(samples) - BUFFER)
    chunk = samples[start:start + int(CHUNK_SIZE * FRAME_RATE)]

    # Plot specgram and save to file
    filename = ('specgrams/%s-%s-%s.png' % (track.trackid, start, track.bpm))
    plt.figure(figsize=(2.56, 0.64), frameon=False).add_axes([0, 0, 1, 1])
    plt.axis('off')
    plt.specgram(chunk, Fs = FRAME_RATE)
    plt.savefig(filename)
    plt.close()

    # Perform random variations to the BPM
    frame_rate = FRAME_RATE
    bpm = track.bpm
    variation = 1 - BPM_VARIATION_AMOUNT + (
        np.random.random() * BPM_VARIATION_AMOUNT * 2)
    bpm *= variation
    bpm = round(bpm, 2)
    # I thought this next line should have been /= but that stretched the wrong way?
    frame_rate *= (bpm / track.bpm) 

    # Read audio data from file
    chunk = samples[start:start + int(CHUNK_SIZE * frame_rate)]

    # Plot specgram and save to file
    filename = ('specgrams/%s-%s-%s.png' % (track.trackid, start, bpm))
    plt.figure(figsize=(2.56, 0.64), frameon=False).add_axes([0, 0, 1, 1])
    plt.axis('off')
    plt.specgram(chunk, Fs = frame_rate)
    plt.savefig(filename)
    plt.close()

我想通过改变给 specgram 函数的 Fs 参数,这会沿着 x 轴拉伸数据,但它似乎正在调整整个图的大小并在图的顶部引入白色 space图像以奇怪和不可预测的方式。我确定我遗漏了什么,但我看不到它是什么。下面是一张图片来说明我得到的结果。

帧率是一个固定数字,它只取决于您的数据,如果您更改它,您将有效地 "stretch" x 轴,但方式不对。例如,如果您有 1000 个数据点对应 1 秒,则您的帧率(或更好的采样频率)将为 1000。如果您的信号是一个简单的 200Hz 正弦波,它会及时稍微增加频率,specgram 将是:

t = np.linspace(0, 1, 1000)
signal = np.sin((200*2*np.pi + 200*t) * t)

frame_rate = 1000
plt.specgram(signal, Fs=frame_rate);

如果您更改帧率,您将获得错误的 x 轴和 y 轴刻度。如果您将帧速率设置为 500,您将拥有:

t = np.linspace(0, 1, 1000)
signal = np.sin((200*2*np.pi + 200*t) * t)

frame_rate = 500
plt.specgram(signal, Fs=frame_rate);

情节非常相似,但这次是错误的:你在x轴上几乎有2秒,而你应该只有1秒,而且你读取的起始频率是100Hz而不是200Hz。


总之,您设置的采样频率需要正确。如果你想拉伸情节,你可以使用像 plt.xlim(0.2, 0.4) 这样的东西。如果你想避免绘图顶部的白带,你可以手动将 ylim 设置为帧速率的一半:

plt.ylim(0, frame_rate/2)

之所以可行,是因为傅立叶变换的简单属性和 Nyquist-Shannon theorem

我的问题的解决方案是设置绘图的 xlim 和 ylim。这是我的测试文件中的代码,我终于在其中删除了所有奇怪的空格:

from pydub import AudioSegment
import numpy as np
import matplotlib.pyplot as plt

BUFFER = 5
FRAME_RATE = 22050
SAMPLE_LENGTH = 2

def plot(audio_file, bpm, variation=1):
    audio = AudioSegment.from_file(audio_file)
    audio = audio.set_channels(1).set_frame_rate(FRAME_RATE)
    samples = audio.get_array_of_samples()
    chunk_length = int(FRAME_RATE * SAMPLE_LENGTH * variation)
    start = np.random.randint(
        BUFFER * FRAME_RATE,
        len(samples) - (BUFFER * FRAME_RATE) - chunk_length)
    chunk = samples[start:start + chunk_length]

    plt.figure(figsize=(5.12, 2.56)).add_axes([0, 0, 1, 1])
    plt.specgram(chunk, Fs=FRAME_RATE * variation)
    plt.xlim(0, SAMPLE_LENGTH)
    plt.ylim(0, FRAME_RATE / 2 * variation)
    plt.savefig('specgram-%f.png' % (bpm * variation))
    plt.close()