¿Por qué obtengo este ruido crujiente al poner a cero las frecuencias altas?

8

Recientemente comencé a jugar con la transformación de Fourier (después de pasar unas semanas aprendiendo sobre las matemáticas detrás de ella). Decidí intentar hackear un filtro de paso bajo en el siguiente sonido:

En particular, tomé la transformada de Fourier, puse a cero la mitad más alta de las frecuencias, y luego tomé la transformada inversa de Fourier. Esto es lo que conseguí

¿Por qué hay ese crujido?

JeremyKun
fuente
Además, debo mencionar que no tengo idea de por qué estoy aplicando un filtro de paso bajo a un clip de sonido. Es puramente experimental. ¿Esa operación tiene sentido para un clip de sonido?
JeremyKun
debe hacer que esas muestras se
puedan

Respuestas:

11

Dos posibles problemas:

  1. Hacer el filtrado en el dominio de frecuencia (usando una FFT) requiere superposición-adición, superposición-guardado o un algoritmo relacionado. Esto es causado por la diferencia entre convolución lineal y circular. De lo contrario, obtienes alias de dominio de tiempo
  2. Parte del ruido suena como un simple recorte. El filtrado en realidad puede aumentar la amplitud del dominio del tiempo para algunas muestras y, si eso excede el rango disponible, se recorta o se envuelve.
Hilmar
fuente
1
A mí me pareció un recorte / envoltura.
heltonbiker
¿Qué puedo hacer para detener el recorte / envoltura? ¿No debería haber hecho esta operación en primer lugar?
JeremyKun
Definitivamente es un recorte. Disminuí la amplitud de la señal de entrada y los crujidos desaparecieron.
JeremyKun
7

Primero, solo una nota, las transformadas de Fourier no son ideales para filtros de paso bajo / alto. Los filtros Butterworth son un buen lugar para comenzar y seguir los filtros Chebyshev / Elípticos si se vuelve más ambicioso.

Parece que estás intentando implementar un filtro ideal. No hay forma de que podamos implementar estos filtros de 'pared de ladrillos' donde cortamos todas las frecuencias por encima / debajo de un valor dado. Todos los filtros bien desarrollados disminuirán de 1 a 0 alrededor de nuestra frecuencia de corte.

Los filtros ideales solo son teóricos posibles y si tuviera una Transformada continua de Fourier su método anterior funcionaría.

Pero estamos haciendo transformaciones discretas de Fourier, por lo que hay más de qué preocuparse. Como no estoy seguro del método de su implementación, supongo que está haciendo ventanas, ya que solo extraer frecuencias es una forma segura de obtener chisporroteo en un DFT con ventana.

Al hacer ventanas en un DFT, uno podría pensar que las amplitudes de frecuencia entre ventanas son relativamente continuas. por ejemplo, si la frecuencia de 400Hz tiene una amplitud de 0.5 en la ventana actual, en la siguiente ventana la amplitud estará cerca de 0.5. Desafortunadamente, esto no es cierto, por lo que si simplemente elimináramos la frecuencia de 400Hz de nuestro DFT, podríamos escuchar ruidos fuertes o grietas entre ventanas.

Un pequeño ejemplo: la frecuencia de corte es de 600 Hz. La ventana 1 reproduce un seno de 800 Hz. La ventana 2 se conecta 'continuamente' con la ventana 1 y reproduce 400 Hz. Luego escucharemos un pop entre la ventana porque la ventana 1 estará en silencio y la ventana 2 se encenderá inmediatamente.

Otra cosa a tener en cuenta es que solo podemos representar una cantidad finita de frecuencias con un DFT. Si tenemos un archivo de audio con una onda sinusoidal de una frecuencia entre dos de nuestras frecuencias discretas en nuestro DFT, en realidad lo representamos con muchas de nuestras frecuencias discretas. Entonces, aunque un archivo de audio de ejemplo puede contener una onda sinusoidal inferior a nuestro límite, si su frecuencia está entre nuestras frecuencias DFT, podríamos cortar parte de él y distorsionarlo con el método anterior, ya que se necesitan frecuencias más altas para representar el audio. archivo.

Espero que ayude

Matt Tytel
fuente
Ah, retracto mi comentario de ventanas (es más un problema de DFT en tiempo real). La respuesta de Hilmar parece más precisa.
Matt Tytel
4

s(t)=slow(t)+shyosolh(t)

slow(t)=cos(2πF0 0t)+cos(2πF1t+π3)

shyosolh(t)=12cos(2πF2t+0.2 0.2)

F0 0,F1FCtutF0 0<F1<FCtutF2>FCtut

norteFs>2F2Fs2F2

He combinado un pequeño programa Python para ilustrar algunos de los conceptos: el código es bastante horrible, pero acabo de tomar un código viejo que tenía para problemas similares. Aunque casi no hay comentarios, debería ser bastante fácil de seguir debido a los pequeños módulos. El son dos DFT / IDFT funciones; dos funciones fshiftn / fshiftp para cambiar la frecuencia de la señal i dominio DFT para filtrado; una función dftlpass para realizar el filtrado en el dominio DFT; una función zpblpass para hacer el filtrado mediante el uso de un filtro Butterworth; una función bbdftsig para formar la señal de prueba y realizar el filtrado; y finalmente una pequeña función tramapara trazar las señales. Al final del guión, se establecen los diferentes parámetros y se hacen las diferentes figuras.

"""
   Test of DFT versus scipy.signal.butter filtering with respect to
   signal reconstruction.

"""

# import ############################################################ import #
import matplotlib as mpl;   mpl.rcParams['backend'] = 'Agg'
import matplotlib.pyplot as mplpp
import matplotlib.mlab as mplml
import numpy as np
import scipy.signal as sps


# initialize #################################################### initialize #
try:
    mpl.rc('text', usetex=False)
    mpl.rc('font', family='serif')
    mpl.rc('font', serif='STIXGeneral')
    mpl.rc('font', size=8)
except AttributeError:
    None


# dft ################################################################## dft #
def dft(xt, fs, t0):
    N, d = len(xt), -2j*np.pi/len(xt)
    w = np.arange(N, dtype=np.float).reshape((N,1))
    c = np.exp(d*t0*fs*w)
    W = np.exp(d*np.dot(w,np.transpose(w)))
    xf = np.multiply(c,np.dot(W,xt)) / float(N)
    f = w*fs/float(N)
    return xf, f


# idft ################################################################ idft #
def idft( X, FS, T0 ):
    N, d = len(X), 2j*np.pi/len(X)
    w = np.arange(N, dtype=float).reshape((N,1))
    cc = np.exp(d*T0*FS*w)
    Wc = np.exp(d*np.dot(w, np.transpose(w)))
    Y = np.dot(Wc, np.multiply(cc, X))
    return Y



# fshiftn ########################################################## fshiftn #
def fshiftn( xf, f ):
    assert type(f) == np.ndarray, "f must be a np.ndarray"
    assert f.shape[1] == 1, "f must be a column array"
    assert xf.shape[1] == 1, "xf must be a column array"
    assert sum(f<0) == 0, "All frequency components must be 0 or positive"

    # Determine sampling rate, tolerance, and allocate output array
    fs, tol = len(f)*(np.abs(f[1,0]-f[0,0])), 1.E-2
    fshift = np.zeros((len(f),1), dtype=float)
    xfshift = np.zeros((len(f),1), dtype=complex)

    # Determine index where f > fs/2
    Nm = np.floor(len(f)/2.0)
    Np = np.floor((len(f)-1.0)/2.0)

    # Compute output frequency array such that -fs/2 <= f < fs/2 and the
    # corresponding Fourier coefficients
    fshift[:Nm,0] = f[Np+1:,0] - fs
    fshift[Nm,0] = f[0,0]
    fshift[Nm+1:,0] = f[1:Np+1,0]

    xfshift[:Nm,0] = xf[Np+1:,0]
    xfshift[Nm,0] = xf[0,0]
    xfshift[Nm+1:,0] = xf[1:Np+1,0]

    return xfshift, fshift


# fshiftp ########################################################## fshiftp #
def fshiftp(xf, f):
    assert type(f) == np.ndarray, "f must be a np.ndarray"
    assert f.shape[1] == 1, "f must be a column array"
    assert xf.shape[1] == 1, "xf must be a column array"
    assert sum(f<0) > 0, "Some input frequencies must be negative"

    # Determine sampling rate, tolerance, and allocate output array
    fs, tol = len(f)*(np.abs(f[1,0]-f[0,0])), 1.E-2
    fshift = np.zeros((len(f),1), dtype=float)
    xfshift = np.zeros((len(f),1), dtype=complex)

    # Determine index where f > fs/2
    #Nx = np.floor((len(f)+1+tol)/2)
    Nm = np.floor(len(f)/2.0)
    Np = np.floor((len(f)-1.0)/2.0)

    # Compute output frequency array such that -fs/2 <= f < fs/2 and the
    # corresponding Fourier coefficients
    fshift[Np+1:,0] = f[:Nm:,0] + fs
    fshift[0,0] = f[Nm,0]
    fshift[1:Np+1:,0] = f[Nm+1:,0]

    xfshift[Np+1:,0] = xf[:Nm:,0]
    xfshift[0,0] = xf[Nm,0]
    xfshift[1:Np+1:,0] = xf[Nm+1:,0]

    return xfshift, fshift


# dftlpass ######################################################## dftlpass #
def dftlpass(xt, fs, fcut):
    # Perform Discrete Fourier Transform
    xf, f = dft(xt, fs, 0.0)

    # Shift frequencies to -fs/2 <= f < fs/2 ... and coefficients
    xfshift, fshift = fshiftn(xf, f)

    # Perform filtration
    xfshift = xfshift * (np.abs(fshift) <= fcut)

    # Re-shift frequencies to 0 <= f < fs ... and coefficients
    xfrecon, frecon = fshiftp(xfshift, fshift)

    # Perform inverse Discrete Fourier Transform
    yt = idft(xfrecon, fs, 0.0)
    return yt.real


# zpblpass ######################################################## zpblpass #
def zpblpass(xn, fcal, fs, fcut):
    bz, az = sps.butter(5, fcut/(fs/2))

    # Gain calibration
    Ncal = np.max([np.int(20*fs/fcal), 30000])
    Nguard = np.int(0.1*Ncal)    
    t = np.arange(Ncal) / fs
    x0_cal = 1.0 * np.cos(2*np.pi*fcal*t)
    yi_cal = sps.filtfilt(bz, az, 2.0*x0_cal*np.cos(2*np.pi*fcal*t))
    k = 1.0/np.mean(yi_cal[Nguard:Ncal-Nguard])

    # Scaled output
    yn = k * sps.filtfilt(bz, az, xn)
    return yn


# bbdftsig ######################################################## bbdftsig #
def bbdftsig(f0, f1, f2, fcut, fs, N):
    t = np.arange(N).reshape((N,1)) / fs
    s0 = np.sin(2*np.pi*f0*t)
    s1 = np.sin(2*np.pi*f1*t + 0.2)
    s2 = 0.7 * np.sin(2*np.pi*f2*t + np.pi/3.0)
    slow = s0 + s1
    s = slow + s2

    sf = dftlpass(s, fs, fcut)
    sfdftv = sf.reshape((N))
    sv = s.reshape((N))
    slowv = slow.reshape((N))

    sv = s.reshape((N))
    sfzpbv = zpblpass(sv, f1, fs, fcut)
    #sfzpbv = sfzpb.reshape((N))
    return sv, slowv, sfdftv, sfzpbv


# plotsigs ######################################################## plotsigs #
def plotsigs(s, slow, sfdft, sfzpb, Nstart, Nstop, fname):
    n = np.arange(s.shape[0])

    # Plot results
    mplpp.figure(1, (5.0,2.25))
    mplpp.clf()
    mplpp.plot(n[Nstart:Nstop], s[Nstart:Nstop], 'm-',
               n[Nstart:Nstop:4], s[Nstart:Nstop:4], 'mx',
               n[Nstart:Nstop], slow[Nstart:Nstop], 'g-',
               n[Nstart:Nstop:10], slow[Nstart:Nstop:10], 'gx',
               n[Nstart:Nstop], sfdft[Nstart:Nstop], 'r-',
               n[Nstart:Nstop:15], sfdft[Nstart:Nstop:15], 'rx',
               n[Nstart:Nstop], sfzpb[Nstart:Nstop], 'b-',
               linewidth=1.5)
    mplpp.legend([r'$s$', r'$s$', r'$s_{\rm low}$', r'$s_{\rm low}$',
                  r'DFT', r'DFT', r'ZPB'], loc='upper right')
    mplpp.ylabel(r'Signal')
    mplpp.xlabel(r'$n$')
    #mplpp.axis([-10.0, 10.0, 1.0E-2, 1.0E2])
    mplpp.grid(True)
    mplpp.savefig(fname, dpi=600,
                bbox_inches='tight', pad_inches=0.05)
    mplpp.close()


# __main__ ######################################################## __main__ #
if __name__ == '__main__':
    # Initialize
    f0 = 3.0
    f1 = 11.5
    f2 = 20.0
    fcut = 15.0
    fs = 1000.0
    N = 5000

    s, slow, sfdft, sfzpb = bbdftsig(f0, f1, f2, fcut, fs, N)
    n = np.arange(s.shape[0])

    # Fig. 1: full data set
    Nstart = 0
    Nstop = N
    fname = 'full.pdf'
    plotsigs(s, slow, sfdft, sfzpb, Nstart, Nstop, fname)

    # Fig. 2: beginning
    Nstart = 0
    Nstop = 150
    fname = 'beginning.pdf'
    plotsigs(s, slow, sfdft, sfzpb, Nstart, Nstop, fname)

    # Fig. 3: middle
    Nstart = np.floor(N/2.0) - 75
    Nstop = Nstart + 100
    fname = 'middle.pdf'
    plotsigs(s, slow, sfdft, sfzpb, Nstart, Nstop, fname)

    # Fig. 4: ending
    Nstart = N - 150
    Nstop = N
    fname = 'ending.pdf'
    plotsigs(s, slow, sfdft, sfzpb, Nstart, Nstop, fname)

norte=5000Fs=1000Fs/ /norte=0.2 0.2F0 0,F1,F2F0 0=3F1=11F2=21FCtut=15

Comienzo de señales - en red Centro de señales - en red Finalización de señales - en la red

sslowslowF1. Como es bastante típico para este tipo de procesamiento, tenemos algunas diferencias al principio y al final de la secuencia debido a los efectos de borde y al acuerdo razonablemente bueno entre ambos tipos de filtrado en la sección central.

F1F1=11,5

Comienzo de señales - fuera de la red Centro de señales - fuera de la red Finalización de señales - fuera de la red

slow

En conclusión, es posible utilizar el filtrado directo forzando los coeficientes de Fourier a cero, lo que también se hace a veces en la detección de compresión para reducir el soporte de una señal para forzar la dispersión en una señal. Sin embargo, hay consecuencias de esto como errores aumentados en particular en los bordes de la señal. Además, lo anterior es un mejor caso en el que toda la señal se trata como una secuencia. Si la señal debe dividirse en marcos de tiempo, se complica ya que entonces debemos considerar algunas técnicas de ventanas u otras técnicas para garantizar la continuidad de la señal entre cuadros. Por lo tanto, mi consejo es similar a algunas de las otras publicaciones al recomendar el uso normal de Butterworth / Elliptic / ... o cualquier filtro.

Lars1
fuente
0

Poner a cero los contenedores en una FFT en realidad puede aumentar la amplitud de otras frecuencias cercanas pero no centradas en el contenedor de ed cero o sus contenedores adyacentes. Este aumento puede causar recorte.

Además, si está haciendo la FFT usando bloques rellenados sin ceros (y sin superposición añadida) (a diferencia de la canción completa en una gran FFT), cualquier modificación de datos de FFT se ajustará desde la parte posterior hasta la parte frontal del secuencia de dominio de tiempo en cada bloque, agregando así otras discontinuidades extrañas en los lugares equivocados.

hotpaw2
fuente
0

Aquí hay un filtro de paso de banda FFT rápido y sucio, con el código FFT también.

void FFT(int n, int inverse, double *gRe, double *gIm, double *GRe, double *GIm)
{

    int m = 0;
    int p = 1;
    int j = 0;
    int i1=0;
    int k=0;
    double ca=0;
    double sa=0;
    int l1,l2,l3;
    double u1,u2;
    double t1 = 0;
    double t2 = 0;
    int i2=0;
    double z;
    /* Calculate m=log_2(n) */
    while(p < n)
    {
        p *= 2;
        m++;
    }
    /* Bit reversal */
    GRe[n - 1] = gRe[n - 1];
    GIm[n - 1] = gIm[n - 1];
    for(i1 = 0; i1 < n - 1; i1++)
    {
        GRe[i1] = gRe[j];
        GIm[i1] = gIm[j];
        k = n / 2;
        while(k <= j)
        {
            j -= k;
            k /= 2;
        }
        j += k;
    }
    /* Calculate the FFT */
    ca = -1.0;
    sa = 0.0;
    l1 = 1;
    l2 = 1;
    l3=0;
    for(l3 = 0; l3 < m; l3++)
    {
        l1 = l2;
        l2 *= 2;
        u1 = 1.0;
        u2 = 0.0;       
    for(j = 0; j < l1; j++)
        {
            i2=j;
            for(i2 = j; i2 < n; i2 += l2)
            {
                i1 = i2 + l1;
                t1 = u1 * GRe[i1] - u2 * GIm[i1];
                t2 = u1 * GIm[i1] + u2 * GRe[i1];
                GRe[i1] = GRe[i2] - t1;
                GIm[i1] = GIm[i2] - t2;
                GRe[i2] += t1;
                GIm[i2] += t2;
            }
            z =  u1 * ca - u2 * sa;
            u2 = u1 * sa + u2 * ca;
            u1 = z;
        }
        sa = sqrt((1.0 - ca) / 2.0);
        if(!inverse) sa =- sa;
        ca = sqrt((1.0 + ca) / 2.0);

    }
    /* Divide through n if it isn't the IDFT */
    if(!inverse)
    {
        int i3=0;
        for(i3 = 0; i3 < n; i3++)
        {
            GRe[i3] /= n;
            GIm[i3] /= n;
        }
    }
}


void mainfftBandPass(double *insamples, double *outsamples, unsigned long fftsize, long lowfreq, long highfreq, long srate)
{
    static double *inbuf=NULL;
    static double *realn=NULL;
    static double *imags=NULL;
    static double *spectr=NULL;
    static double *zer0=NULL;
    static double *olds=NULL;
    static double *infader=NULL;
    static double *outfader=NULL;
    int notched=(highfreq<lowfreq) ? 1 : 0;
    long incounter=0;
    /* treble is the highest baseband frequency */
    /* bass the the lowest baseband frequency */
    /* this function is called twice per FFT block */
    long midcounter=0;
    long outcounter=0;
    long bass=lowfreq*(fftsize/(double)srate);
    long treble=(highfreq)*(fftsize/(double)srate);
    static long halffft=2;
    static long old_fftsize=0;
    static short first=1;
    if(first==1 || fftsize!=old_fftsize)
    {
        if(inbuf)
             free(inbuf);
        if(realn)
            free(realn);
        if(imags)
            free(imags);
        if(spectr)
            free(spectr);
        if(zer0)
            free(zer0);
        if(olds)
            free(olds);
        if(infader)
            free(infader);
        if(outfader)
            free(outfader);
        infader=(double*)malloc(fftsize*sizeof(double));
        outfader=(double*)malloc(fftsize*sizeof(double));
        inbuf=(double*)malloc(fftsize*sizeof(double));
        realn=(double*)malloc(fftsize*sizeof(double));
        imags=(double*)malloc(fftsize*sizeof(double));
        spectr=(double*)malloc(fftsize*sizeof(double));
        zer0=(double*)malloc(fftsize*sizeof(double));
        olds=(double*)malloc(fftsize*sizeof(double));
        if((!inbuf) || (!realn) ||(!imags) ||(!spectr)||(!zer0)||(!ol   ds))
        {
            printf("Not enough memory for FFT!\n");
                    exit(1);
        }
        halffft=fftsize/2;
        long infade=0;
        long outfade=halffft;
        for(infade=0;infade<halffft;infade++)
        {
            outfade--;
            outfader[infade]=(0.5 * cos((infade) *  M_PI/(double)(halffft))+0.5);
            infader[outfade]=outfader[infade];
        }
        first=0;
    }
    memset(realn,0,sizeof(double)*fftsize);
    for(incounter=0;incounter<halffft;incounter++)
    {
        inbuf[incounter]=inbuf[incounter+halffft];
    }
    for(incounter=0;incounter<halffft;incounter++)
    {
        inbuf[incounter+halffft]=insamples[incounter];
    }
    for(incounter=0;incounter<fftsize;incounter++)
    {
        realn[incounter]=inbuf[incounter];
    }   
    memset(imags,0,sizeof(double)*fftsize);
    FFT(fftsize, 0, realn,imags, spectr,zer0);
    memset(realn,0,sizeof(double)*fftsize);
    memset(imags,0,sizeof(double)*fftsize);
    if(notched==0)
    {
        for(midcounter=bass;midcounter<treble;midcounter++)
        {
            realn[midcounter]=spectr[midcounter] * 2.0;
            imags[midcounter]= zer0[midcounter] * 2.0;
        }
        if(bass==0)
            realn[0]=spectr[0];

    }
    else if(notched==1)
    {
        for(midcounter=0;midcounter<halffft;midcounter++)
        {
            if((midcounter<treble) ||(midcounter>bass))
            {
                realn[midcounter]=spectr[midcounter] * 2.0;
                imags[midcounter]= zer0[midcounter] * 2.0;
            }
        }
        if(bass==0)
        {
            realn[0]=0;
        }
        else
        {
            realn[0]=spectr[0];
        }
    }
    FFT(fftsize, 1, realn, imags,spectr,zer0);
    for(outcounter=0;outcounter<halffft;outcounter++)
    {
        outsamples[outcounter]=(((spectr[outcounter] )*infader[outcounter])+(olds[outcounter+halffft]*outfader[outcounter])) ;
    }
    for(outcounter=0;outcounter<fftsize;outcounter++)
    {
        olds[outcounter]=spectr[outcounter];
    }
    memset(spectr,0,fftsize*sizeof(double));
    memset(zer0,0,fftsize*sizeof(double));
    old_fftsize=fftsize;
}

signed short mainbandpass(signed short input, double lowcut, double highcut,long rate,long fftsize)
{
    double retvalue=0;
    static double *insamp=NULL;
    static double *outsamp=NULL;
    static int first=1;
    static int q=0;
    if(first==1)
    {
            insamp=(double*)malloc(fftsize * sizeof(double));
            outsamp=(double*)malloc(fftsize * sizeof(double));
            if(insamp==NULL || outsamp==NULL)
            {
                   printf("Not enough memory for FFT buffers.\n");
                   exit(1);
            }
        memset(insamp,0,fftsize * sizeof(double));
        memset(outsamp,0,fftsize * sizeof(double));
        first=0;
    }

    insamp[q]=input;
    retvalue=outsamp[q];
    if(retvalue> 32767)
        retvalue=32767;
    if(retvalue <-32768)
        retvalue=-32768;
    q++;
    if(q>(fftsize/2)-1)
    {
        mainfftBandPass(insamp,outsamp, fftsize, lowcut,highcut,rate);
        q=0;
    }
    return (signed short)retvalue;
}

para cada muestra del audio de entrada, llame a mainbandpass con la muestra de entrada y el rango de frecuencias que desea mantener en lowcut y highcut. Si lowcut es mayor que highcut, el resultado será un filtro de rechazo de banda. Hay una convolución circular, pero no habrá emisiones fuera de banda, lo cual es bueno para los módems.

Brent Fisher
fuente