4、滑动平均滤波:原理介绍、窗口大小选择、边界处理、Python实现与效果对比

滑动平均滤波,说白了就是「取一段数据的平均值」。

我刚开始做手环算法时,第一个接触的滤波方法就是它。为什么?因为简单、直观、效果好。你想想看,加速度计的数据噪声大多是高频抖动,而人的运动信号是低频的。取平均,正好能把高频抖掉。

4.1 原理介绍

滑动平均的核心思想:

  • 维护一个固定长度的窗口(比如 N 个点)
  • 每次来一个新数据,窗口就往前滑动一格
  • 取窗口内所有数据的平均值,作为当前输出

数学公式很简单:

y[k] = (x[k] + x[k-1] + ... + x[k-N+1]) / N

其中 N 是窗口大小,x 是原始数据,y 是滤波后的数据。

我在项目中遇到过一个问题:有人觉得滑动平均就是「简单平均」,其实不是。简单平均是对整段数据取平均,而滑动平均是实时更新的。每来一个点,窗口就动一下,这才是「滑动」的含义。

4.2 窗口大小选择

窗口大小 N 怎么选?这是滑动平均最关键的参数。

窗口大小 滤波效果 延迟 适用场景
N=3 较弱 1-2个点 快速响应,噪声小
N=5 中等 2-3个点 日常步数检测
N=10 较强 4-5个点 静止姿态识别
N=20 很强 9-10个点 低频趋势提取

我个人习惯:手环加速度计采样率一般是 50Hz 或 100Hz。对于步数检测,N=5 到 N=8 比较合适。窗口太大,人走一步都反应不过来,那就尴尬了。

经验之谈: 窗口大小 = 采样率 × 0.1 秒。比如 50Hz 采样,窗口取 5 个点。这样既能滤掉高频噪声,又不会让延迟太明显。

4.3 边界处理

滑动平均有个小麻烦:数据刚开始的时候,窗口还没填满。

比如 N=5,前 4 个点怎么算?

常见的处理方法有三种:

  1. 补零法: 前面补 0,凑够 N 个点。简单,但开头几个点会偏小。
  2. 截断法: 前 N-1 个点不输出,从第 N 个点开始算。数据完整,但会丢失开头。
  3. 变窗口法: 窗口不满时,用已有的点数取平均。开头平滑过渡,我最推荐。

我曾经踩过一个坑:用补零法处理手环数据,结果前几步的加速度幅值被拉低了,步数检测直接漏检。后来改成变窗口法,问题就解决了。

注意: 边界处理看似小事,但在实时系统中影响很大。尤其是手环这种需要「开机即用」的设备,开头几秒的数据质量很关键。

4.4 Python实现

直接上代码。我习惯用 NumPy 实现,效率高、代码短。

import numpy as np
import matplotlib.pyplot as plt

def sliding_average(data, window_size):
    """
    滑动平均滤波
    data: 一维数组,原始信号
    window_size: 窗口大小
    """
    result = np.zeros_like(data)
    
    for i in range(len(data)):
        # 边界处理:变窗口法
        if i < window_size:
            # 窗口不满时,用已有数据
            window = data[:i+1]
        else:
            window = data[i-window_size+1:i+1]
        
        result[i] = np.mean(window)
    
    return result

# 生成测试数据:正弦波 + 噪声
t = np.linspace(0, 2, 200)
clean = np.sin(2 * np.pi * 2 * t)
noise = 0.3 * np.random.randn(len(t))
signal = clean + noise

# 应用滑动平均
filtered = sliding_average(signal, window_size=5)

# 画图对比
plt.figure(figsize=(10, 4))
plt.plot(t, signal, alpha=0.5, label='原始信号(含噪声)')
plt.plot(t, filtered, 'r-', linewidth=2, label='滑动平均后')
plt.plot(t, clean, 'g--', label='理想信号')
plt.legend()
plt.xlabel('时间 (秒)')
plt.ylabel('加速度 (g)')
plt.title('滑动平均滤波效果对比 (N=5)')
plt.grid(True)
plt.show()

这段代码我用了很多次。你直接复制就能跑,数据也是随机生成的,每次结果会略有不同,但趋势一样。

4.5 效果对比

咱们看看不同窗口大小的实际效果:

  • N=3: 噪声还有残留,曲线不够平滑。适合对实时性要求高的场景。
  • N=5: 噪声基本滤除,波形保留完整。手环步数检测的黄金参数。
  • N=10: 波形开始变钝,峰值被削平。适合看趋势,不适合做事件检测。
  • N=20: 波形严重滞后,细节全丢。除非你要看慢变信号,否则别用。
核心结论: 滑动平均是「用延迟换平滑」。窗口越大,越平滑,但延迟也越大。手环应用中,N=5 到 N=8 是最常用的区间。

嗯,这里要注意:滑动平均不是万能的。它对付高斯白噪声效果很好,但如果噪声是脉冲型的(比如突然跳变),滑动平均反而会把跳变「抹平」,让你误以为没发生什么事。

我记得有一次调试手环的抬腕检测,用了滑动平均后,抬腕动作的峰值被削掉了,导致检测率下降。后来我换成了中值滤波,才把问题解决。这个咱们后面章节会细讲。

总结一下:

  • 滑动平均 = 移动窗口取平均
  • 窗口大小决定平滑程度和延迟
  • 边界处理推荐变窗口法
  • 代码实现简单,适合快速验证

下一章咱们聊聊中值滤波,那个对付脉冲噪声更拿手。