webentwicklung-frage-antwort-db.com.de

Invertierbare STFT und ISTFT in Python

Gibt es eine allgemeine Form von Kurzzeit-Fourier-Transformation mit entsprechender inverser Transformation in SciPy oder NumPy oder was auch immer?

Es gibt die Funktion pyplot specgram in matplotlib, die ax.specgram() aufruft, die mlab.specgram() aufruft, die _spectral_helper() aufruft:

#The checks for if y is x are so that we can use the same function to
#implement the core of psd(), csd(), and spectrogram() without doing
#extra calculations.  We return the unaveraged Pxy, freqs, and t.

aber

Dies ist eine Hilfsfunktion, die die Gemeinsamkeit der .__ implementiert. 204 #psd, csd und Spektrogramm. Es ist NICHT soll außerhalb von mlab verwendet werden

Ich bin mir nicht sicher, ob dies für STFT und ISTFT verwendet werden kann. Gibt es noch etwas, oder sollte ich etwas wie diese MATLAB-Funktionen übersetzen?

Ich kann meine eigene Ad-hoc-Implementierung schreiben. Ich bin nur auf der Suche nach etwas Vollfunktionalem, das verschiedene Windowing-Funktionen beherrscht (aber eine vernünftige Standardeinstellung hat) und mit COLA - Fenstern (istft(stft(x))==x), die von mehreren Personen getestet werden, nicht invertierbar ist Fehler, behandelt die Enden und Nullabstände gut, schnelle RFFT-Implementierung für echte Eingaben usw.

48
endolith

Ich bin ein bisschen spät dran, aber realisiert, dass scipy die Funktion istft ab 0.19.0 eingebaut hat

2
Miss Palmer

Hier ist mein Python-Code, vereinfacht für diese Antwort:

import scipy, pylab

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

Anmerkungen:

  1. Das list Verständnis ist ein kleiner Trick, den ich gerne benutze, um die Blockverarbeitung von Signalen in numpy/scipy zu simulieren. Es ist wie blkproc in Matlab. Anstelle einer for-Schleife wende ich innerhalb eines Listenverständnisses einen Befehl (z. B. fft) auf jeden Frame des Signals an, und scipy.array wandelt ihn dann in ein 2D-Array um. Ich benutze dies, um Spektrogramme, Chromagramme, MFCC-Gramme und vieles mehr zu erstellen.
  2. In diesem Beispiel verwende ich eine naive Overlap-and-Add-Methode in istft. Um das ursprüngliche Signal zu rekonstruieren, muss die Summe der sequentiellen Fensterfunktionen konstant sein, vorzugsweise gleich Eins (1,0). In diesem Fall habe ich das Hann (oder hanning) -Fenster und eine Überlappung von 50% gewählt, die perfekt funktioniert. Siehe diese Diskussion für weitere Informationen.
  3. Es gibt wahrscheinlich prinzipiellere Möglichkeiten, die ISTFT zu berechnen. Dieses Beispiel soll hauptsächlich pädagogisch sein.

Ein Test:

if __== '__main__':
    f0 = 440         # Compute the STFT of a 440 Hz sinusoid
    fs = 8000        # sampled at 8 kHz
    T = 5            # lasting 5 seconds
    framesz = 0.050  # with a frame size of 50 milliseconds
    hop = 0.025      # and hop size of 25 milliseconds.

    # Create test signal and STFT.
    t = scipy.linspace(0, T, T*fs, endpoint=False)
    x = scipy.sin(2*scipy.pi*f0*t)
    X = stft(x, fs, framesz, hop)

    # Plot the magnitude spectrogram.
    pylab.figure()
    pylab.imshow(scipy.absolute(X.T), Origin='lower', aspect='auto',
                 interpolation='nearest')
    pylab.xlabel('Time')
    pylab.ylabel('Frequency')
    pylab.show()

    # Compute the ISTFT.
    xhat = istft(X, fs, T, hop)

    # Plot the input and output signals over 0.1 seconds.
    T1 = int(0.1*fs)

    pylab.figure()
    pylab.plot(t[:T1], x[:T1], t[:T1], xhat[:T1])
    pylab.xlabel('Time (seconds)')

    pylab.figure()
    pylab.plot(t[-T1:], x[-T1:], t[-T1:], xhat[-T1:])
    pylab.xlabel('Time (seconds)')

STFT of 440 Hz sinusoidISTFT of beginning of 440 Hz sinusoidISTFT of end of 440 Hz sinusoid

60
Steve Tjoa

Hier ist der STFT-Code, den ich verwende. STFT + ISTFT gibt hier perfekte Rekonstruktion (auch für die ersten Frames). Ich habe den hier von Steve Tjoa angegebenen Code leicht modifiziert: Hier entspricht die Größe des rekonstruierten Signals der des Eingangssignals.

import scipy, numpy as np

def stft(x, fftsize=1024, overlap=4):   
    hop = fftsize / overlap
    w = scipy.hanning(fftsize+1)[:-1]      # better reconstruction with this trick +1)[:-1]  
    return np.array([np.fft.rfft(w*x[i:i+fftsize]) for i in range(0, len(x)-fftsize, hop)])

def istft(X, overlap=4):   
    fftsize=(X.shape[1]-1)*2
    hop = fftsize / overlap
    w = scipy.hanning(fftsize+1)[:-1]
    x = scipy.zeros(X.shape[0]*hop)
    wsum = scipy.zeros(X.shape[0]*hop) 
    for n,i in enumerate(range(0, len(x)-fftsize, hop)): 
        x[i:i+fftsize] += scipy.real(np.fft.irfft(X[n])) * w   # overlap-add
        wsum[i:i+fftsize] += w ** 2.
    pos = wsum != 0
    x[pos] /= wsum[pos]
    return x
9
Basj

librosa.core.stft und istft sehen ziemlich ähnlich aus wie das, wonach ich gesucht habe, obwohl es sie zu diesem Zeitpunkt noch nicht gab:

librosa.core.stft(y, n_fft=2048, hop_length=None, win_length=None, window=None, center=True, dtype=<type 'numpy.complex64'>)

Sie invertieren jedoch nicht genau. Die Enden sind verjüngt.

3
endolith

Keine der obigen Antworten funktionierte gut OOTB = für mich. Also habe ich Steve Tjoa's modifiziert. 

import scipy, pylab
import numpy as np

def stft(x, fs, framesz, hop):
    """
     x - signal
     fs - sample rate
     framesz - frame size
     hop - hop size (frame size = overlap + hop size)
    """
    framesamp = int(framesz*fs)
    hopsamp = int(hop*fs)
    w = scipy.hamming(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):
    """ T - signal length """
    length = T*fs
    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]))
    # calculate the inverse envelope to scale results at the ends.
    env = scipy.zeros(T*fs)
    w = scipy.hamming(framesamp)
    for i in range(0, len(x)-framesamp, hopsamp):
        env[i:i+framesamp] += w
    env[-(length%hopsamp):] += w[-(length%hopsamp):]
    env = np.maximum(env, .01)
    return x/env # right side is still a little messed up...
1
David

Eine andere STFT gefunden, aber keine entsprechende Umkehrfunktion:

http://code.google.com/p/pytfd/source/browse/trunk/pytfd/stft.py

def stft(x, w, L=None):
    ...
    return X_stft
  • w ist eine Fensterfunktion als Array
  • L ist die Überlappung in Samples
1
endolith

Ich habe dies auch auf GitHub gefunden, aber es scheint, dass es auf Pipelines statt auf normalen Arrays angewendet wird:

http://github.com/ronw/frontend/blob/master/basic.py#LID281

def STFT(nfft, nwin=None, nhop=None, winfun=np.hanning):
    ...
    return dataprocessor.Pipeline(Framer(nwin, nhop), Window(winfun),
                                  RFFT(nfft))


def ISTFT(nfft, nwin=None, nhop=None, winfun=np.hanning):
    ...
    return dataprocessor.Pipeline(IRFFT(nfft), Window(winfun),
                                  OverlapAdd(nwin, nhop))
0
endolith

Eine feste Version der Antwort von basj.

import scipy, numpy as np
import matplotlib.pyplot as plt

def stft(x, fftsize=1024, overlap=4):
    hop=fftsize//overlap
    w = scipy.hanning(fftsize+1)[:-1]      # better reconstruction with this trick +1)[:-1]  
    return np.vstack([np.fft.rfft(w*x[i:i+fftsize]) for i in range(0, len(x)-fftsize, hop)])

def istft(X, overlap=4):   
    fftsize=(X.shape[1]-1)*2
    hop=fftsize//overlap
    w=scipy.hanning(fftsize+1)[:-1]
    rcs=int(np.ceil(float(X.shape[0])/float(overlap)))*fftsize
    print(rcs)
    x=np.zeros(rcs)
    wsum=np.zeros(rcs)
    for n,i in Zip(X,range(0,len(X)*hop,hop)): 
        l=len(x[i:i+fftsize])
        x[i:i+fftsize] += np.fft.irfft(n).real[:l]   # overlap-add
        wsum[i:i+fftsize] += w[:l]
    pos = wsum != 0
    x[pos] /= wsum[pos]
    return x

a=np.random.random((65536))
b=istft(stft(a))
plt.plot(range(len(a)),a,range(len(b)),b)
plt.show()

Ich denke, scipy.signal hat, wonach du suchst. Es hat vernünftige Standardeinstellungen, unterstützt mehrere Fenstertypen usw.

http://docs.scipy.org/doc/scipy-0.17.0/reference/generated/scipy.signal.spectrogram.html

from scipy.signal import spectrogram
freq, time, Spec = spectrogram(signal)
0
meatcomputer