在Python中应用时变过滤器

时间:2013-08-08 14:01:21

标签: python filtering signal-processing

我正在尝试使用Python将具有时变截止频率的带通滤波器应用于信号。我当前使用的例程将我的信号划分为等长时间段,然后对于每个段,我在将信号合并回来之前应用具有时间特定参数的滤波器。参数基于预先存在的估计值。

我似乎遇到的问题是,在应用过滤器后出现的每个时间段的边缘都有“涟漪”。这会导致我的信号不连续,这会干扰我的过滤后数据分析。

我希望有人可以告诉我在Python中是否存在用于实现具有时变参数的过滤器的现有例程?或者,我将非常感谢您就如何解决这个问题提出建议。

编辑 - 下面添加了我想要做的示例。

假设我有一个信号x(t)。我想用带参数(100,200)Hz的带通滤波器对前半部分进行滤波。下半场我想用参数(140,240)Hz进行过滤。我迭代x(t),将我的过滤器应用于每一半,然后重新组合结果。一些示例代码可能如下所示:

outputArray = np.empty(len(x))
segmentSize = len(x) / 2
filtParams  = [(100, 200), (140, 240)]

for i in range(2):
    tempData     = x[i*segmentSize:(i+1)*segmentSize]
    tempFiltered = bandPassFilter(tempData, parameters=filtParams[i])
    outputArray[i*segmentSize:(i+1)*segmentSize] = tempFiltered

(为了节省空间,我们假设我有一个执行带通滤波的功能。)

如您所见,数据段不重叠,只是简单地“粘贴”在新数组中。

编辑2 - 我的问题的一些示例代码@ H.D。

首先,感谢您迄今为止的重要投入。 audiolazy包看起来很棒。

如果我更详细地描述我的目标,我认为会更有用。正如我发布的elsewhere,我试图使用希尔伯特变换提取信号的instantaneous frequency(IF)。我的数据包含很大的噪音但我对IF信号所在的带宽有很好的估计。然而,我遇到的一个问题是 IF通常是非平稳的。因此,使用“静态”滤波器方法时,我经常需要使用宽带通区域,以确保捕获所有频率。

以下代码演示了增加滤波器带宽对IF信号的影响。它包括信号生成功能,使用scipy.signal包的带通滤波器的实现,以及提取结果滤波信号的IF的方法。

from audiolazy import *
import scipy.signal as sig
import numpy as np
from pylab import *

def sineGenerator( ts, f, rate, noiseLevel=None ):
    """generate a sine tone with time, frequency, sample rate and noise specified"""

    fs = np.ones(len(ts)) * f    
    y  = np.sin(2*np.pi*fs*ts)

    if noiseLevel: y = y + np.random.randn(len(y))/float(noiseLevel)
    return y

def bandPassFilter( y, passFreqs, rate, order ):
    """STATIC bandpass filter using scipy.signal Butterworth filter"""

    nyquist = rate / 2.0
    Wn      = np.array([passFreqs[0]/nyquist, passFreqs[1]/nyquist])    
    z, p, k = sig.butter(order, Wn, btype='bandpass', output='zpk')
    b, a    = sig.zpk2tf(z, p, k)

    return sig.lfilter(b, a, y)

if __name__ == '__main__':
     rate = 1e4
     ts   = np.arange(0, 10, 1/rate)

     # CHANGING THE FILTER AFFECTS THE LEVEL OF NOISE
     ys    = sineGenerator(ts, 600.0, 1e4, noiseLevel=1.0) # a 600Hz signal with noise
     filts = [[500, 700], [550, 650], [580, 620]]

    for f in filts:
        tempFilt = bandPassFilter( ys, f, rate, order=2 )
        tempFreq = instantaneousFrequency( tempFilt, rate )

        plot( ts[1:], tempFreq, alpha=.7, label=str(f).strip('[]') )

    ylim( 500, 750 )
    xlabel( 'time' )
    ylabel( 'instantaneous frequency (Hz)' )

    legend(frameon=False)
    title('changing filter passband and instantaneous frequency')
    savefig('changingPassBand.png')

changing passband

信号中有一个频率分量(600Hz)。图例显示了每种情况下使用的过滤器参数。使用较窄的“静态”过滤器可提供“更清晰”的输出。但我的滤波器有多窄,受到频率的限制。例如,考虑以下具有两个频率分量的信号(一个在600Hz,另一个在650Hz)。

varying frequency

在这个例子中,我被迫使用更宽的带通滤波器,这会导致额外的噪声蔓延到IF数据。

我的想法是,通过使用时变滤波器,我可以以特定的时间增量“优化”我的信号的滤波器。因此,对于上述信号,我可能想要在580-620Hz左右过滤前5秒,接下来5秒为630-670Hz。基本上我想最小化最终IF信号中的噪声。

根据您发送的示例代码,我编写了一个函数,该函数使用audiolazy在信号上实现静态Butterworth滤波器。

def audioLazyFilter( y, rate, Wp, Ws ):
    """implement a Butterworth filter using audiolazy"""

    s, Hz = sHz(rate)
    Wp    = Wp * Hz # Bandpass range in rad/sample
    Ws    = Ws * Hz # Bandstop range in rad/sample

    order, new_wp_divpi = sig.buttord(Wp/np.pi, Ws/np.pi, gpass=dB10(.6), gstop=dB10(.1))
    ssfilt = sig.butter(order, new_wp_divpi, btype='bandpass')
    filt_butter = ZFilter(ssfilt[0].tolist(), ssfilt[1].tolist())

    return list(filt_butter(y))

使用此滤波器结合希尔伯特变换程序获得的IF数据与使用scipy.signal获得的IF数据相比较:

AL_filtered = audioLazyFilter( ys, rate, np.array([580, 620]), np.array([570, 630]) )
SP_filtered = bandPassFilter( ys, [580, 620], rate, order=2 )
plot(ts[1:], instantaneousFrequency( SP_filtered, 1e4 ), alpha=.75, label='scipy.signal Butterworth filter')
plot(ts[1:], instantaneousFrequency( AL_filtered, 1e4 ), 'r', alpha=.75, label='audiolazy Butterworth filter')

compare audiolazy with scipy.signal

我现在的问题是,我可以将audiolazy Butterworth例程与您在原始帖子中描述的时变属性结合起来吗?

3 个答案:

答案 0 :(得分:7)

AudioLazy本身适用于时变滤镜

from audiolazy import sHz, white_noise, line, resonator, AudioIO

rate = 44100
s, Hz = sHz(rate)

sig = white_noise() # Endless white noise Stream

dur = 8 * s # Some few seconds of audio
freq = line(dur, 200, 800) # A lazy iterable range
bw = line(dur, 100, 240)

filt = resonator(freq * Hz, bw * Hz) # A simple bandpass filter

with AudioIO(True) as player:
  player.play(filt(sig), rate=rate)

您还可以使用list(filt(sig))filt(sig).take(inf)将其用于绘图(或一般分析)。还有许多其他资源也可能有用,例如直接在Z变换滤波器方程中应用时变系数。

编辑:有关AudioLazy组件的更多信息

以下示例使用IPython完成。

Resonator是一个StrategyDict实例,它将许多实现绑定在一个地方。

In [1]: from audiolazy import *

In [2]: resonator
Out[2]: 
{('freq_poles_exp',): <function audiolazy.lazy_filters.freq_poles_exp>,
 ('freq_z_exp',): <function audiolazy.lazy_filters.freq_z_exp>,
 ('poles_exp',): <function audiolazy.lazy_filters.poles_exp>,
 ('z_exp',): <function audiolazy.lazy_filters.z_exp>}

In [3]: resonator.default
Out[3]: <function audiolazy.lazy_filters.poles_exp>

因此resonator在内部调用resonator.poles_exp函数,您可以从中获得帮助

In [4]: resonator.poles_exp?
Type:       function
String Form:<function poles_exp at 0x2a55b18>
File:       /usr/lib/python2.7/site-packages/audiolazy/lazy_filters.py
Definition: resonator.poles_exp(freq, bandwidth)
Docstring:
Resonator filter with 2-poles (conjugated pair) and no zeros (constant
numerator), with exponential approximation for bandwidth calculation.

Parameters
----------
freq :
  Resonant frequency in rad/sample (max gain).
bandwidth :
  Bandwidth frequency range in rad/sample following the equation:

    ``R = exp(-bandwidth / 2)``

  where R is the pole amplitude (radius).

Returns
-------
A ZFilter object.
Gain is normalized to have peak with 0 dB (1.0 amplitude).

所以详细的过滤器分配将是

filt = resonator.poles_exp(freq=freq * Hz, bandwidth=bw * Hz)

Hz只是一个数字,用于将单位从Hz更改为rad / sample,如大多数AudioLazy组件中所使用的那样。

freq = pi/4bw = pi/8pi已经在audiolazy命名空间中)这样做:

In [5]: filt = resonator(freq=pi/4, bandwidth=pi/8)

In [6]: filt
Out[6]: 
              0.233921
------------------------------------
1 - 1.14005 * z^-1 + 0.675232 * z^-2


In [7]: type(filt)
Out[7]: audiolazy.lazy_filters.ZFilter

您可以尝试使用此过滤器,而不是第一个示例中给出的过滤器。

另一种方法是使用包中的z对象。 首先让我们找到全极点谐振器的常数:

In [8]: freq, bw = pi/4, pi/8

In [9]: R = e ** (-bw / 2)

In [10]: c = cos(freq) * 2 * R / (1 + R ** 2) # AudioLazy included the cosine

In [11]: gain = (1 - R ** 2) * sqrt(1 - c ** 2)

分母可以使用等式中的z直接完成:

In [12]: denominator = 1 - 2 * R * c * z ** -1 + R ** 2 * z ** -2

In [13]: gain / denominator
Out[14]: 
              0.233921
------------------------------------
1 - 1.14005 * z^-1 + 0.675232 * z^-2

In [15]: type(_) # The "_" is the last returned value in IPython
Out[15]: audiolazy.lazy_filters.ZFilter

编辑2:关于时变系数

滤波器系数也可以是Stream实例(可以从任何迭代中转换)。

In [16]: coeff = Stream([1, -1, 1, -1, 1, -1, 1, -1, 1, -1]) # Cast from a list

In [17]: (1 - coeff * z ** -2)(impulse()).take(inf)
Out[17]: [1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

同样,给定列表输入而不是impulse() Stream:

In [18]: coeff = Stream((1, -1, 1, -1, 1, -1, 1, -1, 1, -1)) # Cast from a tuple 

In [19]: (1 - coeff * z ** -2)([1, 0, 0, 0, 0, 0, 0]).take(inf)
Out[19]: [1.0, 0.0, -1, 0, 0, 0, 0]

NumPy 1D数组也是可迭代的:

In [20]: from numpy import array

In [21]: array_data = array([1, -1, 1, -1, 1, -1, 1, -1, 1, -1])

In [22]: coeff = Stream(array_data) # Cast from an array

In [23]: (1 - coeff * z ** -2)([0, 1, 0, 0, 0, 0, 0]).take(inf)
Out[23]: [0.0, 1.0, 0, 1, 0, 0, 0]

最后一个例子显示了时变行为。

编辑3:分块重复序列行为

行函数具有类似于numpy.linspace的行为,它获得范围&#34; length&#34;而不是&#34; step&#34;。

In [24]: import numpy

In [25]: numpy.linspace(10, 20, 5) # Start, stop (included), length
Out[25]: array([ 10. ,  12.5,  15. ,  17.5,  20. ])

In [26]: numpy.linspace(10, 20, 5, endpoint=False) # Makes stop not included
Out[26]: array([ 10.,  12.,  14.,  16.,  18.])

In [27]: line(5, 10, 20).take(inf) # Length, start, stop (range-like)
Out[27]: [10.0, 12.0, 14.0, 16.0, 18.0]

In [28]: line(5, 10, 20, finish=True).take(inf) # Include the "stop"
Out[28]: [10.0, 12.5, 15.0, 17.5, 20.0]

由此,滤波器方程具有不同的样本每样本样本(1样本&#34;块&#34;)。无论如何,你可以使用转发器来处理更大的块大小:

In [29]: five_items = _ # List from the last Out[] value

In [30]: @tostream
   ....: def repeater(sig, n):
   ....:     for el in sig:
   ....:         for _ in xrange(n):
   ....:             yield el
   ....:             

In [31]: repeater(five_items, 2).take(inf)
Out[31]: [10.0, 10.0, 12.5, 12.5, 15.0, 15.0, 17.5, 17.5, 20.0, 20.0]

并在第一个示例的行中使用它,以便freqbw成为:

chunk_size = 100
freq = repeater(line(dur / chunk_size, 200, 800), chunk_size)
bw = repeater(line(dur / chunk_size, 100, 240), chunk_size)

编辑4:使用时变增益/包络模拟来自LTI滤波器的时变滤波器/系数

另一种方法是使用不同的&#34;权重&#34;对于信号的两个不同的滤波版本,并制作一些&#34;交叉渐变&#34;数学与信号,如:

signal = thub(sig, 2) # T-Hub is a T (tee) auto-copy
filt1(signal) * line(dur, 0, 1) + filt2(signal) * line(dur, 1, 0)

这将从相同信号的不同滤波版本应用线性包络(从0到1和从1到0)。如果thub看起来令人困惑,请尝试sig1, sig2 = tee(sig, 2)应用filt(sig1)filt(sig2),这些也应该这样做。

编辑5:时变巴特沃斯滤波器

我花了最后几个小时试图让Butterworth个性化作为你的榜样,强加order = 2并直接给出半功率带宽(~3dB)。我已经完成了四个示例,代码为in this Gist,并且我已将AudioLazy更新为包含gauss_noise高斯分布式噪声流。请注意,gist中的代码没有进行任何优化,在这种特殊情况下可以使用它,并且由于每个样本&#34;所以啁啾示例使它非常慢。系数发现行为。即时频率可以从rad / sample中的[filtered]数据得到:

diff(unwrap(phase(hilbert(filtered_data))))

其中diff = 1 - z ** -1或另一种在离散时间内找到导数的方法,hilbert是来自scipy.signal的函数,它给出了分析信号(离散希尔伯特变换是其虚部)结果)和另外两个是AudioLazy的辅助函数。

当Butterworth突然改变其系数同时保持其记忆而没有噪音时会发生这种情况:

variable_butterworth_abrupt_pure_sinusoid.png

在这种转变过程中,显着的振荡行为是显而易见的。您可以使用移动中位数来平滑&#34;在较低频率的一侧,同时保持突然过渡,但不会在较高频率下工作。嗯,这就是我们对完美正弦曲线的期望,但是有了噪声(很多噪声,高斯的标准偏差等于正弦振幅),它变成了:

variable_butterworth_abrupt_noisy.png

然后我尝试用唧唧声做同样的事情,恰恰是这个:

variable_butterworth_pure_sinusoid.png

这显示了在较低带宽下以最高频率进行过滤时的奇怪行为。并附加噪音:

variable_butterworth_noisy.png

gist中的代码也是AudioIO().play最后一次嘈杂的唧唧声。

编辑6:时变谐振器滤波器

我已经使用谐振器代替Butterworth添加了the same Gist示例。他们使用的是纯Python并且没有经过优化,但在啁啾期间为每个样本调用butter执行速度更快,并且更容易实现,因为所有resonator策略都接受将实例作为有效输入。以下是两个谐振器级联的图(即,二阶滤波器):

reson_2_abrupt_pure_sinusoid.png reson_2_abrupt_noisy.png reson_2_pure_sinusoid.png reson_2_noisy.png

对于三个谐振器的级联(即,三阶滤波器)也是如此:

reson_3_abrupt_pure_sinusoid.png reson_3_abrupt_noisy.png reson_3_pure_sinusoid.png reson_3_noisy.png

这些谐振器在中心频率处的增益等于1(0 dB),并且来自&#34;突然纯正弦波的振荡模式&#34;即使没有任何过滤,转换中的图也会发生。

答案 1 :(得分:1)

尝试使用您正在使用的每个滤波器过滤整个信号,然后适当地合并滤波后的信号。粗略地说,在伪代码中:

# each filter acts on the entire signal with each cutoff frequency
s1 = filter1(signal)
s2 = filter2(signal)
s3 = filter3(signal)

filtered_signal = s1[0:t1] + s2[t1:t2] + s3[t2:t3]

我认为这样可以避免因为切断信号然后过滤而导致的所描述的伪像。

另一种可能性是使用Short Time Fourier Transform (STFT)。这是an implementation using numpy

基本上,您可以对信号进行STFT,通过操作时频阵列来过滤信号,然后对阵列进行反向STFT以获得滤波信号。

使用可逆wavelet transform可能会获得更好的结果。这是一个paywalled paper,描述了如何使用小波变换完成的工作。

答案 2 :(得分:1)

如果您使用切片提取信号的某些部分,那么您可以使用矩形窗口有效地对数据进行窗口化,这种窗口由于突然的不连续而在边缘处“振铃”。解决这个问题的一种方法是使用一个响铃较少的窗口,如一个汉宁窗口:

import numpy as np

signal = np.random.randn(222)
length = 50
window = np.hanning(length)
for i in range(0, len(signal)-length, length):
    do_filtering(signal[i:i+length] * window)

有关Windows的更多信息:http://en.m.wikipedia.org/wiki/Window_function