Toccata in Nowhere.

Python scipy.signal 带通滤波器与 MATLAB 的区别

2021.07.22

虽然在函数名和使用方法上极为相似,但如果仔细观察,二者的滤波结果可能存在一定的相位差异。

例子

我们以一个简单的序列为例,使用MATLAB中巴特沃斯滤波器带通滤波之后,绘制结果。以下是MATLAB版本:

clc,clear
dat = 1:1000;
dt = 0.01;
f0 = 1;
f1 = 20;
fs=1/dt/2;
[b,a]=butter(2,[f0,f1]/fs); 
dat1=filtfilt(b,a,dat);
subplot(311)
plot((0:999)*fs, dat);
subplot(312)
plot((0:999)*fs, dat1);
subplot(313)
plot((0:999)*fs/1000, abs(fft(dat1)));

以及 Python 版本,使用 scipy.signal 实现:

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

dat = np.arange(1,1001,1)
f0 = 1
f1 = 20
dt = 0.01
fs = 1/dt/2

b, a = signal.butter(2, [f0/fs,f1/fs], 'bandpass')
dat1 = signal.filtfilt(b, a, dat,padtype = 'odd')

plt.figure(figsize=[10,10])
plt.subplot(311)
plt.plot(np.arange(1000)*fs, dat)
plt.subplot(312)
plt.plot(np.arange(1000)*fs, dat1, label="Python")
plt.subplot(313)
plt.plot(np.arange(1000)*fs/1000, np.abs(scipy.fft(dat1)))

如果将MATLAB的滤波结果 dat1 拷贝至Python脚本中,并赋值给 aM,可以计算二者结果的互相关:

plt.figure(figsize=[10,10])
cc = signal.correlate(dat1, aM)
plt.plot(cc, label="Cross-correlate")

二者的互相关显示,二者存在三个 $dt$ ,即 $0.03$ 的延迟/相位差。

原因与解决

在进行带通滤波时,需要在边缘进行填充(padding)为信号边界进行滤波。在MATLAB中默认padding的长度为3*(max(len(a), len(b)) - 1),而scipy.signal.filtfilt中默认为 3*max(len(a), len(b))

可以通过在Python通过添加padlen参数,指定padding长度。从而得到和MATLAB中一样的计算结果:

b, a = signal.butter(2, [f0/fs,f1/fs], 'bandpass')
dat1 = signal.filtfilt(b, a, dat,padlen=3*(max(len(b),len(a))-1))

MATLAB 中则无法进行指定(截止R2021a版本)。

Reference

致谢

本文感谢 ZhhSong 老哥的锲而不舍。 感谢Paco Wong 在参考链接中的回答