关于设计没有相位敏感边缘伪影的数字滤波器的建议?

信息处理 过滤器 数字滤波器 带通
2022-01-28 08:34:23

我非常精通统计学,但不是真正的数字信号过滤。我有一个数据场景,我希望能够很容易地滤除一些已知频带的噪声(人体脉冲),但是我在使用 scipy.signal 库中的标准工具时遇到了很多麻烦,并认为我一定是误解了如何设计数字滤波器。到目前为止,我在这里有一个笔记本可以完成我的探索,但要点是标准的 scipy 滤波器似乎会在我的信号开始和结束时造成大的失真,其精确行为取决于噪声信号的相位我'我试图减去。以防万一上面的活页夹链接失效,我还将包括以下一些关键点:

首先生成一些与我的真实数据相似的合成数据:

#generate time vector
samples_per_sec = 10.0
total_time = 40.0
time = np.linspace(0, total_time, int(total_time*samples_per_sec))

#generate the pulse signal
pulse_hz = 1.0
pulse_phase = np.radians(0)
pulse = np.sin(time*(2*np.pi)*pulse_hz - pulse_phase)

#generate the BOLD signal (just something that goes up then down)
dist = stats.beta(2, 2)
bold = dist.pdf((time-10)/20) / 10.0 # division by 10 to make bold a small signal

#combine
pulse_plus_bold = pulse+bold
plt.plot(time, pulse_plus_bold);

组合信号

尝试一阶黄油:

#1st order butterworth filter in ba mode
ba1 = signal.butter(
    output = 'ba'
    , N = 1 #needs to be low if using output='ba', else use output='sos' and sosfiltfilt
    , Wn = [0.5,1.5]
    , btype = 'bandstop'
    , fs = samples_per_sec
)
filtered_ba1_nopad = signal.filtfilt(
    b = ba1[0]
    , a = ba1[1]
    , x = pulse_plus_bold
    , padtype = None
)
plt.plot(time, filtered_ba1_nopad, 'b');
plt.plot(time, bold, 'r--');
plt.legend(['Filtered', 'Expected'], loc=(1.04,.5));

在此处输入图像描述

具有均匀填充的一阶 Butterworth:

filtered_ba1_pad_even = signal.filtfilt(
    b = ba1[0]
    , a = ba1[1]
    , x = pulse_plus_bold
    , method = 'pad'
    , padtype = 'even'
)
plt.plot(time, filtered_ba1_pad_even, 'b');
plt.plot(time, bold, 'r--');
plt.legend(['Filtered', 'Expected'], loc=(1.04,.5));

在此处输入图像描述

具有奇数填充的一阶 Butterworth:

filtered_ba1_pad_odd = signal.filtfilt(
    b = ba1[0]
    , a = ba1[1]
    , x = pulse_plus_bold
    , method = 'pad'
    , padtype = 'odd'
)
plt.plot(time, filtered_ba1_pad_odd, 'b');
plt.plot(time, bold, 'r--');
plt.legend(['Filtered', 'Expected'], loc=(1.04,.5));

在此处输入图像描述

后者看起来真的很好!但是在玩了之后,我发现奇数或偶数(或任何一个)填充效果更好似乎取决于被过滤掉的信号的相位。例如,虽然上面使用奇数填充获得了出色的滤波,但这是相同的场景,但在脉冲信号中添加了相移,从而产生奇数和偶数的边缘伪影:

phase = np.radians(45)
pulse_shifted = np.sin(time*(2*np.pi)*pulse_hz - phase)
pulse_shifted_plus_bold = pulse_shifted+bold

filtered_shifted_ba1_pad_odd = signal.filtfilt(
    b = ba1[0]
    , a = ba1[1]
    , x = pulse_shifted_plus_bold
    , method = 'pad'
    , padtype = 'odd'
)
filtered_shifted_ba1_pad_even = signal.filtfilt(
    b = ba1[0]
    , a = ba1[1]
    , x = pulse_shifted_plus_bold
    , method = 'pad'
    , padtype = 'even'
)

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(5, 3))
axes[0].plot(time, filtered_shifted_ba1_pad_odd, 'b')
axes[0].plot(time, bold, 'r--')
axes[1].plot(time, filtered_shifted_ba1_pad_even, 'b')
axes[1].plot(time, bold, 'r--')
fig.tight_layout()
plt.title('Odd (left) and Even (right)')
plt.legend(['Filtered', 'Expected'], loc=(1.04,.5));

在此处输入图像描述

我还尝试了“gust”填充方法以及高阶滤波器(当然使用 sos),并在我尝试过的所有内容中观察到相同的相位相关边缘伪影。有小费吗?

1个回答

您的基本问题是 filtfilt (和大多数其他线性过滤例程)采用为无限长时间扩展而设计的过滤器,并将它们应用于一大块数据,就好像数据在两个方向上用零无限扩展一样。

所以你有一个合法的带通滤波器,它在信号的起点“看到”信号的合法跳跃

您可以采取三种基本方法;前两个是临时且容易的,如果您从第一原则开始,第三个则很困难。它肯定已经在某个地方解决了,但是在这里对“过滤有限长度数据”的简短搜索并没有让我感到高兴。

方法1:窗口输入数据

获取您的输入数据,并将其乘以使其末端逐渐变细的值。即在每一端超过 10 个样本从 0 到 1 的斜坡,或12(1cosπnN)每端有 N 个样本(在左端适当反转)。你会有一些伪影(毕竟,上升的正弦波与稳定的正弦波不同),但它们会被衰减。这是实现余弦边缘衰减的 python 代码,能够自定义信号的中心百分比保持为 1:

def attenuate_edges(signal,time,edge_attenuation_percent):
  start = int(np.floor(len(time)*edge_attenuation_percent))
  end = int(len(time)-start)
  ramp = (1-np.cos(np.pi*(np.arange(start)/start)))/2
  edge_attenuator = np.ones(len(time))
  edge_attenuator[0:start] = ramp
  edge_attenuator[end:len(time)] = np.flip(ramp)
  return(signal*edge_attenuator)

方法 2:修剪输出数据

做你现在正在做的事情,并在最后去掉那些肮脏的东西。这可能是最简单的,如果您可以收集更多数据,就不会失去任何东西。

方法 3:对干扰信号进行适当的估计,然后将其减去

如果您喜欢数学并且有时间,这将很有趣。基本上,您将使用一个事实,即您的干扰信号的值在时间n以特定方式与您当时的干扰信号值相关联k对于所有值nk在您的数据集中。你最终可能会得到一个看起来很像 Wiener 或 Kalman 滤波器的东西,它会考虑到最终效应。您的估计在末端会更糟,但这会在末端显示为一点噪音 - 而不是那些响亮的大脉冲。

如果我无法弄清楚这方面的搜索词,我将需要一天的时间和另一天的时间来验证,并且应该是专家。OTOH、高斯或拉普拉斯可能在 19 世纪发明了它,甚至可能认为它足够重要,可以在某处写下来。所以我确定该方法存在。