滑动平均滤波:数字信号处理的经典算法

📌 转载声明
原平台:CSDN
原作者:北域码匠
原文地址:点击跳转原文
备注:本文仅个人技术学习归档,无任何商用行为,全部版权归属原作者,原作者如需删稿可私信立刻下架。

滑动平均滤波(Moving Average Filter,又称移动平均滤波)是工业控制、传感器数据处理和信号采集领域中最基础且广泛应用的数字滤波算法。它以稳定性著称,能有效消除随机噪声、平滑数据波动,同时保留数据的真实变化趋势。

滑动平均滤波详解

基本概念

定义

滑动平均滤波(Moving Average Filter)是一种经典的时域滤波算法,通过计算连续N个最新数据的算术平均值实现滤波功能。该算法采用固定长度的数据窗口,随着新数据不断加入,最旧的数据会被移除,使窗口持续向前滑动。这种机制能有效平滑数据并抑制噪声干扰。

工作原理

初始化

设定窗口大小N,创建长度为N的缓冲区

数据更新
  • 新数据加入缓冲区末尾
  • 若缓冲区已满(达到N个数据),移除最旧的数据
计算输出

计算缓冲区所有数据的算术平均值并输出

通俗理解

将滑动平均滤波视为一个固定长度的队列:

  • 新数据加入队尾
  • 队列满时移除队首数据
  • 实时计算队列内数据的平均值作为输出

这种机制能显著平滑数据中的高频波动。例如,使用N=5的滑动平均计算室温时,当前输出为最近5次测量的平均值。当第6次测量到来时,将剔除第1次测量,计算第2-6次测量的平均值作为新输出。

核心术语详解

窗口大小 N

  • 定义:参与平均计算的数据数量(关键参数)
  • 影响
    • N值大:平滑效果强但响应慢
    • N值小:响应快但平滑效果弱
  • 选择原则: 需平衡信号变化速度与噪声强度,通常通过实验确定最优值

滑动窗口

  • 实现方式:环形缓冲区或队列数据结构
  • 特点
    • 固定长度(由N决定)
    • 内容持续更新
    • 始终保留最新N个数据
  • 应用示例: 嵌入式系统中常用数组实现,通过指针/索引管理数据进出

随机噪声

  • 特性

    • 无规律的高频小幅波动
    • 通常服从正态分布
    • 来源包括传感器误差、电磁干扰等

  • 典型表现
    • 测量值在真实值附近随机波动
    • 频率通常高于有效信号

  • 示例
    • 电子秤末位数字跳动
    • 温度传感器的微小波动

有效信号

  • 特性

    • 缓慢变化的有规律物理量
    • 我们需要获取的有用信息
    • 频率通常低于噪声

  • 保护措施
    • 选择合适的N值避免过度平滑
    • 确保滤波后信号不失真

  • 示例
    • 室内温度昼夜变化
    • 电机转速的平稳调整

历史背景

滑动平均滤波(Moving Average Filter)是最古老的数字信号处理算法之一,其发展历程可分为三个阶段:

手工计算时代(19世纪)

  • 首次应用:统计学家将其用于经济数据(如股票价格波动)和气象数据(如气温变化趋势)的平滑处理
  • 计算方式:采用滑动窗口手工计算算术平均值,窗口宽度通常为5、7、10等奇数
  • 典型案例:1884年,英国气象局首次采用7日滑动平均分析伦敦气温变化

数字化普及阶段(20世纪中叶)

  • 计算机移植:1946年ENIAC计算机诞生后,该算法成为首批数字化的统计方法
  • 工业自动化应用(1950-1970):
    • 模拟量采集(如4-20mA信号)
    • 数字量去抖(如机械触点消抖)
    • 过程控制(如PID输入预处理)

现代应用阶段(21世纪)

  • 基础地位:所有DSP教材首章必讲算法,被誉为"数字滤波的Hello World"
  • 硬件适配
    • 8位单片机(RAM<1KB)
    • PLC梯形图编程标准指令
    • 物联网终端(如NB-IoT/LoRa模块)
  • 新兴领域:MEMS传感器融合、边缘计算节点数据预处理

原理详解

数学公式表达

滑动平均滤波(Simple Moving Average, SMA)的数学表达式如下:

给定窗口大小(数据点数量)为 N,当前时刻采集的新数据点为 xₙ,窗口内的数据序列为 {xₙ, xₙ₋₁, ..., xₙ₋ₙ₊₁},则滤波输出值 yₙ 可表示为:

yₙ = (xₙ + xₙ₋₁ + ... + xₙ₋ₙ₊₁) / N

使用求和符号简化为:
yₙ = (1/N) * Σ xₙ₋ₖ (k 从 0 到 N-1)

示例:当 N=5 时,当前输出为最近 5 个采样点的算术平均值:
yₙ = (xₙ + xₙ₋₁ + xₙ₋₂ + xₙ₋₃ + xₙ₋₄)/5

核心原理深入解析

固定窗口机制

  • 算法维护固定大小的数据缓冲区(窗口),通常窗口大小 N 取 4-20 之间的值
  • 每次新数据到来时的处理流程:
    • 将新数据加入窗口末端
    • 若窗口已满(包含 N 个数据),则删除最旧的数据(窗口首端数据)
    • 始终保留窗口内最近的 N 个数据点

示例(N=3 时的窗口变化过程):

  • 收到 x₁ → 窗口:[x₁]
  • 收到 x₂ → 窗口:[x₁, x₂]
  • 收到 x₃ → 窗口:[x₁, x₂, x₃]
  • 收到 x₄ → 窗口:[x₂, x₃, x₄](x₁ 被移除)

等权平均特性

  • 窗口内所有数据点具有相同的权重系数 1/N
  • 与指数加权移动平均(EWMA)的区别:
    • EWMA 赋予新数据更高权重,旧数据权重呈指数衰减
    • SMA 所有数据权重均等,更强调"整体平均"效果

权重分布示意图(N=5):
每个 xₙ₋ₖ 的权重:┌───┬───┬───┬───┬───┐
│0.2│0.2│0.2│0.2│0.2│
└───┴───┴───┴───┴───┘

噪声抑制原理

随机噪声特性:

  • 均值为 0(正负噪声出现概率相等)
  • 短期相关性弱(前后噪声值相互独立)

通过平均运算实现:

  • 正负噪声相互抵消
  • N 值越大,噪声抑制效果越好(方差减小为原来的 1/N)

示例
真实信号=2.0V,噪声幅度=±0.1V
采样序列:2.08, 1.95, 2.03, 2.12, 1.97(N=5)
滤波输出 = (2.08+1.95+2.03+2.12+1.97)/5 = 2.03V
结果更接近真实值

趋势跟随能力

通过窗口滑动机制:

  • 反映信号的缓慢变化(低频成分)
  • 过滤快速波动(高频噪声)

响应速度与窗口大小 N 的关系:

  • N 较小:响应快,但噪声抑制能力弱
  • N 较大:噪声抑制强,但响应延迟大

执行流程详解

初始化

创建固定长度的数据缓存结构:

  • 可选择数组或队列数据结构实现
  • 缓存长度固定为5(示例中的N=5)
  • 初始化总和变量sum=0
  • 初始化数据计数变量count=0
  • 缓存初始值通常设置为0,也可根据应用场景设为其他默认值
  1. int windowSize = 5;
  2. int[] dataBuffer = new int[windowSize]; // 固定长度数组
  3. int sum = 0;
  4. int count = 0;
  5. int currentIndex = 0;

采集新数据

数据源 获取实时数据:

  • 数据源包括各类传感器(温度、压力、加速度计等)
  • 也可能是外部输入的数据流
  • 示例中设新采集的数据为x
  1. // 模拟采集新数据
  2. var newData = ReadSensor(); // 假设这是读取传感器的函数

更新滑动窗口

根据当前缓存状态处理新数据:

缓存未满(count < N):

  • 将新数据直接加入缓存
  • 更新sum = sum + new_data
  • count += 1

缓存已满(count == N):

  • 采用FIFO(先进先出)策略
  • 先计算sum = sum - 最旧数据(即将被替换的数据)
  • 再替换最旧数据为new_data
  • 更新sum = sum + new_data
  • 数据总数count保持不变
  1. if (count < windowSize)
  2. {
  3. // 缓存未满的情况
  4. dataBuffer[currentIndex] = newData;
  5. sum += newData;
  6. count++;
  7. currentIndex++;
  8. }
  9. else
  10. {
  11. // 缓存已满的情况
  12. int oldestIndex = currentIndex % windowSize;
  13. sum -= dataBuffer[oldestIndex]; // 减去最旧数据
  14. dataBuffer[oldestIndex] = newData; // 替换为最新数据
  15. sum += newData; // 加上最新数据
  16. currentIndex = (currentIndex + 1) % windowSize; // 循环缓冲区
  17. }

计算平均值

计算滤波输出值:

  • 有效数据求和:直接使用sum变量
  • 有效数据个数:min(count, N)
  • 滤波输出 = sum / 有效数据个数
  1. if (count > 0)
  2. {
  3. double average = sum / Math.Min(count, windowSize);
  4. return average;
  5. }
  6. else
  7. {
  8. return 0; // 或根据需求处理无数据情况
  9. }

循环执行

持续处理数据流:

  • 实际应用中通常采用循环或事件驱动方式
  • 每次新数据到达时重复步骤2-4

应用场景示例:

  • 工业传感器数据平滑
  • 金融数据移动平均计算
  • 实时信号处理系统
  1. while (true) // 持续运行的示例
  2. {
  3. float newData = ReadSensor();
  4. UpdateBuffer(newData);
  5. float filteredValue = CalculateAverage();
  6. Output(filteredValue);
  7. Thread.Sleep(samplingInterval); // 按采样间隔执行
  8. }

此流程特别适用于需要平滑噪声数据但又要保持实时性的应用场景,通过调整窗口大小N可以平衡滤波效果和响应速度。

算法性能分析

时间复杂度 分析

基础实现

  • 每次计算输出时需遍历窗口内全部N个数据点
  • 时间复杂度:O(N)/次计算
  • 实例:当N=10时,每次计算需10次加法运算

优化实现(工业标准方案)

  • 维护动态总和变量:
    • 总和减去最早数据
    • 总和加上新数据
  • 时间复杂度优化至O(1)/次计算
  • 尤其适合高频采样场景(≥100Hz的工业传感器)

空间复杂度

  • 需存储N个历史数据(常用循环缓冲区)
  • 固定内存占用:N×sizeof(data_type)
  • 典型应用:
    • 8位MCU:N=5,uint16_t → 10字节RAM
    • PLC系统:N=20,float → 80字节RAM

计算资源需求

运算类型

  • 仅需加减法运算
  • 支持纯整数运算(无需浮点单元)

逻辑复杂度

  • 无复杂条件判断
  • 无需分支预测
  • 典型实现仅需5-10条汇编指令

适用硬件

  • 8/16位单片机(8051、PIC等)
  • ARM Cortex-M0系列
  • 工业PLC(如西门子S7-200)
  • 嵌入式传感器节点

延迟特性

相位延迟

  • 固定延迟:(N-1)/2个采样周期
  • 实例:
    • N=5@100Hz → 20ms延迟
    • N=10@1kHz → 4.5ms延迟

频率响应

  • 幅频特性呈sinc函数
  • 无频率混叠
  • 无振铃效应(优于IIR滤波器)
  • 截止频率≈0.443/窗口时长

参考代码

提供两种最佳实现方案:

  • 队列版本:逻辑清晰直观,便于理解算法原理
  • 数组优化版本:性能达到极致,适用于工业级实际应用场景

通用说明

  • 支持 float/double/int 数据类型
  • 支持动态调整窗口尺寸
  • 具备数据未满时的自适应滤波功能(消除初始值干扰)

队列版滑动平均滤波

  1. /// <summary>
  2. /// 队列版滑动平均滤波(逻辑清晰,适合学习)
  3. /// </summary>
  4. public class MovingAverageFilter_Queue
  5. {
  6. // 滑动窗口大小
  7. private readonly int _windowSize;
  8. // 数据队列(存储窗口内最新N个数据)
  9. private readonly Queue<double> _dataQueue;
  10. // 总和缓存(优化求和计算)
  11. private double _sum;
  12. /// <summary>
  13. /// 初始化滤波器
  14. /// </summary>
  15. /// <param name="windowSize">窗口大小(建议2~20,根据噪声大小调整)</param>
  16. public MovingAverageFilter_Queue(int windowSize)
  17. {
  18. // 窗口大小合法性校验
  19. if (windowSize < 1)
  20. throw new ArgumentException("窗口大小必须大于等于1");
  21. _windowSize = windowSize;
  22. _dataQueue = new Queue<double>(windowSize);
  23. _sum = 0;
  24. }
  25. /// <summary>
  26. /// 滤波处理:输入原始数据,输出平滑后数据
  27. /// </summary>
  28. /// <param name="newData">新采集的原始数据</param>
  29. /// <returns>滤波后数据</returns>
  30. public double Filter(double newData)
  31. {
  32. // 1. 将新数据加入队列和总和
  33. _dataQueue.Enqueue(newData);
  34. _sum += newData;
  35. // 2. 队列超过窗口大小,移除最旧数据
  36. if (_dataQueue.Count > _windowSize)
  37. {
  38. double oldData = _dataQueue.Dequeue();
  39. _sum -= oldData;
  40. }
  41. // 3. 计算平均值(窗口未满时用实际数据个数平均)
  42. return _sum / _dataQueue.Count;
  43. }
  44. /// <summary>
  45. /// 重置滤波器(清空数据)
  46. /// </summary>
  47. public void Reset()
  48. {
  49. _dataQueue.Clear();
  50. _sum = 0;
  51. }
  52. }

数组 优化版滑动平均滤波

  1. /// <summary>
  2. /// 数组版滑动平均滤波(性能最高,无GC分配,工业级使用)
  3. /// </summary>
  4. public class MovingAverageFilter_Array
  5. {
  6. private readonly double[] _buffer; // 数据缓存数组
  7. private readonly int _windowSize; // 窗口大小
  8. private double _sum; // 数据总和
  9. private int _index; // 当前数据写入索引
  10. private int _count; // 有效数据个数
  11. public MovingAverageFilter_Array(int windowSize)
  12. {
  13. if (windowSize < 1)
  14. throw new ArgumentException("窗口大小必须大于等于1");
  15. _windowSize = windowSize;
  16. _buffer = new double[windowSize];
  17. _sum = 0;
  18. _index = 0;
  19. _count = 0;
  20. }
  21. /// <summary>
  22. /// 滤波核心方法
  23. /// </summary>
  24. public double Filter(double newData)
  25. {
  26. // 有效数据计数
  27. if (_count < _windowSize)
  28. _count++;
  29. // 总和 = 总和 - 被覆盖的旧数据 + 新数据
  30. _sum = _sum - _buffer[_index] + newData;
  31. // 更新缓存数组
  32. _buffer[_index] = newData;
  33. // 索引循环(环形缓冲区)
  34. _index = (_index + 1) % _windowSize;
  35. // 返回平均值
  36. return _sum / _count;
  37. }
  38. /// <summary>
  39. /// 重置滤波器
  40. /// </summary>
  41. public void Reset()
  42. {
  43. Array.Clear(_buffer, 0, _windowSize);
  44. _sum = 0;
  45. _index = 0;
  46. _count = 0;
  47. }
  48. }

使用示例

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. // 初始化滤波器,窗口大小=5
  6. var filter = new MovingAverageFilter_Array(5);
  7. // 模拟带噪声的传感器数据:真实值=25,叠加±1随机噪声
  8. Random random = new Random();
  9. Console.WriteLine("原始数据\t滤波后数据");
  10. for (int i = 0; i < 20; i++)
  11. {
  12. // 生成带噪声数据
  13. double rawData = 25 + (random.NextDouble() * 2 - 1);
  14. // 滤波处理
  15. double smoothData = filter.Filter(rawData);
  16. // 输出结果(保留2位小数)
  17. Console.WriteLine($"{rawData:F2}\t\t{smoothData:F2}");
  18. }
  19. }
  20. }

优缺点深度分析

优点详解

计算简单高效

  • 实现简单:仅需对窗口内数据进行求和再取平均
  • 计算示例(N=5):y(n) = [x(n) + x(n-1) + ... + x(n-4)] / 5
  • 对比优势:相比IIR滤波的乘加运算或卡尔曼滤波的矩阵运算,计算复杂度极低

资源占用极低

  • 内存需求:只需存储N个历史数据(如10点平均仅需10字节RAM)
  • 硬件适配性:适合8位MCU等低端硬件,甚至可用RC积分电路模拟实现
  • 典型应用:智能传感器、嵌入式设备、低成本IoT终端

噪声抑制效果稳定

  • 抑制原理:高斯白噪声通过平均后标准差降低√N倍
  • 实测数据(N=10):可将±5mV随机噪声降至±1.58mV
  • 稳定性优势
    • 无IIR滤波器的发散问题
    • 等权平均避免系统误差累积(连续处理10000点输出偏差<0.1%)
  • 适用场景:需要长期稳定性的测量系统(如温度、压力监测)

参数调节直观

  • 单一参数:仅需调整窗口大小N
  • 调节规律:N↑→平滑效果↑→延迟↑
  • 工程建议
    • 初始值通常取N=5
    • 工业控制常用范围N=4~20

实时性能优异

  • 优化算法y(n) = y(n-1) + [x(n) - x(n-N)]/N(时间复杂度O(1))
  • 执行效率:STM32F103处理10点平均仅需0.8μs
  • 适用场景:电机转速检测、电源纹波监测等高速应用

通用性强

  • 适用信号:缓慢变化的物理量(温度/压力/液位/光照等)
  • 典型应用
    • 热电偶测温(N=8~16)
    • 电子称重(N=4~10)
    • 电池电压监测(N=5~20)

缺点详解

固有相位延迟

  • 延迟计算:理论延迟 = (N-1)/2 个采样周期
  • 实例影响(N=10@1kHz采样):产生4.5ms延迟,可能导致控制系统超调

抗脉冲干扰能力弱

  • 敏感表现:单个野值会使输出偏差1/N
  • 典型场景:电源浪涌、传感器触点抖动
  • 改进方案:中值滤波更适合脉冲噪声环境

数据权重分配不合理

  • 本质问题:未体现物理系统"近大远小"特性
  • 案例说明:温度测量中最新数据比10秒前更具参考价值
  • 优化方案:可采用指数加权移动平均(EWMA)

高频抑制效果有限

  • 频率响应:-20dB/dec衰减特性
  • 实测局限:对100Hz以上噪声抑制效果显著下降
  • 不适用场景:振动信号、音频信号处理

窗口大小选择困境

  • 典型矛盾

    • N=3:残留噪声±2.5mV(延迟1ms)
    • N=15:残留噪声±1.0mV(延迟7ms)

  • 解决方案
    • 根据系统允许最大延迟反推N值
    • 采用变窗口移动平均(需额外算法支持)

适用场景详解

适用场景

滑动平均滤波(Moving Average Filter)是一种计算简单、易于实现的数字滤波方法,特别适合处理信号变化缓慢且以随机噪声为主的场景。因其出色的滤波效果和实现便捷性,成为工业应用中常用的滤波方案之一。

典型应用场景

温度采集

  • 应用场景

    • 室温监测(智能家居、HVAC系统)
    • 水温测量(锅炉、热水器、鱼缸温控)
    • 炉温监测(工业炉、烤箱、高温反应设备)

  • 特点:温度信号通常变化缓慢,但传感器易受环境噪声(如电磁干扰、热噪声)影响。

湿度采集

  • 应用场景

    • 环境湿度监测(气象站、农业大棚)
    • 工业湿度控制(制药、食品存储)

  • 特点:湿度数据变化平稳,但仍可能存在小幅波动。

液位/压力采集

  • 应用场景

    • 液压系统压力监测(工程机械、注塑机)
    • 气压测量(空压机、气压传感器)
    • 水箱液位监测(水塔、污水处理系统)

  • 特点:液位和压力信号变化较慢,但易受机械振动或电磁干扰影响。

直流电压/电流采集

  • 应用场景

    • 电源电压监测(UPS、开关电源)
    • 电池电压监测(锂电池管理系统BMS)

  • 特点:直流信号本身稳定,但可能叠加高频噪声。

慢速 运动控制

  • 应用场景

    • 电机低速位置检测(步进电机、伺服电机)
    • 角度采集(机械臂、旋转编码器)

  • 特点:低速运动时信号变化较慢,但可能受机械振动或电磁干扰影响。

气象数据处理

  • 应用场景

    • 风速监测(气象站、风力发电机组)
    • 气压测量(天气预报、航空设备)
    • 光照强度平滑(智能照明、农业监测)

  • 特点:气象数据变化缓慢,但易受瞬时干扰(如阵风、闪电)影响。

物联网传感器

  • 应用场景

    • 温湿度传感器(智能家居、农业监测)
    • 空气质量传感器(PM2.5、CO₂检测)
    • 噪声传感器(环境噪声监测)

  • 特点:IoT设备通常采用低功耗传感器,数据易受噪声干扰。

不适用场景

滑动平均滤波在以下情况表现不佳:

  • 信号快速变化的场景(如高速电机控制、振动测量、音频信号处理)

    • 原因:滤波会延迟信号响应,导致动态性能下降。


  • 存在强脉冲干扰的场景(如电力系统受雷击、工业电磁干扰)

    • 解决方案:可结合中值滤波(Median Filter)使用。


  • 实时性要求极高的控制系统(如无人机飞控、高速伺服系统)

    • 原因:需要存储历史数据,计算量较大,可能引入额外延迟。

备注:在混合噪声环境(如同时存在高频噪声和脉冲干扰)时,可考虑滑动平均与中值滤波的组合方案。

总结

核心总结

  • 滑动平均滤波 = 固定窗口 + 滑动更新 + 等权平均
  • 它是入门最简单、使用最广泛的基础滤波算法
  • 性能:优化版 O (1) 时间复杂度,极低资源占用
  • 效果:完美抑制随机噪声,保留缓慢变化的真实信号
  • 使用关键:窗口大小 N 调节(噪声大→调大 N,要求实时→调小 N)

最佳实践建议

  • 工业使用优先选择数组环形缓冲区实现(无 GC、高性能)
  • 窗口大小推荐:2~20(根据实际数据调试)
  • 脉冲干扰多的场景:滑动平均 + 中值滤波 组合使用
  • 资源极度受限设备:可使用整数运算避免浮点计算

滑动平均滤波是工业数据处理的基础算法,其可靠性和稳定性历经数十年实践检验,是所有工程师必须掌握的核心滤波技术。


posted @ 2026-06-23 14:51  北域码匠  阅读(92)  评论(1)    收藏  举报