Por que eu recebo esse ruído crepitante ao zerar as altas frequências?

8

Recentemente, comecei a brincar com a transformação de Fourier (depois de passar algumas semanas aprendendo sobre a matemática por trás dela). Decidi tentar hackear um filtro passa-baixo na seguinte sequência de sons:

Particularmente, peguei a transformada de Fourier, zerei o 1/2 mais alto das frequências e depois a transformada de Fourier inversa. Isto é o que eu tenho

Por que esse barulho de estalido?

JeremyKun
fonte
Além disso, devo mencionar que não faço ideia por que estou aplicando um filtro passa-baixo a um clipe de som. É puramente experimental. Essa operação faz sentido para um clipe de som?
21712 JeremyKun
você deve fazer essas amostras para download
endolith

Respostas:

11

Dois possíveis problemas:

  1. A filtragem no domínio da frequência (usando uma FFT) requer adição de sobreposição, economia de sobreposição ou um algoritmo relacionado. Isso é causado pela diferença entre convolução linear e circular. Caso contrário, você obtém um alias no domínio do tempo
  2. Alguns dos ruídos parecem simples recortes. Na verdade, a filtragem pode aumentar a amplitude do domínio do tempo para algumas amostras e, se isso exceder o intervalo disponível, ela é cortada ou enrolada.
Hilmar
fonte
11
Soou como recorte / embrulho para mim.
21812 heltonbiker
O que posso fazer para interromper o recorte / embalagem? Eu não deveria ter feito essa operação em primeiro lugar?
JeremyKun
É definitivamente um recorte. Aumentei a amplitude do sinal de entrada e os sons de crepitação desapareceram.
JeremyKun
7

Primeiro, apenas uma observação: as transformadas de Fourier não são ideais para filtros de passa baixa / alta. Os filtros Butterworth são um bom lugar para começar e depois dos filtros Chebyshev / Elíptico, se você for mais ambicioso.

Parece que você está tentando implementar um filtro ideal. Não há como implementar esses filtros de 'parede de tijolos' onde cortamos todas as frequências acima / abaixo de um determinado valor. Todos os filtros bem desenvolvidos diminuirão de 1 a 0 em torno da nossa frequência de corte.

Os filtros ideais são apenas possíveis teóricos e, se você tivesse uma transformação contínua de Fourier, seu método acima funcionaria.

Mas estamos fazendo transformações discretas de Fourier, para que haja mais com o que se preocupar. Como não tenho certeza do método de sua implementação, acho que você está fazendo janelas, já que apenas a retirada de frequências é uma maneira certa de ficar crepitando em uma DFT com janelas.

Ao fazer janelas em um DFT, pode-se pensar que as amplitudes de frequência entre janelas são relativamente contínuas. por exemplo, se a frequência de 400Hz tiver uma amplitude de 0,5 na janela atual, na próxima janela a amplitude será próxima de 0,5. Infelizmente, isso não é verdade; portanto, se simplesmente removermos a frequência de 400Hz da nossa DFT, poderemos ouvir estalos ou rachaduras entre as janelas.

Um pequeno exemplo: a taxa de corte é de 600Hz. A janela 1 está reproduzindo um seno de 800Hz. A janela 2 se conecta 'continuamente' à janela 1 e reproduz 400Hz. Então ouviremos um pop entre a janela porque a janela 1 ficará silenciosa e a janela 2 será ligada imediatamente.

Outra coisa a ter em mente é que só podemos representar uma quantidade finita de frequências com uma DFT. Se tivermos um arquivo de áudio com uma onda senoidal de uma frequência entre duas de nossas frequências discretas em nossa DFT, então na verdade o representamos com muitas de nossas frequências discretas. Portanto, mesmo que um exemplo de arquivo de áudio possa conter uma onda senoidal menor que o nosso ponto de corte, se sua frequência estiver entre as nossas frequências DFT, poderemos cortar parte dele e distorcê-lo com o método acima, uma vez que são necessárias frequências mais altas para representar o áudio Arquivo.

espero que ajude

Matt Tytel
fonte
Ah, retiro meu comentário de janela (é mais um problema de DFT em tempo real). A resposta de Hilmar parece mais precisa.
Matt Tytel
4

s(t)=slow(t)+shigh(t)

slow(t)=cos(2πf0t)+cos(2πf1t+π3)

shigh(t)=12cos(2πf2t+0.2)

f0,f1fcutf0<f1<fcutf2>fcut

Nfs>2f2fs2f2

Combinei um pequeno programa Python para ilustrar alguns dos conceitos - o código é horrível, mas peguei um código antigo que eu tinha para problemas semelhantes. Embora quase não haja comentários, deve ser bastante fácil de seguir devido aos pequenos módulos. O são dois dft / IDFT funções; duas funções fshiftn / fshiftp para mudar a frequência do domínio domínio DFT para filtragem; uma função dftlpass para realizar a filtragem no domínio DFT; uma função zpblpass para fazer a filtragem usando um filtro Butterworth; uma função bbdftsig para formar o sinal de teste e realizar a filtragem; e, finalmente, uma pequena função plotsigstraçar os sinais. No final do script, os diferentes parâmetros são definidos e as diferentes figuras são feitas.

"""
   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)

N=5000fs=1000fs/N=0.2f0,f1,f2f0=3f1=11f2=21fcut=15

Início dos sinais - em rede Meio dos sinais - na rede Fim de sinais - em rede

sslowslowf1. Como é bastante típico para esse tipo de processamento, temos algumas diferenças no início e no final da sequência devido a efeitos de borda e razoavelmente boa concordância entre os dois tipos de filtragem na seção intermediária.

f1f1=11.5

Início dos sinais - fora da rede Meio dos sinais - fora da rede Fim de sinais - fora da rede

slow

Portanto, em conclusão, é possível usar a filtragem direta forçando os coeficientes de Fourier a zero, o que também é feito algumas vezes na detecção compressiva para reduzir o suporte de um sinal para forçar a escarsidade a um sinal. No entanto, há conseqüências disso, devido ao aumento de erros, principalmente nas bordas do sinal. Além disso, o acima é o melhor caso em que todo o sinal é tratado como uma sequência. Se o sinal precisar ser dividido em períodos de tempo, fica complicado, pois precisamos considerar uma técnica de janelas ou outra técnica para garantir a continuidade do sinal entre os quadros. Portanto, meu conselho é semelhante a alguns dos outros posts sobre a recomendação de usar normalmente um Butterworth / Elliptic / .. ou qualquer outro filtro.

Lars1
fonte
0

Zerar posições em uma FFT pode realmente aumentar a amplitude de outras frequências próximas, mas não centralizadas, na posição zero-zero ou em suas posições adjacentes. Esse aumento pode causar recorte.

Além disso, se você estiver executando a FFT usando blocos sem preenchimento zero (e não sobrepostos) (em oposição à música inteira em uma grande FFT), quaisquer modificações nos dados da FFT serão contornadas de trás para a frente do teclado. sequência do domínio do tempo em cada bloco, adicionando outras descontinuidades estranhas nos lugares errados.

hotpaw2
fonte
0

Aqui está um filtro de passagem de banda FFT de zeragem de caixa rápido e sujo, com o código FFT também.

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 amostra do áudio de entrada, chame mainbandpass com a amostra de entrada e a faixa de frequências que você deseja manter em atalho e atalho. Se lowcut for maior que highcut, o resultado será um filtro de rejeição de banda. Há uma convolução circular em andamento, mas não haverá emissões fora da banda, o que é bom para os modems.

Brent Fisher
fonte