在Python中应用时变过滤器

all*_*nds 4 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
Run Code Online (Sandbox Code Playgroud)

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

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

编辑2 - 我的问题@HD的一些示例代码

首先,感谢您迄今为止的重要贡献.audiolazy包看起来像一个很棒的工具.

如果我更详细地描述我的目标,我认为会更有用.正如我在其他地方发布的那样,我试图使用希尔伯特变换提取信号的瞬时频率(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')
Run Code Online (Sandbox Code Playgroud)

改变通带

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

频率变化

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

我的想法是,通过使用时变滤波器,我可以以特定的时间增量"优化"我的信号的滤波器.因此对于上述信号,我可能希望在前5秒内过滤580-620Hz左右,然后在接下来的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))
Run Code Online (Sandbox Code Playgroud)

使用此滤波器结合希尔伯特变换例程获得的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')
Run Code Online (Sandbox Code Playgroud)

将audiolazy与scipy.signal进行比较

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

H.D*_*.D. 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)
Run Code Online (Sandbox Code Playgroud)

你也可以用它密谋(或分析,在一般情况),通过使用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>
Run Code Online (Sandbox Code Playgroud)

因此,在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).
Run Code Online (Sandbox Code Playgroud)

因此,详细的过滤器分配将是

filt = resonator.poles_exp(freq=freq * Hz, bandwidth=bw * Hz)
Run Code Online (Sandbox Code Playgroud)

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

让我们用freq = pi/4bw = pi/8(pi已经在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
Run Code Online (Sandbox Code Playgroud)

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

另一种方法是使用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)
Run Code Online (Sandbox Code Playgroud)

分母可以使用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
Run Code Online (Sandbox Code Playgroud)

编辑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]
Run Code Online (Sandbox Code Playgroud)

同样,给定一个列表输入而不是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]
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

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

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

line函数的行为类似于numpy.linspace,它获取范围"length"而不是"step".

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]
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

而在从第一个例子中的行中使用它,这样freqbw变成:

chunk_size = 100
freq = repeater(line(dur / chunk_size, 200, 800), chunk_size)
bw = repeater(line(dur / chunk_size, 100, 240), chunk_size)
Run Code Online (Sandbox Code Playgroud)

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

另一种方法是对信号的两个不同滤波版本使用不同的"权重",并对信号进行一些"交叉渐变"数学运算,例如:

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

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

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

我花了最后几个小时试图让Butterworth个性化作为你的榜样,强加order = 2并直接给出半功率带宽(~3dB).我已经做了四个例子,代码在这个Gist中,我已经更新了AudioLazy以包含gauss_noise高斯分布式噪声流.请注意,gist中的代码没有经过优化,在特定情况下可以使用它,并且由于"每个样本"系数查找行为,啁啾示例使其非常慢.可以从rad/sample中的[filtered]数据获得即时频率:

diff(unwrap(phase(hilbert(filtered_data))))
Run Code Online (Sandbox Code Playgroud)

其中diff = 1 - z ** -1或其它的方法来找到在离散时间的衍生物,hilbert是从功能scipy.signal,让我们分析信号(离散希尔伯特变换是其结果的虚数部分),并且另外两个是从AudioLazy辅助函数.

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

variable_butterworth_abrupt_pure_sinusoid.png

这种转变中的振荡行为是显而易见的.您可以使用移动中位数来"平滑"低频侧的那个,同时保持突然转换,但这对于较高频率不起作用.那么,这就是我们对完美正弦曲线的期望,但是有了噪声(很多噪声,高斯的标准偏差等于正弦振幅),它变为:

variable_butterworth_abrupt_noisy.png

我试着用唧唧声做同样的事情,正是这样:

variable_butterworth_pure_sinusoid.png

当使用较低带宽进行过滤时,这会显示出一种奇怪的行为.并附加噪音:

variable_butterworth_noisy.png

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

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

在同一个Gist中添加一个使用谐振器代替Butterworth的例子.它们使用纯Python并且没有进行优化,但是butter在啁啾期间执行速度比调用每个样本要快,并且更容易实现,因为所有resonator策略都接受Stream实例作为有效输入.以下是两个谐振器级联的图(即二阶滤波器):

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),即使没有任何滤波,转换中的"突然纯正弦曲线"曲线的振荡模式也会发生.