传感器数据跳变:均值、中值和低通滤波怎么选

前言

传感器接入单片机后,原始数据很少是一条理想的平滑曲线。温度、距离、压力、光照、电流、IMU 等信号都会受到量化误差、电源噪声、机械振动、通信抖动和环境变化影响。更麻烦的是,数据中的“跳变”不一定都是噪声:有些是偶发的毛刺,有些是缓慢叠加的随机波动,也有些恰好是真实物理量发生了阶跃变化。

因此,滤波的目标不是让曲线看起来越平滑越好,而是在抑制无效波动与保留有效变化之间取得平衡。窗口过大、滤波过强,可能让控制系统反应变慢;滤波过弱,又无法消除误触发和错误读数。

本文以嵌入式实时采样为背景,说明如何根据跳变形态选择均值滤波、中值滤波和一阶低通滤波,并给出可直接移植到 Arduino、STM32 或 ESP32 项目的 C/C++ 示例。

1. 先判断“跳变”属于哪一种

在写滤波代码前,先连续保存一段原始采样值,至少包含正常工况、干扰出现时刻以及信号真实变化时刻。建议同时记录采样时间戳,避免把采样周期不稳定误认为数据噪声。

常见数据形态可以分成以下几类:

数据形态 常见原因 首选方法 注意事项
围绕真实值的小幅随机波动 ADC 量化、热噪声、轻微机械振动 均值滤波或低通滤波 会带来一定响应滞后
偶尔出现单点极大值或极小值 I2C/SPI 读数异常、接触不良、超声波回波误判 中值滤波 先确认不是有效事件
连续多个样本突然偏离 电源扰动、外界强干扰、传感器失效 限幅 + 故障检测 单靠平滑滤波不够
真实值缓慢变化,但叠加高频抖动 温度、光照、电位器等慢变量 一阶低通滤波 根据采样周期设置系数
真实值本身发生阶跃 开关状态、接近目标、碰撞等 少滤波或使用状态判断 过度滤波会延迟响应

一个实用原则是:先按异常形态处理离群点,再做平滑。 例如超声波测距偶尔测到 0 cm 或 500 cm,应先判断该值是否在合理物理范围内;直接做均值可能会让这个异常值污染后续多个输出样本。

2. 三种滤波器的核心差异

方法 主要处理对象 对单点毛刺的抑制 响应速度 RAM 占用 典型用途
均值滤波 连续随机波动 一般 窗口越大越慢 需要保存窗口 稳态 ADC、温度、光照
中值滤波 孤立异常点 很强 取决于窗口大小 需要保存并排序窗口 距离、接触不良读数、脉冲干扰
一阶低通 高频抖动 较弱 由系数决定 极低 连续慢变量、控制输入

三者不是互斥关系。实际项目中常见的组合是:

原始采样 -> 合法范围检查 -> 中值滤波 -> 一阶低通 -> 业务判断

这条链路尤其适合超声波、红外测距和部分模拟量传感器:中值滤波先去掉偶发错误读数,低通滤波再减小持续的小幅抖动。

3. 均值滤波:适合连续随机噪声

均值滤波使用最近 N 个样本的算术平均值作为输出:

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

当噪声近似随机、正负波动大致对称时,多个样本平均后会互相抵消,因此均值滤波适合温度、光照、电压等变化相对平缓的信号。

3.1 固定窗口均值实现

以下实现维护一个环形缓冲区与累计和,每次更新的时间复杂度为 O(1),避免反复遍历整个数组。

template <size_t N>
class MovingAverage {
public:
    float update(float sample) {
        sum_ -= samples_[index_];
        samples_[index_] = sample;
        sum_ += sample;
​
        index_ = (index_ + 1) % N;
        if (count_ < N) {
            ++count_;
        }
        return sum_ / count_;
    }
​
private:
    float samples_[N] = {};
    float sum_ = 0.0f;
    size_t index_ = 0;
    size_t count_ = 0;
};
​
MovingAverage<8> voltageFilter;
​
float filteredVoltage(float rawVoltage) {
    return voltageFilter.update(rawVoltage);
}

3.2 窗口大小怎么定

窗口不是越大越好。采样周期为 T_s 时,N 个点大致覆盖 N × T_s 的历史数据。

  • 每 10 ms 采样一次、使用 N = 8,输出会参考约 80 ms 内的数据。

  • 每 100 ms 采样一次、使用 N = 20,输出会参考约 2 s 内的数据。

  • 用于阈值报警时,应确认这段延迟是否会造成漏检或响应过慢。

均值滤波对单个离群点并不鲁棒。假设正常值约为 100,其中一个样本误读为 500,即便 N = 5,平均值也会被抬高约 80。所以存在明显毛刺时,应先做范围检查或中值滤波。

4. 中值滤波:优先处理孤立毛刺

中值滤波取一个窗口内样本排序后的中间值。对于 N = 5 的窗口:

样本:100, 101, 99, 500, 100
均值:180
中值:100

可以看到,单个 500 的异常样本会严重影响均值,但几乎不会影响中值。中值滤波因此很适合处理偶发尖峰、通信偶发错误、回波误判和接触抖动。

4.1 使用奇数窗口

窗口长度通常取奇数,例如 3、5、7。这样排序后存在唯一中位数,不需要额外定义两个中间值如何合并。嵌入式项目中 3 点中值和 5 点中值最常用:

  • 3 点:延迟低,能清除单点毛刺。

  • 5 点:抗离群能力更强,适合错误读数较多的环境。

  • 7 点及以上:计算和延迟增加,只有干扰持续较多采样点时才考虑。

4.2 小窗口中值实现

以下示例使用 5 个样本,复制后进行插入排序。对于很小的固定窗口,代码简单且开销可控。

template <size_t N>
class MedianFilter {
public:
    static_assert(N % 2 == 1, "Median window must be odd");
​
    float update(float sample) {
        samples_[index_] = sample;
        index_ = (index_ + 1) % N;
        if (count_ < N) {
            ++count_;
        }
​
        float sorted[N];
        for (size_t i = 0; i < count_; ++i) {
            sorted[i] = samples_[i];
        }
        for (size_t i = 1; i < count_; ++i) {
            float value = sorted[i];
            size_t j = i;
            while (j > 0 && sorted[j - 1] > value) {
                sorted[j] = sorted[j - 1];
                --j;
            }
            sorted[j] = value;
        }
        return sorted[count_ / 2];
    }
​
private:
    float samples_[N] = {};
    size_t index_ = 0;
    size_t count_ = 0;
};
​
MedianFilter<5> distanceFilter;

中值滤波不是故障诊断的替代品。如果异常值连续出现,说明传感器安装、通信链路、电源或测量条件可能存在真实问题。此时应记录异常次数并上报状态,而不是无限吞掉错误数据。

5. 一阶低通滤波:用一个系数平衡平滑与响应

一阶低通滤波也常被称为指数滑动平均。它不需要保存历史窗口,只保留上一次输出:

y[k] = y[k-1] + alpha × (x[k] - y[k-1])

其中:

  • x[k] 是当前原始样本。

  • y[k] 是当前滤波输出。

  • alpha 位于 0 到 1 之间。

alpha 越小,输出越平滑,但跟随真实变化越慢;alpha 越大,输出越灵敏,但对噪声的抑制越弱。

5.1 适合实时项目的实现

class LowPassFilter {
public:
    explicit LowPassFilter(float alpha) : alpha_(alpha) {}
​
    float update(float sample) {
        if (!initialized_) {
            value_ = sample;
            initialized_ = true;
        } else {
            value_ += alpha_ * (sample - value_);
        }
        return value_;
    }
​
    void reset(float sample) {
        value_ = sample;
        initialized_ = true;
    }
​
private:
    float alpha_;
    float value_ = 0.0f;
    bool initialized_ = false;
};
​
LowPassFilter temperatureFilter(0.2f);
​
float filteredTemperature(float rawTemperature) {
    return temperatureFilter.update(rawTemperature);
}

初始化时要直接使用第一个有效样本。若默认从 0 开始滤波,设备刚启动时会出现一段不真实的爬升过程,可能误触发低阈值告警。

5.2 根据采样周期设置 alpha

如果希望用时间常数 tau 描述平滑强度,可根据实际采样周期 T_s 计算:

alpha = T_s / (tau + T_s)

例如每 20 ms 采样一次,希望输出在约 180 ms 的尺度上平滑:

alpha = 0.02 / (0.18 + 0.02) = 0.1

这比固定写一个经验值更可靠,尤其当任务调度改变、采样周期从 10 ms 改为 100 ms 时。若采样周期存在明显抖动,应使用真实的时间差 dt 动态计算系数,或先让采样定时器保持稳定。

6. 一套实际可用的处理链路

对容易出现离群值又需要平滑输出的传感器,可以采用“有效性检查 + 中值 + 低通”的组合:

MedianFilter<5> medianFilter;
LowPassFilter lowPassFilter(0.2f);
​
bool readFilteredDistance(float rawDistance, float& result) {
    // 范围应按具体传感器规格和安装条件配置。
    if (rawDistance < 2.0f || rawDistance > 400.0f) {
        return false;
    }
​
    const float median = medianFilter.update(rawDistance);
    result = lowPassFilter.update(median);
    return true;
}

处理顺序很重要:

  1. 先拒绝协议错误值、超量程值和不符合物理约束的值。

  2. 使用中值滤波抵抗偶发毛刺。

  3. 使用低通滤波减小连续抖动。

  4. 最后再做阈值判断、PID 控制或数据上报。

对于安全相关或闭环控制场景,不能只依赖滤波后的值。还需要保留原始值、异常计数、连续有效样本数量和超时状态,避免滤波器用旧数据掩盖传感器已经失效的事实。

7. 滤波参数的验证方法

参数应来自采样数据和业务约束,而不是“看起来顺眼”。建议在串口、CSV 文件或上位机曲线中同时输出原始值和滤波值,并完成以下测试:

  • 静态测试:传感器保持不动,比较滤波前后的峰峰值和标准差。

  • 阶跃测试:快速改变被测量,例如移动目标物,记录输出达到新值 90% 所需时间。

  • 毛刺测试:模拟一次错误读数,确认不会导致报警、控制输出或 UI 误跳变。

  • 持续异常测试:让传感器持续返回非法值,确认系统会报告故障而不是一直显示旧的平滑结果。

  • 边界测试:在量程上下限、阈值附近和上电初始阶段测试,检查滤波初始化与滞回逻辑。

可以用下面的记录格式快速对比:

time_ms, raw, median, lowpass, valid
0,       102.1, 102.1,  102.1,   1
20,      101.8, 101.8,  102.0,   1
40,      486.0, 102.1,  102.0,   1
60,      102.3, 102.1,  102.0,   1
80,      -1.0,  -,      -,       0

8. 常见误区

8.1 把滤波当成修复硬件问题

若数据跳变与电机启动、继电器吸合、线缆移动或电源切换强相关,应优先排查供电、接地、屏蔽、传感器安装和通信时序。滤波只能降低表现,不能修复错误来源。

8.2 所有传感器都套同一组参数

温度变化慢,可接受较强平滑;避障测距和姿态控制则更关注延迟。不同传感器、不同采样频率和不同业务目标不应共用同一个窗口或 alpha。

8.3 只过滤、不设置滞回

当测量值在报警阈值附近上下抖动时,即便做过滤波,仍可能频繁切换状态。对开关类业务应配合滞回:例如高于 32.0 才报警,低于 30.0 才解除报警。

8.4 忽略滤波引入的延迟

滤波输出一定会滞后于真实输入。用于电机保护、碰撞检测或高速控制前,必须以最坏情况评估延迟,并保留快速故障通道。

9. 选型结论

可以用下面的规则快速起步:

  • 数据只是连续小幅抖动,且允许短暂延迟:选均值滤波。

  • 数据会偶发跳到明显不合理的极值:先选中值滤波。

  • 信号持续变化,需要很低的 RAM 占用和可调的平滑程度:选一阶低通滤波。

  • 同时存在毛刺与高频抖动:采用范围检查 + 中值 + 低通的组合。

  • 数据异常连续出现或与硬件动作相关:先解决传感器、电源、接线和安装问题,再讨论滤波。

最终应以“误触发是否减少、真实变化是否仍能及时响应、异常是否能够被识别”为验收标准。曲线平滑只是结果,可靠地保留有效信息才是滤波设计的目标。