从频谱图重建音频(将其用作过滤器)

信息处理 频谱图 stft 重建
2022-01-30 08:16:50

给定使用以下代码计算的频谱图:

import matplotlib.pyplot as plt
import numpy as np
import scipy
from scipy import signal, fft
from scipy.io import wavfile
from skimage import util
import librosa
import pylab
# from scipy.fftpack import fft

def stft(x, fs, framesz, hop):
    framesamp = int(framesz*fs)
    hopsamp = int(hop*fs)
    w = scipy.hanning(framesamp)
    X = scipy.array([scipy.fft(w*x[i:i+framesamp]) for i in range(0, len(x)-framesamp, hopsamp)])
    return X

def istft(X, fs, T, hop):
    x = scipy.zeros(T*fs)
    framesamp = X.shape[1]
    hopsamp = int(hop*fs)
    for n,i in enumerate(range(0, len(x)-framesamp, hopsamp)): x[i:i+framesamp] += scipy.real(scipy.ifft(X[n]))
    return x

audio_data = f'../recordings/SDRuno_20220309_140138Z_1kHz.wav' # y movement as test...
fs, data = wavfile.read(audio_data)
data = np.mean(data, axis=1) # convert to mono by avg l+r channels
N = data.shape[0]
L = N / fs
amp = 2 / N * np.abs(data)

print(f'Audio length: {L:.2f} seconds')

# f, ax = plt.subplots()
# ax.plot(np.arange(N) / fs, data)
# ax.set_xlabel('Time [s]')
# ax.set_ylabel('Amplitude [unknown]');
# plt.show()

M = int(fs * 0.001 * 20) # originally we had 1024, however now we use this for 20ms window resolution
# amp = 2 * np.sqrt(2)
stft_sig_f, stft_sig_t, stft_sig_Zxx = signal.stft(data, fs=fs, window='hann', nperseg=M, detrend=False)
# plt.pcolormesh(stft_sig_t, stft_sig_f, np.abs(stft_sig_Zxx), vmin=0, vmax=amp, shading='gouraud')
# plt.title('STFT Magnitude')
# plt.ylabel('Frequency [Hz]')
# plt.xlabel('Time [sec]')
# plt.show()

freqs, times, Sx = signal.spectrogram(data, fs=fs, window='hanning', nperseg=M, detrend=False, scaling='spectrum')
f, ax = plt.subplots(figsize=(10,5))
ax.pcolormesh(times, freqs / 1000, 10 * np.log10(Sx), cmap='inferno')
ax.set_ylabel('Frequency [kHz]')
ax.set_xlabel('Time [s]');
plt.show()

如何使用频谱图的“更清晰的信号”来过滤音频。我确实在某处读到过,这Sx实际上Zxx**2Zxx可以用于 ISTFT 来重建音频的,但是我的知识非常缺乏。任何帮助,将不胜感激!

编辑:绘制 STFT 的当前代码只显示一个黑色窗口。

此示例中使用的 IQ WAV 文件可在此处找到:https ://www.dropbox.com/s/at31myl5oufvfa3/SDRuno_20220309_140138Z_1kHz.wav?dl=0

2个回答

“频谱图不可逆”并不完全正确。只需谷歌“invert spectrogram python”——结果中应该是最著名的算法Griffin-Lim因此,人们可以调整频谱图,然后将其反转,作为一种非线性滤波形式。然而,我们能做的最好的事情是在全局相移内恢复信号,ejϕ0(它仍然完美地保留了瞬时频率和幅度)。

通过可微算子实现 STFT 并通过梯度下降最小化重建误差(尺度图示例)可以获得更好的结果torch.stft在这里可能会有所帮助。

正如@dan 所提到的,幅度仅包含所需信息的一半。

您可以尝试将复相角设置为随机值 [0…2*pi]。这会发出«vocoder-ish»的声音。对于语音信号,它听起来像“噪音说话”。

在某些情况下,相位被假定为最小相位,但我不知道这在频谱图中是否有意义?

编辑:

也许我没有使用正确的术语。下面的 MATLAB 代码执行基于 fft 的滤波器组,具有 50% 的重叠、功率互补(双)窗口和 fft 系数的复杂表示。它是可逆的(在数值精度内)

%% generate window
win_pow = 2;%power complementary
Nh = 256;
N = 2*Nh;
win = hann(N+1);
win = win(1:N);
win = win / sum(win([1 end/2+1]));
win = win.^(1/win_pow);
assert(max(abs(win.^win_pow + circshift(win, Nh).^win_pow - 1)) < 1e-14);

%% test 50% overlapping STFT
x = 2*rand(3*N,1)-1;
X = buffer(x, N, Nh, "nodelay");
X = X .* win;
X = fft(X);
% <- this is where you would do something useful
Y = ifft(X);
Y = Y .* win;
x_hat = debuffer(Y, Nh);
err = x(1:length(x_hat)) - x_hat;
assert(max(abs(err((Nh+1):2*N))) < 1e-15);

这个滤波器组是不重叠的、矩形窗口的并且也是可逆的。我认为这也是一个“STFT”,如果由于“严格采样”而有些差的话:

%% test critically sampled STFT
x = 2*rand(3*N,1)-1;
X = buffer(x, N, 0, "nodelay");
X = X;
X = fft(X);
Y = ifft(X);
Y = Y;
x_hat = debuffer(Y, 0);
err = x(1:length(x_hat)) - x_hat;
assert(max(abs(err((Nh+1):2*N))) < 1e-15);

每当我看到 abs(X) (并且相位被丢弃)时,我将其视为频谱图,并使用前一个片段中的严格采样滤波器组,如果没有关于信号统计的一些重要假设,我无法看到它是如何可逆的? 这就是你们所说的STFT吗?频谱图?我对这些回答感到困惑。

%% test critically sampled spectrogram
x = 2*rand(3*N,1)-1;
X = buffer(x, N, 0, "nodelay");
X = X;
X = fft(X);
X = abs(X);
Y = ifft(X);
Y = Y;
x_hat = debuffer(Y, 0);
err = x(1:length(x_hat)) - x_hat;

x 在我的机器上表示为 1536x1 向量,12288 字节。

X 在我的机器上表示为 512x3 数组,12288 字节。然而,由于它是镜像的,它只包含 257x3x8 = 6168 字节的唯一信息。因此它不可能传达 x 向量的所有可能性?

我看到过采样滤波器组如何包含“附加信息”(即使您丢弃相位信息)可能用于帮助重建。这听起来像是一个我不想冒险的练习。