HOWTO · Python

在 Python 中实现低通滤波器

学习如何使用 SciPy Butterworth 滤波器、移动平均或高斯平滑在 Python 中过滤采样数据,并理解截止频率与采样限制。

本页内容

低通滤波器会让较低频率的分量通过,并衰减较高频率的分量;它不一定会完全去除截止频率以上的所有分量。处理采样数据时,截止频率与采样率应使用相同的频率单位。本指南介绍三种使用 NumPy 和 SciPy 对一维信号进行滤波或平滑的方法。

以下示例均为可独立执行的完整示例,但本文未在此处执行,也未展示其结果。运行前,请在 Python 环境中安装 NumPy、SciPy 和 Matplotlib。

使用 SciPy Butterworth 滤波器设置明确的截止频率

Butterworth 滤波器的通带具有最大平坦度。截止频率应低于奈奎斯特频率,即采样率的一半。示例中,fs 为每秒 500 个采样点,截止频率为 100 Hz。将 fs 传给 scipy.signal.butter,即可使用相同的单位指定截止频率。滤波器阶数决定过渡带的陡峭程度:阶数越高,滚降越陡,但对瞬态和数值行为也越敏感。

此示例使用二阶节和 sosfiltfilt,对信号进行正向和反向滤波。这种离线分析方法可以避免相位延迟,但需要双向处理数据,而且可能影响有限长度信号的两端。对于流式或实时数据,应使用 sosfilt 等因果滤波器,并考虑其相位延迟和启动瞬态。

import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import butter, sosfiltfilt

fs = 500.0
cutoff = 100.0
order = 4
time = np.arange(500) / fs
signal = np.sin(2 * np.pi * 10 * time) + 0.5 * np.sin(2 * np.pi * 180 * time)

sections = butter(order, cutoff, btype="lowpass", fs=fs, output="sos")
filtered = sosfiltfilt(sections, signal)

plt.plot(time, signal, label="input")
plt.plot(time, filtered, label="filtered")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()

根据要处理的信号和希望保留的频率,调整 cutoff 和 order。sosfiltfilt 适用于离线数据,其结果不能与因果实时滤波器的结果互换。

应用移动平均低通滤波器

移动平均会将每个点替换为固定窗口内的平均值。这是一种简单的平滑方法,并非具有指定截止频率响应的 Butterworth 设计。使用 NumPy 卷积的 valid 模式时,结果只包含完整窗口,因此长度会比输入少 window_size - 1 个采样点。绘图时应相应调整时间值。

import matplotlib.pyplot as plt
import numpy as np

fs = 100.0
window_size = 5
time = np.arange(100) / fs
signal = np.sin(2 * np.pi * 3 * time) + 0.2 * np.sin(2 * np.pi * 25 * time)
weights = np.ones(window_size) / window_size
smoothed = np.convolve(signal, weights, mode="valid")
smoothed_time = time[window_size - 1:]

plt.plot(time, signal, label="input")
plt.plot(smoothed_time, smoothed, label="moving average")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()

窗口大小必须为正数,且不能超过信号长度。增大窗口会纳入更多采样点,使平滑效果更明显,但也会让变化显得更宽,并改变窗口结果的对齐位置。若要使用居中对齐,解读时间值时应考虑窗口带来的延迟。

使用高斯滤波器平滑采样数据

一维高斯滤波器使用呈高斯形状的核对相邻采样点加权。在 scipy.ndimage.gaussian_filter1d 中,sigma 的单位是采样点,而不是秒或赫兹。必要时,可根据采样率将所需的时间尺度换算为采样点数。高斯平滑适合一般的平滑处理,但不会产生具有明确截止频率的陡峭响应。

import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import gaussian_filter1d

fs = 100.0
sigma_seconds = 0.04
time = np.arange(100) / fs
signal = np.sin(2 * np.pi * 3 * time) + 0.2 * np.sin(2 * np.pi * 25 * time)
smoothed = gaussian_filter1d(signal, sigma=sigma_seconds * fs)

plt.plot(time, signal, label="input")
plt.plot(time, smoothed, label="Gaussian smoothed")
plt.xlabel("Time (seconds)")
plt.ylabel("Amplitude")
plt.legend()
plt.tight_layout()
plt.show()

示例将 0.04 秒的平滑尺度换算为 4 个采样点。默认行为会通过反射信号来处理两端;如果数据适用不同的端点模型,可以选择其他 mode,并谨慎解读信号两端的结果。

选择滤波器并理解其限制

当你需要明确的频率响应且知道采样率时,可以使用 Butterworth 设计。需要简单的窗口平滑时,可以使用移动平均;适合采用平滑局部加权时,可以使用高斯平滑。这些方法的频率响应、延迟和端点处理方式各不相同,因此应将结果与原始数据对比,并确认任务所需的重要特征仍然清晰可见。

对于采样数据,奈奎斯特频率是采样率的一半。低通截止频率必须低于奈奎斯特频率,而且采样率必须足以表示关注的频率。滤波器无法恢复采样时未捕获的信息。离线正反向滤波可以减少相位偏移;因果滤波适合流式处理,但会引入延迟和启动效应。对于有限长度的记录,填充方式和初始条件也会让靠近两端的滤波结果更敏感。

常见问题

低通滤波器有什么作用?

它会按照自身的频率响应让较低频率的分量通过,并衰减较高频率的分量。截止频率表示该响应中的过渡位置,并不意味着所有更高频率都会完全消失。

什么时候应该使用低通滤波器?

当较高频率的变化对当前任务而言属于噪声,且采样率和截止频率已知时,可以使用低通滤波器。还应检查平滑处理是否去除了信号中有意义的快速变化。

Butterworth 滤波器与移动平均有什么区别?

Butterworth 滤波器根据截止频率、采样率和阶数进行设计。移动平均则使用固定数量的采样点作为窗口,其频率响应不同;窗口长度不能直接替代 Butterworth 截止频率。

这些滤波器可以用于实时数据吗?

因果滤波器可以逐个处理到达的采样点,但需要考虑相位延迟和启动瞬态。Butterworth 示例中的正反向方法需要完整的数据记录,适用于离线处理。移动平均也可以随着新采样点到达而更新,但需要妥善处理窗口和对齐方式。

可以使用哪些 Python 库?

NumPy 提供数组和卷积功能;SciPy 提供 Butterworth 滤波器设计和高斯平滑函数。Matplotlib 可以绘制输入信号和滤波后的信号,以便检查结果。