web-dev-qa-db-fra.com

STFT et ISTFT inversibles en Python

Existe-t-il une forme polyvalente de transformation à court terme de Fourier avec transformation inverse correspondante intégrée dans SciPy ou NumPy ou autre?

Il y a la fonction pyplot specgram dans matplotlib, qui appelle ax.specgram(), qui appelle mlab.specgram(), qui appelle _spectral_helper() :

#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.

mais

Il s'agit d'une fonction d'assistance qui implémente les points communs entre le 204 # psd, csd et spectrogramme. Il est NOT destiné à être utilisé en dehors de mlab

Je ne suis pas sûr si cela peut être utilisé pour faire un STFT et ISTFT, cependant. Y at-il autre chose, ou devrais-je traduire quelque chose comme ces fonctions MATLAB ?

Je sais comment écrire ma propre implémentation ad-hoc; Je cherche simplement quelque chose de complet, capable de gérer différentes fonctions de fenêtrage (mais qui a une valeur par défaut raisonnable), qui est totalement invertible avec les fenêtres COLA (istft(stft(x))==x), testé par plusieurs personnes, sans aucun décalage. erreurs, gère bien les fins et zéro remplissage, mise en œuvre rapide de RFFT pour une entrée réelle, etc.

48
endolith

Je suis un peu en retard mais scipy a intégré la fonction istft à compter du 0.19.0.

2
Miss Palmer

Voici mon code Python, simplifié pour cette réponse:

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

Remarques:

  1. La compréhension de liste est un petit truc que j’aime utiliser pour simuler le traitement en bloc de signaux dans numpy/scipy. C'est comme blkproc dans Matlab. Au lieu d’une boucle for, j’applique une commande (par exemple, fft) à chaque image du signal à l’intérieur d’une liste de compréhension, puis scipy.array le transforme en tableau 2D. J'utilise ceci pour créer des spectrogrammes, des chromagrammes, des grammes MFCC et bien plus encore.
  2. Pour cet exemple, j’utilise une méthode naïve de chevauchement et d’ajout dans istft. Pour reconstruire le signal d'origine, la somme des fonctions de la fenêtre séquentielle doit être constante, de préférence égale à l'unité (1.0). Dans ce cas, j'ai choisi la fenêtre Hann (ou hanning) et un chevauchement de 50% qui fonctionne parfaitement. Voir cette discussion pour plus d'informations.
  3. Il y a probablement plus de méthodes fondées sur des principes pour calculer ISTFT. Cet exemple est principalement destiné à être éducatif.

Un examen:

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

Voici le code STFT que j'utilise. STFT + ISTFT donne ici une reconstruction parfaite (même pour les premières images). J'ai légèrement modifié le code donné ici par Steve Tjoa: la magnitude du signal reconstruit est la même que celle du signal d'entrée.

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

Aucune des réponses ci-dessus n'a bien fonctionné OOTB pour moi. J'ai donc modifié celui de Steve Tjoa. 

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

Trouvé un autre STFT, mais pas de fonction inverse correspondante:

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

def stft(x, w, L=None):
    ...
    return X_stft
  • w est une fonction de fenêtre sous forme de tableau
  • L est le chevauchement, en échantillons
1
endolith

J'ai également trouvé ceci sur GitHub, mais il semble fonctionner sur des pipelines au lieu de tableaux classiques:

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

Une version corrigée de la réponse de 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()

Je pense que scipy.signal a ce que vous cherchez. Il a des valeurs par défaut raisonnables, supporte plusieurs types de fenêtres, etc ...

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