我正在尝试使用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')
信号中有一个频率分量(600Hz)。图例显示了每种情况下使用的过滤器参数。使用较窄的“静态”过滤器可提供“更清晰”的输出。但我的滤波器有多窄,受到频率的限制。例如,考虑以下具有两个频率分量的信号(一个在600Hz,另一个在650Hz)。
在这个例子中,我被迫使用更宽的带通滤波器,这会导致额外的噪声蔓延到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')
我现在的问题是,我可以将audiolazy Butterworth例程与您在原始帖子中描述的时变属性结合起来吗?
答案 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/4
和bw = 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
您可以尝试使用此过滤器,而不是第一个示例中给出的过滤器。
另一种方法是使用包中的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]
并在第一个示例的行中使用它,以便freq
和bw
成为:
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突然改变其系数同时保持其记忆而没有噪音时会发生这种情况:
在这种转变过程中,显着的振荡行为是显而易见的。您可以使用移动中位数来平滑&#34;在较低频率的一侧,同时保持突然过渡,但不会在较高频率下工作。嗯,这就是我们对完美正弦曲线的期望,但是有了噪声(很多噪声,高斯的标准偏差等于正弦振幅),它变成了:
然后我尝试用唧唧声做同样的事情,恰恰是这个:
这显示了在较低带宽下以最高频率进行过滤时的奇怪行为。并附加噪音:
gist中的代码也是AudioIO().play
最后一次嘈杂的唧唧声。
编辑6:时变谐振器滤波器
我已经使用谐振器代替Butterworth添加了the same Gist示例。他们使用的是纯Python并且没有经过优化,但在啁啾期间为每个样本调用butter
执行速度更快,并且更容易实现,因为所有resonator
策略都接受将实例作为有效输入。以下是两个谐振器级联的图(即,二阶滤波器):
对于三个谐振器的级联(即,三阶滤波器)也是如此:
这些谐振器在中心频率处的增益等于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