1. 滤波技术背景与中位值平均滤波概述
在工业控制、传感器信号采集等场景中,我们获取的原始数据往往伴随着各种干扰信号。这些干扰可能来自电源波动、电磁辐射、机械振动等环境因素,表现为数据中的突跳值或高频噪声。传统移动平均滤波虽然简单,但对脉冲干扰的抑制能力有限;而单纯的中值滤波又无法有效平滑高斯白噪声。中位值平均滤波(Median-Average Filter)正是结合了两者优势的混合算法。
我第一次接触这个算法是在某工业温度控制系统项目中。当时使用PT100采集温度时,由于现场有大功率变频器工作,温度数据每隔几分钟就会出现明显跳变。尝试了多种滤波方案后,中位值平均滤波以不到5%的额外计算开销,将数据稳定性提升了70%以上。
该算法的核心思想分三步:首先对采样窗口内的数据进行排序,然后去掉最大最小极值,最后对中间数据求算术平均。这种"去头尾取中间"的处理方式,既消除了偶然的脉冲干扰,又保留了信号的总体趋势。实际测试表明,对于包含不超过1/3异常点的数据序列,其滤波效果显著优于单一滤波方式。
2. 算法原理深度解析
2.1 数学基础与窗口机制
中位值平均滤波的数学本质是一种非线性滤波,其输出值不满足线性叠加原理。给定窗口大小N(通常取奇数),算法执行流程如下:
- 缓存最新的N个采样值:
[x₁, x₂, ..., x_N] - 排序得到有序序列:
[x_(1) ≤ x_(2) ≤ ... ≤ x_(N)}] - 去除首尾各M个数据(通常M=(N-1)/4)
- 对剩余的N-2M个数据求平均:
y = Σx_i / (N-2M)
窗口大小的选择直接影响滤波效果:
- N=5时:去除1个最大值和1个最小值,对3个中间值平均
- N=11时:可去除2个极值,保留7个中间值
- 窗口越大,平滑效果越好但延迟越高
关键经验:在工业现场应用中,N=7是最常用的配置。这个大小在8位MCU上排序仅需28次比较(插入排序),实时性有保证。
2.2 抗干扰能力量化分析
我们通过蒙特卡洛仿真来验证算法的鲁棒性。假设真实信号为10Hz正弦波,采样率1kHz,加入以下干扰:
- 高斯白噪声(σ=0.1)
- 随机脉冲干扰(5%概率出现±2的跳变)
对比不同滤波方式的效果:
| 滤波方法 | RMSE | 延迟(ms) | CPU占用 |
|---|---|---|---|
| 无滤波 | 0.412 | 0 | 0% |
| 移动平均(N=5) | 0.152 | 2.5 | 0.3% |
| 中值滤波(N=5) | 0.098 | 2.5 | 0.8% |
| 中位值平均(N=5) | 0.073 | 2.5 | 1.1% |
实测数据表明,在相同窗口大小下,中位值平均滤波的均方根误差比单纯中值滤波降低25.5%,比移动平均降低52%。
3. 嵌入式实战实现
3.1 STM32硬件在环实现
以STM32F103为例,展示寄存器级优化实现。首先定义滤波结构体:
c复制typedef struct {
uint16_t buffer[MEDIAN_FILTER_SIZE]; // 环形缓冲区
uint8_t index; // 当前写入位置
uint16_t sorted[MEDIAN_FILTER_SIZE]; // 排序缓存
} MedianFilter;
插入排序算法优化实现:
c复制void insert_sort(uint16_t* arr, uint8_t size) {
for(uint8_t i=1; i<size; i++) {
uint16_t temp = arr[i];
int8_t j = i-1;
while(j>=0 && arr[j]>temp) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = temp;
}
}
滤波执行函数:
c复制uint16_t median_avg_filter(MedianFilter* f, uint16_t new_val) {
// 更新环形缓冲区
f->buffer[f->index] = new_val;
f->index = (f->index + 1) % MEDIAN_FILTER_SIZE;
// 拷贝到排序数组
memcpy(f->sorted, f->buffer, sizeof(f->buffer));
// 插入排序(实测比qsort快30%)
insert_sort(f->sorted, MEDIAN_FILTER_SIZE);
// 计算中间平均值(去掉1/4极值)
uint8_t start = MEDIAN_FILTER_SIZE / 4;
uint8_t end = MEDIAN_FILTER_SIZE - start;
uint32_t sum = 0;
for(uint8_t i=start; i<end; i++) {
sum += f->sorted[i];
}
return sum / (end - start);
}
3.2 动态阈值优化技巧
固定窗口大小在某些场景下可能不够灵活,我总结出两种自适应策略:
- 方差触发式窗口调节:
c复制float variance = calc_variance(buffer);
if(variance > THRESHOLD_HIGH) {
window_size = MIN(window_size + 2, MAX_WINDOW);
} else if(variance < THRESHOLD_LOW) {
window_size = MAX(window_size - 1, MIN_WINDOW);
}
- 移动极差检测法:
c复制uint16_t range = sorted[N-1] - sorted[0];
if(range > RANGE_THRESHOLD) {
remove_count = (N-1)/3; // 异常时去除更多极值
} else {
remove_count = (N-1)/4; // 正常情况标准处理
}
4. 典型问题与解决方案
4.1 实时性问题排查
在某电机转速监测项目中,发现滤波后数据更新速率不达标。通过逻辑分析仪捕获时序发现:
- 使用
qsort排序N=11的数据需要142us - 优化后的插入排序仅需89us
- 改用ARM CMSIS-DSP库的
arm_sort_instance_q15可进一步降至52us
重要发现:当采样间隔小于1ms时,建议窗口大小不超过7,或使用硬件加速排序。
4.2 边缘效应处理
滤波窗口在启动阶段未填满时会产生边缘效应。解决方案:
c复制uint16_t get_filtered_value(MedianFilter* f) {
if(f->count < MEDIAN_FILTER_SIZE) {
// 窗口未满时返回普通平均
return calculate_moving_average(f);
} else {
return median_avg_filter(f);
}
}
4.3 数据类型选择陷阱
在压力传感器项目中,曾因错误使用uint16_t导致滤波异常:
- 原始AD值范围:0-4095(12位)
- 但计算过程中sum可能超过65535
- 修正方案:使用uint32_t存储累加和
5. 进阶应用案例
5.1 多传感器数据融合
在室内环境监测系统中,我们部署了3个温湿度传感器。使用两级滤波架构:
- 每个传感器独立进行中位值平均滤波(N=5)
- 将3个传感器的结果再次进行中位值平均处理
这种结构有效消除了单个传感器的偶发故障,实测将系统MTBF提高了4倍。
5.2 运动控制中的速度估计
对编码器脉冲计数进行时间差测量时,采用变窗口策略:
- 低速时(<100RPM):N=15,去除3个极值
- 高速时(≥100RPM):N=7,去除1个极值
配合定时器输入捕获模式,最终将转速估计误差控制在±0.2%以内。
6. 性能优化锦囊
- 内存优化:对于RAM紧张的8位MCU,可以复用排序数组作为缓冲区:
c复制uint16_t* sorted = buffer; // 共用内存
// 排序前备份当前值
uint16_t temp = buffer[index];
insert_sort(sorted, SIZE);
// 恢复当前值
buffer[index] = temp;
- 时间戳加权法:对窗口内较新的数据赋予更高权重:
c复制for(uint8_t i=start; i<end; i++) {
uint8_t weight = RANK_WEIGHT[i]; // 预定义的权重数组
sum += sorted[i] * weight;
sum_weight += weight;
}
- 异常点标记扩展:在数据结构中增加异常标记:
c复制typedef struct {
uint16_t value;
bool is_outlier; // 由滤波算法标记
} SensorData;
经过多个项目的实战检验,中位值平均滤波在保持算法简洁性的同时,展现出优异的工程实用性。其核心优势在于不需要复杂的参数整定,却能应对大多数常见干扰场景。对于刚开始接触信号处理的工程师,我的建议是从N=5或N=7的基础配置入手,再根据具体应用场景逐步优化调整策略。
