Como escrever filtro lowpass para sinal amostrado em Python?


16

Eu tenho algum sinal que amostrou cada 1 ns (1e-9 segundos) e tem, digamos, 1e4 pontos. Preciso filtrar altas frequências desse sinal. Digamos que eu precise filtrar frequências maiores que 10 MHz. Eu quero que para frequências menores que o sinal de frequência de corte seja passado inalterado. Isso significa que o ganho do filtro será 1 para frequências inferiores à frequência de corte. Gostaria de poder especificar a ordem dos filtros. Quero dizer, o filtro de primeira ordem tem uma inclinação de 20 db / década (queda de energia) após a frequência de corte, o filtro de segunda ordem tem uma inclinação de 40 db / dec após a frequência de corte e assim por diante. Alto desempenho do código é importante.

Respostas:


19

A resposta de frequência para o filtro projetado usando a função de manteiga é:

Resposta do filtro Butterworth

Mas não há razão para limitar o filtro a um design de filtro monotônico constante. Se você deseja uma atenuação mais alta na faixa de parada e na faixa de transição mais íngreme, existem outras opções. Para obter mais informações sobre a especificação de um filtro usando iirdesing, consulte isso . Como mostra os gráficos de resposta em frequência para o design da manteiga , a frequência de corte (ponto de -3dB) está longe do objetivo. Isso pode ser atenuado pela amostragem reduzida antes da filtragem (as funções de design terão um tempo difícil com um filtro tão estreito, 2% da largura de banda). Vamos analisar a taxa de amostragem original com o limite especificado.

import numpy as np
from scipy import signal
from matplotlib import pyplot as plt

from scipy.signal import fir_filter_design as ffd
from scipy.signal import filter_design as ifd

# setup some of the required parameters
Fs = 1e9           # sample-rate defined in the question, down-sampled

# remez (fir) design arguements
Fpass = 10e6       # passband edge
Fstop = 11.1e6     # stopband edge, transition band 100kHz
Wp = Fpass/(Fs)    # pass normalized frequency
Ws = Fstop/(Fs)    # stop normalized frequency

# iirdesign agruements
Wip = (Fpass)/(Fs/2)
Wis = (Fstop+1e6)/(Fs/2)
Rp = 1             # passband ripple
As = 42            # stopband attenuation

# Create a FIR filter, the remez function takes a list of 
# "bands" and the amplitude for each band.
taps = 4096
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

# The iirdesign takes passband, stopband, passband ripple, 
# and stop attenuation.
bc, ac = ifd.iirdesign(Wip, Wis, Rp, As, ftype='ellip')  
bb, ab = ifd.iirdesign(Wip, Wis, Rp, As, ftype='cheby2') 

Filtros de taxa de amostra original

Como mencionado, como estamos tentando filtrar uma porcentagem tão pequena da largura de banda, o filtro não terá um corte agudo. Nesse caso, filtro lowpass, podemos reduzir a largura de banda para obter um filtro com melhor aparência. A função de reamostragem python / scipy.signal pode ser usada para reduzir a largura de banda.

Observe que a função de reamostragem realizará a filtragem para evitar aliases. A pré-filtragem também pode ser realizada (para reduzir o aliasing) e, nesse caso, poderíamos simplesmente re-amostrar em 100 e pronto , mas a pergunta foi feita sobre a criação de filtros. Neste exemplo, reduziremos a amostra em 25 e criaremos um novo filtro

R = 25;            # how much to down sample by
Fsr = Fs/25.       # down-sampled sample rate
xs = signal.resample(x, len(x)/25.)

Se atualizarmos os parâmetros de design para o filtro FIR, a nova resposta será.

# Down sampled version, create new filter and plot spectrum
R = 25.             # how much to down sample by
Fsr = Fs/R          # down-sampled sample rate
Fstop = 11.1e6      # modified stopband
Wp = Fpass/(Fsr)    # pass normalized frequency
Ws = Fstop/(Fsr)    # stop normalized frequency
taps = 256
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

Resposta de filtro com redução de amostra

O filtro que opera nos dados de amostragem reduzida tem uma resposta melhor. Outro benefício do uso de um filtro FIR é que você terá resposta de fase linear.


1
Obrigado. Como você cria um gráfico do espectro de sinal?
— 1212 Alex

Muito obrigado por uma excelente resposta! Gostaria de saber se você poderia explicar como aplicar um filtro FIR com base nos coeficientes calculados usando o Remez? Estou tendo problemas para entender o que filtfiltdeseja para o aparâmetro.
— 23413 ali13m

Depois de ter os coeficientes de um projeto do filtro, ( b para FIR b e um para IIR), você pode utilizar um par de funções diferentes para realizar a filtragem: lfilter , convolve , filtfilt . Normalmente, todas essas funções operam de maneira semelhante: y = filtfilt (b, a, x) Se você tiver um filtro FIR, basta definir a = 1 , x é o sinal de entrada, b é o coeficiente FIR. Esta postagem também pode ajudar.
— Christopher Felton

5

Isto funciona?

from __future__ import division
from scipy.signal import butter, lfilter

fs = 1E9 # 1 ns -> 1 GHz
cutoff = 10E6 # 10 MHz
B, A = butter(1, cutoff / (fs / 2), btype='low') # 1st order Butterworth low-pass
filtered_signal = lfilter(B, A, signal, axis=0)

Você está certo, porém, a documentação não é muito completa. Parece que butteré um invólucro para iirfilter, que está melhor documentado :

N: int A ordem do filtro. Wn: array_like Uma sequência escalar ou comprimento-2 que fornece as frequências críticas.

Porém, a maioria dessas coisas é clonada do matlab, para que você possa ver também a documentação deles :

a frequência de corte normalizada Wn deve ser um número entre 0 e 1, em que 1 corresponde à frequência de Nyquist, π radianos por amostra.

Atualizar:

Eu adicionei documentação para essas funções. :) O Github facilita isso.



1

Estou tendo bons resultados com este filtro FIR. Observa que aplica o filtro duas vezes, avançando e retrocedendo, de modo a compensar o deslocamento do sinal (a filtfiltfunção não funcionou, não sei por quê):

def firfilt(interval, freq, sampling_rate):
    nfreq = freq/(0.5*sampling_rate)
    taps =  sampling_rate + 1
    a = 1
    b = scipy.signal.firwin(taps, cutoff=nfreq)
    firstpass = scipy.signal.lfilter(b, a, interval)
    secondpass = scipy.signal.lfilter(b, a, firstpass[::-1])[::-1]
    return secondpass

Um ótimo recurso para filtrar o design e o uso, de onde peguei esse código e de onde exemplos de filtros passa-banda e passe-hi podem ser obtidos, é ISTO .


Não acredito que exista muito benefício em filtrar para frente e para trás um filtro FIR. Um filtro IIR pode se beneficiar de avançar / reverter (filtfilt) porque você pode obter fase linear de um filtro de fase não linear por meio de filtragem reversa.
— Christopher Felton

2
@ChristopherFelton Acabo de reverter para sincronizar um sinal eletromiográfico RAW com a versão suavizada de si mesmo. Eu sei que poderia mudar o sinal, mas filtrar duas vezes acaba sendo menos problemático. Vale notar que a segunda passagem quase não altera a primeira passagem já filtrada ... Obrigado por observar!
— heltonbiker

Ah sim. Para remover o atraso (atraso do grupo), bom ponto.
— Christopher Felton

1

Não tenho direitos de comentário ...

@ endolith: Eu uso o mesmo que você, exceto usando o scipy.signal.filtfilt (B, A, x) onde x é o vetor de entrada a ser filtrado - por exemplo, numpy.random.normal (size = (N)) . filtfilt faz uma passagem para frente e para trás do sinal. Por uma questão de integridade (a maioria é igual a @endolith):

import numpy as np
import scipy.signal as sps

input = np.random.normal(size=(N)) # Random signal as example
bz, az = sps.butter(FiltOrder, Bandwidth/(SamplingFreq/2)) # Gives you lowpass Butterworth as default
output = sps.filtfilt(bz, az, input) # Makes forward/reverse filtering (linear phase filter)

filtfilt, como também sugerido por @heltonbiker, exige matrizes de coeficientes, acredito. Caso você precise executar a filtragem de banda em banda básica complexa, é necessária uma configuração mais envolvida, mas isso não parece ser um problema aqui.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.