尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python数字信号滤波实战:低通、陷波与FFT置零详解

发布时间:2026/9/5 17:53:27

资讯中心
01
ARTICLE

Python数字信号滤波实战:低通、陷波与FFT置零详解

Python数字信号滤波实战:低通、陷波与FFT置零详解
滤波是信号处理里出现频率最高的词之一但很多人在真正处理一段含干扰的数据时反而会卡在第一步不知道该用哪种滤波器也不知道参数怎么配。题目的“我的刀呢”看起来像一句玩笑其实很贴近实际场景——滤波器工具就在那里但你首先要判断面前的干扰是固定频率、宽带噪声还是缓慢漂移然后才能决定拿低通、高通、带通还是陷波去处理。如果这一步判断错了后面执行得再熟练得到的也是一段“看起来平滑、但有用信息被改坏”的曲线。这篇文章围绕一个最小实验展开一段 10Hz 有效正弦信号叠加了 50Hz 工频干扰和随机噪声。后面的内容会先解释滤波的本质再准备 Python 环境用 SciPy 设计 Butterworth 低通滤波器对比因果滤波和非因果滤波的差异再用 FFT 置零和陷波器处理固定频率干扰最后给出验证指标、高频坑位和生产环境落地建议。适合刚接触数字信号处理的 Python 开发者也适合要写传感器数据清洗逻辑、却还要回头补基础知识的工程师。1. 滤波不是把曲线画平而是把频率切开1.1 时域里看不清的干扰在频域里有固定座位很多人对滤波的第一印象是“把波形变平滑”。这个理解本身没有错但它容易掩盖滤波的真正含义。一个信号在时间轴上是一条高低起伏的曲线而在频率轴上可以被拆成很多不同频率的正弦分量每个分量有自己的幅度。所谓“滤掉噪声”本质上是按照频率对信号做取舍而不是简单地把毛刺抹平。这一点在做传感器数据清洗时尤其关键。比如采集环境里存在市电设备信号里常常混入 50Hz 工频干扰设备振动或线缆抖动又会产生随机噪声。同样是噪声它们的频率特征完全不同50Hz 干扰集中在一个很窄的频率点附近随机噪声往往分布在一个很宽的频率范围。处理方式因此不同前者更适合窄带抑制后者更适合低通滤波。如果只盯着时域曲线很难把这两种情况区分开。所以合理的技术路线是先对信号做一次快速傅里叶变换观察幅度谱里哪些频率分量异常突出再决定滤波策略。频谱像是给噪声编号定位滤波则是按座位号把不需要的听众请出场。1.2 低通、高通、带通、陷波是四把不同的刀滤波器按“放行哪些频率、拦下哪些频率”可以分成四类。选择的第一原则不是“哪个效果看起来好”而是“我的有效信号和干扰分别落在哪个频段”。滤波器类型放行特征典型用途低通低于截止频率放行高于截止频率衰减去掉高频随机噪声、毛刺保留基线变化高通高于截止频率放行低于截止频率衰减去掉直流漂移、缓慢变化的基线带通只放行某一频段从振动信号中提取某个机械特征频率带阻/陷波只拦掉一个窄频段其它基本放行去除 50Hz 或 60Hz 工频干扰本文实验里的有效信号是 10Hz主要干扰是 50Hz 工频和宽带随机噪声。随机噪声的能量集中在高频段所以低通滤波器能拦下大部分50Hz 干扰虽然距离 10Hz 有一定距离但用低通也能衰减只是衰减幅度取决于滤波器阶数和截止频率。另一条路线是先用陷波器专门干掉 50Hz再考虑是否还需要低通。1.3 理想滤波器和真实滤波器的差距最终体现在参数上理想低通滤波器在频域上是一条阶跃曲线截止频率以下完全放行以上完全切除。真实滤波器做不到这种“一刀切”它一定有一个过渡带也就是从放行到衰减之间的一段斜坡。过渡带越窄滤波器阶数通常越高但高阶带来的相位畸变和数值不稳定也随之上升。于是设计滤波器就变成一组参数取舍截止频率、阶数、纹波、阻带衰减。同一类滤波器Butterworth 通带最平坦但过渡带较缓Chebyshev 过渡带更陡但有通带纹波Bessel 更重视波形保真但阻带衰减慢FIR 滤波器可以做到严格线性相位但相同性能下阶数更高、计算量更大。注意滤波器的本质是给不同频率分配不同权重。所以看到滤波结果不合理时先回到频谱图问一句“权重是不是给错了”比反复调参数更有效。2. 准备实验环境用 Python 造一段“已知答案”的噪声信号2.1 安装依赖并确认基本函数要用 Python 做数字滤波主要依赖是 NumPy、SciPy 和 Matplotlib。SciPy 的scipy.signal模块提供了滤波器设计、因果滤波和边界处理等函数。建议创建独立虚拟环境避免污染系统 Python。python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip pip install numpy scipy matplotlibWindows 环境下激活命令改成.venv\Scripts\activate。如果原始项目没有固定 Python 版本建议使用 Python 3.10 或更高版本。SciPy 的接口在较新版本里比较稳定但个别函数如filtfilt的边界填充策略仍与版本有关所以落地前最好固定依赖版本。2.2 生成 10Hz 有效信号、50Hz 工频干扰和随机噪声实验需要一个“已知答案”的信号这样才能在滤波之后用滤波结果和真实有效信号做误差对比。先构造干净的有效信号再叠加干扰模拟真实采集数据。import numpy as np import matplotlib.pyplot as plt from scipy import signal fs 1000.0 # 采样率 1000Hz duration 2.0 # 2 秒 t np.arange(0, duration, 1 / fs) # 有效信号10Hz 正弦 clean 1.0 * np.sin(2 * np.pi * 10.0 * t) # 干扰50Hz 工频干扰 随机噪声 hum 0.8 * np.sin(2 * np.pi * 50.0 * t 0.4) rng np.random.default_rng(42) random_noise 0.15 * rng.standard_normal(t.shape) # 被污染的信号 noisy clean hum random_noise这里采样率fs是整个实验最重要的量。所有频率相关的参数最终都要用它换算采样率设错后续截止频率全部失去含义。有效信号取 10Hz是因为它和 50Hz 之间有明显间隔便于观察低通滤波器的效果随机噪声让场景更接近真实采集信号。2.3 先画图记录滤波前的频谱基线滤波前必须先建立基线。画两幅图一幅看时域波形另一幅看幅度谱。幅度谱用numpy.fft.rfft和rfftfreq计算。freqs np.fft.rfftfreq(len(noisy), d1 / fs) amp np.abs(np.fft.rfft(noisy)) fig, axes plt.subplots(2, 1, figsize(10, 6)) axes[0].plot(t, noisy, lw0.7) axes[0].set_xlabel(time/s) axes[0].set_ylabel(amplitude) axes[0].set_title(raw signal) axes[1].plot(freqs, amp) axes[1].set_xlim(0, 120) axes[1].set_xlabel(frequency/Hz) axes[1].set_ylabel(amplitude) axes[1].set_title(amplitude spectrum) plt.tight_layout() plt.show()正常结果应该能看到两个明显尖峰一个在 10Hz 附近对应有效信号另一个在 50Hz 附近对应工频干扰。随机噪声则表现为整个频带上并不光滑的底部。如果看不到 50Hz 尖峰先检查采样率和信号长度是否让该频率正好落在频域分辨点上。3. 用 Butterworth 低通滤波器完成第一次去噪3.1 滤波器设计函数要处理的三个参数实验里有效信号是 10Hz干扰主要是 50Hz 和随机噪声。先选低通滤波器目标是保留 10Hz同时让 50Hz 明显衰减。最简单的是 Butterworth 滤波器它通带平坦参数语义也容易理解。from scipy.signal import butter, filtfilt, lfilter def butter_lowpass(cutoff, fs, order4): nyquist 0.5 * fs # 奈奎斯特频率 normal_cutoff cutoff / nyquist b, a butter(order, normal_cutoff, btypelow) return b, a cutoff 30.0 b, a butter_lowpass(cutoff, fs, order4)这里要理解三个参数。一是截止频率cutoff它决定“放行到哪一档”。本实验取 30Hz既给 10Hz 有效信号留出余量又能让 50Hz 落在衰减区。若截止频率太靠近 10Hz有效信号幅度会下降若太靠近 50Hz工频干扰衰减不够。二是采样率fs它决定奈奎斯特频率为 500Hz也就是这个数字系统能表示的最高频率。三是阶数order阶数越高过渡带越陡但相位变化和数值误差也越大。设计返回的b, a是 IIR 滤波器的分子分母系数。把这些系数代入lfilter或filtfilt就能对信号真正执行滤波。3.2 lfilter 和 filtfilt 代表两种不同的滤波思路同一组b, a系数可以有两种调用方式得到的波形差异很大。filtered_causal lfilter(b, a, noisy) # 因果滤波实时可部署 filtered_offline filtfilt(b, a, noisy) # 零相位滤波离线常用lfilter按时间顺序逐点处理当前样本处理第 n 个点时只用得到第 n 个点以及它之前的历史所以它满足因果性可以放进实时系统。代价是滤波会带来相位延迟波形相对原始信号会向右偏移这在后续对比步进信号时非常明显。filtfilt的思路是先把信号正向过滤一遍再把时间轴反转用同一组系数再过滤一遍。两次滤波后相位延迟被抵消输出信号和原始信号在相位上对齐适合离线分析。代价是它使用了“未来数据”无法直接用于流式在线场景。一句话总结要分析历史数据优先用filtfilt要在采集过程中边采边滤只能选lfilter或等价的状态保持方案。3.3 ba 与 sos输出格式影响数值稳定性butter默认返回b, a直接型系数。阶数不高时没有问题但阶数上到 8 阶甚至更高b, a形式可能出现数值误差放大滤波结果变成大幅波动甚至 NaN。更稳妥的做法是让butter输出二阶节形式并调用对应的滤波函数。sos signal.butter(4, 30.0, btypelow, fsfs, outputsos) filtered_offline_sos signal.sosfiltfilt(sos, noisy)这里outputsos返回的是一个若干行、每行 6 个系数的二维数组每一行代表一个二阶节。sosfiltfilt会按二阶节逐级处理数值稳定性远好于直接用高阶b, a。刚入门时可以先用b, a理解原理写生产代码时建议优先考虑sos。4. 对付固定频率干扰FFT 置零和陷波器更精准4.1 在频域里手动切除 45Hz 到 55Hz低通滤波适合对付宽带随机噪声但它的“杀伤范围”比较大。如果干扰是 50Hz 工频且有效信号离它不远更精准的做法是只切除 50Hz 附近的一小段频率。FFT 提供了一种直观的切除方式把信号变换到频域把目标频段的幅度置零再反变换回时域。spec np.fft.rfft(noisy) freqs np.fft.rfftfreq(len(noisy), d1 / fs) band_mask (freqs 45.0) (freqs 55.0) spec_cleaned spec.copy() spec_cleaned[band_mask] 0.0 recovered_fft np.fft.irfft(spec_cleaned, nlen(noisy))这段代码先把整个信号转换到频域再找出 45Hz 到 55Hz 对应的所有频点把它们设为 0最后反变换恢复时域信号。因为原始实验信号长度和采样率刚好让 50Hz 落在频域分辨率点上所以切除效果比较干净。4.2 为什么 FFT 法只适合离线分析和固定频率FFT 置零最大的吸引力是直观但它有几个明显限制。第一它默认信号是周期的如果被分析的这段信号不是正好由整数个周期组成频谱会泄漏50Hz 的能量会扩散到相邻频点只置零几个 bin 可能砍不干净。解决办法是加窗或延长数据但这会改变有效信号的幅度并不是无损操作。第二FFT 处理整段数据时依赖未来样本天然不是实时算法。第三简单地硬置零可能在时域引入振荡尤其当被切除频段包含较强有用频率时波形边缘会出现吉布斯效应。因此FFT 置零适合在实验阶段确认“干扰到底落在哪个频率”或者做一批离线数据的批量清洗。它不应该成为实时采集链路里的默认方案。4.3 用 iirnotch 做陷波滤波保留其它频率分量若确认要长期去除固定频率干扰推荐用陷波滤波器。SciPy 的iirnotch根据中心频率和品质因数 Q 生成一组 IIR 系数只衰减目标频点附近很窄的范围。b_notch, a_notch signal.iirnotch(w050.0, Q30.0, fsfs) filtered_notch signal.filtfilt(b_notch, a_notch, noisy)参数w0是要去除的中心频率这里取 50Hz。Q控制陷波宽度Q 越大被滤掉的频段越窄对相邻频率的影响越小但 Q 太大时滤波器对中心频率漂移很敏感。如果现场工频不是严格的 50Hz而是在 50Hz 上下浮动就需要适当降低 Q或者改用自适应算法跟踪频率。现实中直接把 10Hz 低通和 50Hz 陷波联合使用往往比单独依赖某一种滤波器更稳。5. 验证滤波效果不要只回答“看起来平滑了吗”5.1 波形对比要看幅值、延迟和边缘滤波做完了常见误区是“只要输出曲线平滑就认为成功了”。实际上平滑的曲线也可能是失真有效信号被衰减、相位被延迟、上升沿被拉平。验证时先把原始含噪信号、滤波结果和已知有效信号叠在一张图里重点看三个位置峰值幅度是否接近原值波形前沿是否相对原始信号左右偏移信号边缘有没有出现不该存在的振铃。plt.figure(figsize(12, 5)) plt.plot(t, noisy, alpha0.35, lw0.6, labelraw) plt.plot(t, filtered_offline, lw1.0, labelfiltfilt) plt.plot(t, filtered_causal, lw1.0, labellfilter) plt.xlim(0, 0.4) plt.legend() plt.xlabel(time/s) plt.ylabel(amplitude) plt.show()把时域窗口限制在 0 到 0.4 秒会更容易看出lfilter输出发生延迟而filtfilt的输出和 10Hz 原始波形基本同步。看到延迟不代表滤波失败而是因果系统本身的特征在实时环境里这种延迟只要固定且可预测就能用补偿逻辑处理。5.2 频谱对比要看残余干扰是否真正下降验证滤波效果最可靠的工具还是频谱。把滤波前和滤波后的信号分别做 FFT观察目标频点的幅度是否明显下降。amp_after np.abs(np.fft.rfft(filtered_offline)) freqs_after np.fft.rfftfreq(len(filtered_offline), d1 / fs) plt.figure(figsize(10, 4)) plt.plot(freqs, amp, labelraw spectrum) plt.plot(freqs_after, amp_after, labelfiltered spectrum) plt.xlim(0, 120) plt.xlabel(frequency/Hz) plt.ylabel(amplitude) plt.legend() plt.show()正常结果中50Hz 附近应该出现一个明显凹陷10Hz 附近的峰值基本保留。如果 50Hz 衰减不大先确认实际采样率是否与代码里的fs一致再确认截止频率、阶数或陷波中心频率是否落在正确位置。5.3 指标对比用 RMSE 量化误差在仿真实验里有效信号clean是已知的所以可以计算滤波后的输出相对真实信号的误差。均方根误差表达了滤波结果与理想结果的总体偏离程度数值越小越好。def rmse(x, y): return float(np.sqrt(np.mean(np.square(x - y)))) rmse_raw rmse(noisy, clean) rmse_lowpass rmse(filtered_offline, clean) rmse_notch rmse(filtered_notch, clean) print(fraw RMSE : {rmse_raw:.4f}) print(flowpass RMSE : {rmse_lowpass:.4f}) print(fnotch RMSE : {rmse_notch:.4f})这个指标的局限性要清楚它只适用于仿真或已知标准信号的测试数据。真实数据里没有干净参照物无法直接计算误差这时更常见的方法是观察滤波结果是否符合业务规律或把滤波前后的频谱同时保存下来做回归测试。验证信号处理结果时不能只看程序有没有运行成功。输入波形、输出幅度、频谱峰值、边界区域和误差指标都要核对尤其要保留滤波前数据否则出了问题没有对照。6. 四个高频坑位与排查链路6.1 滤波后信号整体偏移不是数据坏了现象用lfilter滤波后的曲线和原始信号相比整体向右移动了一段距离看起来像相位没对上。原因IIR 滤波器是因果系统会对信号引入依赖频率的相位延迟。阶数越高或截止频率设置越接近有效信号延迟越明显。检查方式把原始信号、lfilter结果和filtfilt结果画在同一张图里。若filtfilt结果对齐而lfilter结果偏移则基本确定是相位延迟。解决方案离线分析改用filtfilt或sosfiltfilt实时系统无法使用未来数据就要测量滤波器在该通带内的固定群延迟在后续处理中做延迟补偿或改用线性相位的 FIR 滤波器。6.2 首尾出现大幅摆动问题通常出在边界处理现象整段信号中间滤波效果正常但开头几百个点和结尾几百个点出现很大的摆动或畸变。原因filtfilt在边界上会扩展信号扩展方式不合理或滤波器阶数过高时边界效应会被放大FFT 置零如果遇到频域泄漏也可能在时域端点产生振荡。检查方式分别只画滤波结果的前 200 个点和后 200 个点观察异常是否集中在边界对比lfilter和filtfilt如果只有零相位滤波出现说明是边界处理问题。解决方案对离线数据给filtfilt设置合理的padlen或适当降低滤波器阶数对实时处理通常要丢弃前若干个输出样本并在系统启动初期使用状态初始化逻辑。边界区域在业务上如果不可信直接截掉比强行修正更安全。6.3 配了截止频率却像没滤先检查单位与采样率现象低通截止频率设成 30Hz可滤波后 50Hz 干扰还是很明显。原因最常见的是采样率fs与数据真实采样率不一致其次是截止频率没有做奈奎斯特归一化直接把 30 当成归一化频率传给butter还有可能是analog被误设为True导致函数按模拟滤波器设计。检查方式先打印数据的真实采样率再检查butter调用时传入的是cutoff / (fs / 2)还是错误的cutoff / fs。之后对滤波输出做一次频谱分析看 50Hz 处幅度是否下降。解决方案把采样率作为全局参数统一管理写一个固定测试用例输入已知频率的标准信号判断输出频谱是否与预期一致。6.4 高阶滤波结果变成 NaN优先怀疑数值稳定性现象滤波器阶数提高到 10 阶以上滤波输出变成NaN或出现极端尖峰。原因高阶 IIR 滤波器直接使用b, a形式时极点非常靠近单位圆浮点舍入误差被放大系统变得不稳定。检查方式用np.roots(a)查看极点位置。如果极点模长接近或大于 1说明稳定性已经出问题同时尝试把output设为sos看看结果是否恢复正常。解决方案不要直接构造高阶b, a改用signal.butter(order, ..., outputsos)再用sosfiltfilt或sosfilt执行滤波。必要时降低单次滤波阶数改为级联结构。6.5 从现象倒推原因的标准排查顺序无论问题长什么样建议按下面的顺序检查避免在无关参数上浪费大量时间先确认采样率fs是否和原始数据一致这决定后续所有频率参数的意义。再确认滤波器类型。低通、高通、带通、陷波的语义不要和业务目标弄反。接着检查截止频率和归一化方式确认没有把 Hz 直接当成归一化频率。再检查输出形式高阶滤波优先使用sos。最后结合时域波形和频域幅度谱判断是相位问题、边界问题还是衰减量不够。问题现象优先排查方向常用处理建议波形整体偏移因果滤波器相位延迟离线用 filtfilt实时做延迟补偿首尾大幅摆动边界填充与滤波阶数调整 padlen降低阶数丢弃边界样本滤波后原样输出采样率或归一化错误用真实 fs 换算奈奎斯特频率输出 NaN高阶 ba 数值不稳定改用 SOS 结构降低阶数50Hz 仍然明显过渡带不够陡或 Q 过小提高阶数或用陷波器单点切除7. 从离线演示到生产环境还有哪些事必须做7.1 在线滤波和离线滤波是两套实现很多人在 Python 里调通filtfilt后以为直接搬到实时系统就能用。真实环境里filtfilt依赖整段信号天然无法在线运行。采集系统只能在每个新样本到达时用上一时刻的滤波器状态和当前样本更新输出这正是lfilter有状态版本要解决的问题。学习实验里可以不断生成整段数据再滤波生产系统则要在初始化时分配滤波器状态在每次新样本到来时推进状态。后端服务处理批量历史数据时filtfilt通常没问题嵌入式或边缘设备做逐点采集时一定要换用因果滤波并在程序启动阶段丢弃或标记不可信样本。7.2 生产环境需要关注的参数和监控滤波器上线之后不是一劳永逸。工厂环境里的干扰频率可能随设备转速变化市电频率也可能在小范围内漂移。固定参数滤波器适合频率稳定的场景一旦工况变化滤波效果会下降。此时要考虑几个措施定期记录滤波前后的频谱特征用自动检测脚本判断目标频点是否出现异常回升把滤波参数外置到配置中心避免修改参数后重新编译整个程序对关键信号做双通道对比一路原始一路滤波方便出现争议时回溯。如果滤波器运行在资源受限设备上还要检查系数精度、运算量、内存占用和实时性。Python 方案适合原型验证和离线统计真正落到嵌入式环境通常需要把滤波器系数另存成 C 语言或定点算法能使用的格式并用模拟数据和实测数据双重验证。7.3 滤波器上线前检查清单下面的清单可以直接复制到项目的验收文档里避免只验证“代码能跑”。[ ] 确认数据采集实际采样率与代码中的fs一致最好在配置文件里统一声明。[ ] 用仿真信号做单元测试输入已知频率的有效信号和干扰检查输出幅度谱是否符合预期。[ ] 明确滤波场景是离线分析还是实时处理并据此选择filtfilt或lfilter。[ ] 对高阶 IIR 滤波器使用 SOS 结构检查极点是否全部位于单位圆内。[ ] 检查滤波结果首尾边界区域确认没有异常摆动必要时截掉不可信样本。[ ] 对比滤波前后频谱确认目标干扰频率确实下降有效频段没有被明显压制。[ ] 将滤波参数外置化并在日志中记录采样率、阶数、截止频率与版本号。[ ] 为滤波输出增加限幅、异常检测和原始数据备份机制。滤波这件事最容易让人忽略的地方在于它既可以成为数据质量提升器也可能变成失真来源。只要有一个参数理解偏差比如把采样率记错、把截止频率方向写反、把实时因果滤波与离线零相位滤波混用都会得到一段符合“平滑直觉”但不符合工程预期的新信号。本文这套“先看频谱、再选类型、再验证相位和边界”的思路适用于多数信号清洗任务。做完实验后可以继续尝试改变有效信号频率、把干扰改成变化频率、加入脉冲噪声并比较低通、带通和自适应滤波方案的差异——这些练习比单纯背函数参数更能建立对滤波器的直觉。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。