4.2 信号处理与滤波 本节摘要:滤波是按频率改造信号的操作。scipy.signal 提供从滤波器设计到应用的完整链路:butter、cheby1、ellip 三种经典 IIR 滤波器各有取舍,lfilter 做因果滤波、filtfilt 做零相位滤波,sos 形式保证高阶滤波器的数值稳定。本节从一段最小去噪代码出发,讲清选型、应用与验证三个环节,再用移动平均与 IIR 滤波的正面对比,说明频域思维的价值。 先说结论 阅读完本节,你应当能够: 用 butter 设计低通滤波器,并正确归一化截止频率; 对比 butter、cheby1、ellip 的通带纹波与阻带衰减,能按需求选型; 解释 lfilter 的相位延迟与 filtfilt 零相位的机制,说清各自的适用场景;
本节摘要:滤波是按频率改造信号的操作。scipy.signal 提供从滤波器设计到应用的完整链路:butter、cheby1、ellip 三种经典 IIR 滤波器各有取舍,lfilter 做因果滤波、filtfilt 做零相位滤波,sos 形式保证高阶滤波器的数值稳定。本节从一段最小去噪代码出发,讲清选型、应用与验证三个环节,再用移动平均与 IIR 滤波的正面对比,说明频域思维的价值。
阅读完本节,你应当能够:
场景是心电或振动测量。信号里既有我们关心的 5 赫兹成分,又有 50 赫兹工频干扰,还叠加着随机噪声。目标很明确:把 50 赫兹和随机噪声去掉,尽量完整地保留 5 赫兹波形。先上最小例子:
import numpy as np from scipy import signal fs = 1000 t = np.arange(0, 2, 1/fs) x = np.sin(2*np.pi*5*t) + 0.4*np.sin(2*np.pi*50*t) + 0.1*np.random.default_rng(0).normal(size=len(t)) b, a = signal.butter(4, 30/(fs/2), btype='low') y = signal.lfilter(b, a, x)
核心只有三行。signal.butter 设计一个 4 阶巴特沃斯低通滤波器,第二个参数 30/(fs/2) 是归一化截止频率——30 赫兹是通带边缘,除以奈奎斯特频率得到 0 到 1 之间的值;signal.lfilter 把信号过一遍滤波器。跑完你会发现 50 赫兹那条波浪线明显变平,高频成分被按住了。这就是滤波器的全部工作:让一部分频率通过,把另一部分按比例衰减。
时域里滤波看起来像"磨平毛刺",但真正理解它必须站在频域:滤波器是一个频率选择性开关,对每个频率乘一个增益系数。设计滤波器的本质,就是设计这条增益曲线——通带增益接近 1,阻带增益接近 0,过渡带越窄越好,但越窄付出的代价越大。有了这个视角,前面 4.1 的频谱分析就变成了滤波的前置步骤:先看清噪声在哪,再决定把哪段频率按下去。
设计完成的滤波器,别急着应用,先用 freqz 看一眼频率响应:横轴是频率、纵轴是增益,通带应该接近 1,截止点(增益掉到 0.707,即负 3 分贝处)应该落在你设定的位置。这一步十分钟,能拦住一半以上的设计错误——参数传错、归一化算错,在频率响应曲线上都藏不住。
butter:通带内最平坦,没有纹波,相位响应相对温和,是默认首选。cheby1:允许通带纹波,换来的是一阶更陡的过渡带——同样的阶数下衰减更快,代价是通带内振幅起伏,波形会带一点"涟漪"。ellip:通带和阻带都有纹波,换来的是同等性能下最低的阶数,计算最省,但相位畸变最明显,脉冲响应过冲最大。工程口诀:默认 butter;阶数受限、需要陡峭衰减时用 cheby1;对阶数极其敏感的场景才轮到 ellip。
上面三种都是 IIR 滤波器——输出会反馈回输入,用很少的系数就能做出陡峭的过渡带。另一大流派是 FIR,signal.firwin 直接按抽头系数设计,天生线性相位、没有稳定性问题,代价是同样的性能需要几十上百个系数,计算量大。工程判断:离线处理、对相位要求高选 FIR;阶数受限、追求计算量小选 IIR。本章主角是 IIR,但知道 FIR 的存在,选型时才不会漏掉选项。
| 类型 | 通带纹波 | 过渡带宽度 | 同性能所需阶数 | 相位特性 | 典型用途 |
|---|---|---|---|---|---|
| butter | 无 | 最宽 | 最高 | 温和 | 通用去噪 |
| cheby1 | 有 | 中等 | 中等 | 较差 | 阶数受限时 |
| ellip | 有 | 最窄 | 最低 | 最差 | 阶数敏感场景 |
lfilter 是因果滤波:每个输出点只依赖当前及过去的输入,所以能实时计算,信号边来边处理。代价是相位延迟——滤波器本质上对信号做了"时移",波形整体往后拖,拖多少取决于滤波器的群延迟。filtfilt 把信号正着滤一遍、反着再滤一遍,两次的相位延迟方向相反、相互抵消,得到零相位结果,波形在时间轴上不偏移,峰值位置精确。这正适合离线分析:比如要测量波形峰值的精确时刻,或者保留波形的形态做特征提取。代价是不能实时——filtfilt 的输出依赖整段数据,末端的处理会"借用"未来的点;另外首尾会出现边缘效应,两端一小段数据会失真。群延迟不是玄学:4 阶 butter 低通在通带内的延迟大约是几个采样点,如果你把 lfilter 与 filtfilt 的输出画在一起,能直观看到前者整体后移、后者与原信号对齐。这个对比实验值得做一次,比背结论牢靠。
高阶 IIR 用 b、a 直接型实现,浮点舍入误差会被极点放大,高阶系统的极点对系数扰动极其敏感,严重时滤波结果直接发散成噪声。sos(二阶节级联)把高阶系统拆成多个二阶系统的串联,每一级都只处理温和的低阶子问题,数值稳定性大幅提升。写法很轻:signal.butter(8, 0.1, output='sos'),应用时用 sosfilt 或 sosfiltfilt。结论明确:阶数达到 4 以上,一律用 sos 形式,别拿 b、a 去赌精度。当然,1 到 2 阶的滤波器用 b、a 没有任何问题,原型验证阶段怎么方便怎么来;进入正式分析,再统一换成 sos。规则不是教条,是风险控制。
卷积是滤波的另一种实现,FIR 滤波的本质就是卷积。signal.convolve 按定义直接算,复杂度是 N 乘 M;fftconvolve 把卷积搬到频域,用 FFT 完成,复杂度是 N 乘 logN。核越长,fftconvolve 的优势越大;核只有三五个点时,直接卷积反而更快,因为 FFT 有固定开销。选择标准一句话:核长度超过几十个点,就换 fftconvolve。顺带一提,fftconvolve 在图像处理里也常用,大卷积核的二维卷积能省出数量级的时间。
移动平均是最朴素的去噪:取窗口内数据的平均值。它在频域对应一条形状固定的响应曲线——主瓣之外还有一圈圈旁瓣,衰减很慢,一部分高频噪声会从旁瓣"漏"过去;更麻烦的是它没有任何参数能调整频率选择性,窗口长度一动,通带和阻带一起变,做不到"只砍 50 赫兹、保留 49 赫兹"。对比之下,butter 低通可以精确控制截止频率和衰减速度。移动平均不是不能用——数据预览、快速平滑时它依然顺手——但它做不了按频率精确切割,严谨去噪时别把它当主力。举个数:窗口长度 50 个点、采样率 1000 赫兹的移动平均,频率响应的第一个零点在 1000 除以 50 等于 20 赫兹处——也就是说它对 20 赫兹附近的成分衰减最大,而对 40 赫兹以上的高频反而只衰减一部分。这个形状完全由窗口长度决定,你没法单独指定"砍掉 50 赫兹"。IIR 低通则把控制权交还给设计者,这正是本节要建立的核心差别。
滤波从来不是一行代码的事,标准流程四步:先看频谱,确定噪声的频率位置;再选型,按需求在 butter、cheby1、ellip 里挑;然后设计,注意把截止频率除以奈奎斯特频率做归一化,阶数高就用 sos;最后验证,用滤波前后的频谱对比确认效果,而不是盯着时域波形说"看起来干净了"。
| 方法 | 相位 | 实时性 | 边缘效应 | 适用场景 |
|---|---|---|---|---|
| lfilter | 有延迟 | 可以 | 较小 | 实时处理、流式数据 |
| filtfilt | 零相位 | 不可以 | 明显 | 离线分析、保波形 |
| 移动平均 | 有延迟 | 可以 | 小 | 快速预览、粗平滑 |
截止频率根据频谱分析的结果定,别拍脑袋——4.1 的 rfft 就是为这一步服务的。阶数从 2 到 4 起步,够用就行:阶数越高过渡带越陡,但相位畸变和过冲越明显,滤波后的波形越"变形"。需要同时衰减多个频带时,优先考虑多个滤波器串联,或者直接设计带阻;单频干扰用 iirnotch,下一节实战里我们会用到它。
高通、带通、带阻的设计参数只是 btype 不同:'high'、'band'、'stop',带通和带阻需要传两个频率组成的列表。选型判断一句话:噪声比信号频率高,用低通;比信号低,用高通;噪声夹在中间或干扰是单频,用带阻;只想要某个频带,用带通。判断依据永远是 4.1 的频谱图,不是猜。
不会。lfilter 与 filtfilt 都保持输入输出等长,改变的是内容不是形状。变短的是"有效数据"——filtfilt 的边缘效应让首尾失真,实际可用区间比原始短。另一个高频疑问是"为什么滤波后噪声还在",多半是阶数或截止频率没压够,回去看频谱,而不是把信号再滤一遍:重复滤波只是把增益曲线再平方一次,该漏的噪声照样漏,反而多一倍的相位畸变和边缘失真。
⚠️ 常见坑:截止频率忘了除以奈奎斯特频率。butter 的第三个参数是归一化频率,范围 0 到 1,直接把 30 赫兹传进去,滤波器会按采样率 2 赫兹来设计,出来的曲线完全不是你要的,而且不会报任何错误。
💡 关键直觉:滤波前先花十秒钟看一眼频谱。噪声在哪个频段、信号在哪个频段,决定了你用低通、高通还是带阻,也决定了截止频率放哪。跳过这一步的滤波,本质是盲人摸象。
下面这张示意图把 4.1 与本节串起来:滤波前,有用成分、工频干扰和随机噪声挤在频谱里;滤波后,干扰被按下去,有用成分原样保留。做实验时把这张图作为验收标准——频域里的变化,才是滤波真正干了什么。

一维信号会滤波了,下一步把同样的思维搬到二维:下一节我们处理图像——先学去噪,再学形态学与对象计数。