音频转频谱图及逆向转换:解决窗口过长报错问题
频谱图转音频报错解决与实现方法
背景
已实现音频转频谱图的功能,代码如下:
''' Function for converting all the waves files to image files. ''' import matplotlib.pyplot as plt import librosa def wavesToSpecs(audio_path:str, spec_path:str): fname = str(audio_path).split('/')[-1].split('.')[0] samples, sample_rate = librosa.load(audio_path, sr=None) fig, ax = plt.subplots(figsize=(5,5)) ax.set_axis_off() # 注意:原代码中Fs=2是错误的,应使用实际采样率sample_rate,否则频谱图频率轴完全错误 ax.specgram(samples, Fs=sample_rate) fig.savefig(f'{fname}.png', bbox_inches="tight", pad_inches=0) plt.close(fig) del sample_rate, samples, fig, ax wavesToSpecs("/content/wav/test.wav", "test.jpg")
尝试将频谱图转回音频时,运行以下代码出现报错:
import librosa from scipy import signal import scipy.io.wavfile as sf file = r"C:\desktop\file.wav" sample_rate, samples = sf.read(file) nperseg = int(sample_rate * 0.001 * 20) frequencies, times, spectrogram = signal.spectrogram(samples, sample_rate, nperseg=nperseg, window=signal.hann(nperseg)) audio_signal = librosa.griffinlim(spectrogram) print(audio_signal, audio_signal.shape) sf.write('test.wav', audio_signal, sample_rate)
报错信息
Exception has occurred: ValueError window is longer than input signal
问题分析与解决方案
1. 先解决报错问题
报错原因是nperseg(窗口长度)计算值超过了音频信号的总采样数。比如你的音频时长不足20ms,sample_rate * 0.02的结果会大于len(samples)。
解决方法:
nperseg = int(sample_rate * 0.02) # 20ms窗口 nperseg = min(nperseg, len(samples)) # 避免窗口过长 # 如果nperseg还是过小,调整窗口时长,比如改成10ms if nperseg < 32: nperseg = 32
2. 核心问题:你没有从生成的频谱图(PNG)还原,而是重新计算了原音频的频谱
你当前的代码是直接对原音频做频谱分析再还原,这不是“从频谱图转回音频”的正确流程。正确的流程分两种情况:
情况A:转频谱图时同时保存数值化频谱数据(推荐)
修改音频转频谱图的代码,同时保存频谱的数值数据,这样还原时能保证精度:
import matplotlib.pyplot as plt import librosa import numpy as np from scipy import signal def wavesToSpecs(audio_path:str): fname = str(audio_path).split('/')[-1].split('.')[0] samples, sample_rate = librosa.load(audio_path, sr=None) # 计算频谱(匹配matplotlib specgram的默认参数) frequencies, times, spectrogram = signal.spectrogram( samples, fs=sample_rate, nperseg=256, # specgram默认值 window='hann', scaling='spectrum' ) # 保存数值化频谱和参数 np.save(f'{fname}_spec.npy', spectrogram) np.save(f'{fname}_params.npy', np.array([sample_rate, frequencies, times])) # 生成可视化频谱图(可选) fig, ax = plt.subplots(figsize=(5,5)) ax.set_axis_off() ax.specgram(samples, Fs=sample_rate) fig.savefig(f'{fname}.png', bbox_inches="tight", pad_inches=0) plt.close(fig) return spectrogram, sample_rate wavesToSpecs("/content/wav/test.wav")
然后从保存的数值数据还原音频:
import numpy as np import librosa import scipy.io.wavfile as sf # 加载保存的频谱数据 fname = "test" spectrogram = np.load(f'{fname}_spec.npy') params = np.load(f'{fname}_params.npy') sample_rate = params[0] # 使用Griffin-Lim算法还原音频 audio_signal = librosa.griffinlim(spectrogram) # 保存还原后的音频 sf.write(f'{fname}_recovered.wav', audio_signal, int(sample_rate))
情况B:从PNG图像还原音频(精度较低)
如果只能从已生成的PNG图像还原,需要将图像像素值转换为频谱幅度,但会丢失大量信息:
import numpy as np import matplotlib.pyplot as plt import librosa import scipy.io.wavfile as sf # 读取频谱图图像 img = plt.imread("test.png") # 转换为灰度图(如果是彩色) if len(img.shape) == 3: img = np.mean(img, axis=2) # 反转颜色(因为specgram默认是深色背景,亮色代表高幅度) img = 1 - img # 调整为合适的频谱幅度范围(系数需根据实际情况调整) spectrogram = img * 1000 # 使用Griffin-Lim还原,替换为原音频的采样率 sample_rate = 44100 audio_signal = librosa.griffinlim(spectrogram) sf.write('recovered_from_img.wav', audio_signal, sample_rate)
内容的提问来源于stack exchange,提问作者jangles
相关产品推荐
相关产品推荐

