第2章:时域分析基础——均值滤波、中值滤波的原理与Python实现

各位同学,欢迎来到实战课的第二讲。今天咱们聊聊时域分析里最基础、也最实用的两个工具——均值滤波和中值滤波。

说实话,我刚开始做信号处理那会儿,总觉得滤波这事儿得靠什么高级算法。后来在项目里摔过几次跟头才明白:基础方法用好了,能解决80%的工程问题。你想想看,现场采集的高频数据,噪声往往就那么几种——要么是随机白噪声,要么是脉冲尖刺。对付它们,均值和中值滤波就是最趁手的兵器。

2.1 均值滤波:平滑噪声的“平均主义”

均值滤波的原理,说白了就是用窗口内所有点的平均值,来代替中心点的值。我习惯把它想象成“拉帮结派”——让每个数据点都看看周围邻居的平均水平,别太出格。

核心公式

y[n] = (1/M) * Σ x[n + k]   (k = -L 到 L)

其中 M = 2L + 1 是窗口长度,L 是窗口半径。

举个简单的例子。假设我们有一段传感器数据,混入了高频噪声。用均值滤波走一遍,噪声会被“抹平”。但代价是什么?信号的边缘也会变模糊。我在做振动信号分析时就吃过这个亏——为了降噪把窗口设得太大,结果故障特征全被平滑掉了。

Python实现

import numpy as np
import matplotlib.pyplot as plt

def mean_filter(signal, window_size):
    """均值滤波实现"""
    half = window_size // 2
    result = np.zeros_like(signal)
    
    for i in range(half, len(signal) - half):
        result[i] = np.mean(signal[i-half : i+half+1])
    
    # 边界处理:直接复制原值
    result[:half] = signal[:half]
    result[-half:] = signal[-half:]
    
    return result

# 生成测试信号
t = np.linspace(0, 1, 500)
clean = np.sin(2 * np.pi * 5 * t)
noise = 0.3 * np.random.randn(500)
noisy = clean + noise

# 应用均值滤波
filtered = mean_filter(noisy, window_size=5)

# 可视化
plt.figure(figsize=(10, 4))
plt.plot(t, noisy, alpha=0.5, label='含噪信号')
plt.plot(t, filtered, 'r-', linewidth=2, label='均值滤波后')
plt.legend()
plt.show()

我的经验:窗口大小选3或5通常够用。超过7,信号失真会明显增加。我曾经在ECG信号处理中试过窗口=15,结果QRS波群都快认不出来了。

2.2 中值滤波:对抗脉冲噪声的利器

中值滤波的思路完全不同——它取窗口内所有点的中位数,而不是平均值。为什么要这么做?

你想想看,如果信号里突然蹦出一个尖峰(比如传感器受干扰),均值滤波会被这个尖峰“带偏”,而中值滤波直接忽略它。因为中位数对异常值不敏感。

核心公式

y[n] = median( x[n-L], ..., x[n], ..., x[n+L] )

我记得有一次做工业现场的数据采集,电机启动瞬间会产生强烈的电磁干扰,信号上全是毛刺。均值滤波怎么调都不行,换成中值滤波后,干净得跟教科书一样。

Python实现

def median_filter(signal, window_size):
    """中值滤波实现"""
    half = window_size // 2
    result = np.zeros_like(signal)
    
    for i in range(half, len(signal) - half):
        result[i] = np.median(signal[i-half : i+half+1])
    
    result[:half] = signal[:half]
    result[-half:] = signal[-half:]
    
    return result

# 加入脉冲噪声
impulse_noise = np.zeros(500)
impulse_noise[100:105] = 5.0
impulse_noise[300:303] = -4.0
noisy_impulse = clean + impulse_noise

# 对比两种滤波
mean_result = mean_filter(noisy_impulse, 5)
median_result = median_filter(noisy_impulse, 5)

注意:中值滤波对高斯白噪声的效果不如均值滤波。它擅长的是“点状”噪声,而不是“面状”噪声。选哪个,得看你的噪声长什么样。

2.3 滑动窗口的选择策略

窗口怎么选?这是新手最容易纠结的问题。我给大家总结了一套实用策略

场景 推荐窗口大小 说明
高频噪声(白噪声) 3~7 均值滤波优先,窗口越大越平滑
脉冲噪声(尖刺) 3~5 中值滤波优先,窗口太大丢失细节
缓慢变化的趋势信号 11~21 可以大窗口,但要小心相位延迟
边缘检测场景 3 窗口越小越好,保留跳变沿

我个人习惯是从最小窗口开始试。先设窗口=3,看效果;不行再逐步增大。千万别一上来就搞个大窗口,否则信号特征被抹平了都不知道。

避坑指南:我曾经在金融时间序列上用过均值滤波,窗口设了20。结果发现滤波后的序列跟原序列有肉眼可见的相位偏移——因为均值滤波本质上是低通滤波器,会引入延迟。后来我改用零相位滤波(比如scipy的filtfilt)才解决。

2.4 知识体系总览

下面这张图,是我梳理的本章核心逻辑。你可以把它当作一个“决策树”——遇到噪声时,按图索骥就行。

时域降噪决策流程 含噪信号输入 噪声类型判断 脉冲噪声/尖刺 → 中值滤波 高斯白噪声 → 均值滤波 窗口=3~5(小窗口) 窗口=3~7(逐步增大) 降噪后信号输出

嗯,这张图的核心逻辑就是:先看噪声长什么样,再选滤波方法,最后定窗口大小。别搞反了顺序。

2.5 实战建议

最后,我给大家三个实战中总结出来的建议:

  1. 先可视化,再滤波。拿到数据别急着写代码,先画个图看看噪声形态。我见过太多人上来就套滤波,结果把有用信号滤没了。
  2. 组合使用效果更好。如果信号里既有高斯噪声又有脉冲噪声,可以先中值滤波去尖刺,再均值滤波平滑。我在雷达信号处理中常用这个组合。
  3. 注意边界效应。滤波时窗口会超出信号边界,处理不好会在两端产生假信号。我习惯用“镜像延拓”或“零填充”来处理边界,别直接扔掉。

一句话总结:均值滤波抹平噪声,中值滤波剔除尖刺。窗口大小决定平滑程度,也决定信号失真程度。选对了,事半功倍;选错了,事倍功半。


专注资料整理