别让杂波毁了你的项目!十大滤波算法「小白友好版」,看完就能用
摘要:本文介绍了10种常用滤波算法,帮助解决电子项目中传感器数据波动问题。包括限幅滤波(设警戒线拦截突变值)、算术平均滤波(多数据取平均)、中位值滤波(取中间值排除极端值)、递推平均滤波(滚动队列更新数据)、中位值平均滤波(先筛除极值再平均)和限幅平均滤波(先限幅再平均)。每种方法附有特点说明和可直接使用的代码示例,适合Arduino和工业检测等场景,无需复杂理论即可上手。(149字)
别让杂波毁了你的项目!十大滤波算法「小白友好版」,看完就能用
做电子项目时,你有没有过这种崩溃时刻?
测温度时,数值突然从25℃蹦到99℃,以为传感器烧了,拆下来检查半天没毛病;
测水位时,数据忽高忽低像坐过山车,明明水箱没动,屏幕上的数字却跳个不停;
甚至有时候,明明代码逻辑没问题,设备却因为“假数据”乱动作
其实啊,不是传感器坏了,也不是代码写错了,多半是你忘了给数据“除杂”,
少了个关键步骤:滤波。
今天就给大家扒一扒10种最常用的滤波算法,不用啃晦涩的公式,不用懂复杂的理论,像聊家常一样给你讲明白,看完不管是做Arduino项目,还是搞工业检测,都能直接上手用!
1. 限幅滤波法:给数据设个“跳崖警戒线”
(1)原理:别让数据“瞎蹦”
简单说,就是给数据定个“最大允许波动值A”——比如上次采集到的温度是25℃,这次突然变成35℃,如果A设为5,那35-25=10>5,就当这个35℃是“杂波捣蛋”,不算数,还沿用上次的25℃;要是这次只变到28℃,没超A,就用新的28℃。
(2)特点:能打“突袭杂波”,却怕“连环骚扰”
- 优点:对付那种突然冒出来的“脉冲杂波”特别管用,比如电路偶尔受到干扰,数据跳一下,它能立刻按住。
- 缺点:要是杂波是“周期性的”(比如每隔1秒就跳一次),它就没辙了;而且滤出来的数据不够平滑,像走台阶一样,一顿一顿的。
(3)例程:现成代码拿走用
直接复制到项目里,把FILTER_A改成你需要的“警戒线”就行:
int Filter_Value;
int Value;
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1 // 这里的1就是“警戒线”,根据你的数据改
int Filter() {
int NewValue;
NewValue = Get_AD(); // 获取新的传感器数据
// 判断是否超过“警戒线”
if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
return Value; // 超了就用上次的数
else
return NewValue; // 没超就用新数
}
2. 算术平均滤波法:给数据“算平均分”
(1)原理:多抓几个数据,取个平均值
就像老师算班级平均分一样:连续采集N个数据,把它们加起来,再除以N,结果就是滤完波的数据。比如测压力,连续抓4个数据:101、102、99、100,加起来402,除以4得100.5,就用100.5当有效数据。
(2)说明:N选得好,滤波效果才好
选N有讲究,不是越大越好:
- N大一点(比如12):数据会很平滑,但反应变慢,比如温度变了,要等12次采样才会显示变化,灵敏度低。
- N小一点(比如4):数据反应快,但平滑度差,偶尔还是会有杂波。
- 常见搭配:测流量(变动稍快)选N=12;测压力(相对稳)选N=4。
(3)特点:适合“稳一点”的杂波,不适合“急活”
- 优点:对付那种“随机乱晃”的杂波很合适,比如数据在100左右跳来跳去,算完平均就稳了。
- 缺点:一是慢,要是你需要实时控制(比如机器人避障,要立刻反应),它跟不上;二是费内存,要存N个数据,像囤太多快递占地方。
(4)例程:代码里的“平均分计算器”
int Filter_Value;
// 算术平均滤波法
#define FILTER_N 12 // 采集12个数据算平均,可改
int Filter() {
int i;
int filter_sum = 0; // 用来存数据总和
for(i = 0; i < FILTER_N; i++) {
filter_sum += Get_AD(); // 逐个加数据
delay(1); // 等1毫秒再采下一个,避免太密集
}
return (int)(filter_sum / FILTER_N); // 算平均并返回
}
3. 中位值滤波法:挑“中间派”当代表
(1)原理:数据排排队,中间那个最靠谱
先连续采集N个数据(N必须是奇数,比如5、7),把它们按大小排好队,然后挑正中间的那个当有效数据。比如采了5个温度:23、25、24、26、22,排好队是22、23、24、25、26,中间的24就是最终结果。
(2)特点:专治“突发极端值”,怕“急性子数据”
- 优点:能把那种突然冒出来的“极端杂波”直接排除,比如测温度突然蹦到50℃(实际是24℃),排队后50℃在最右边,中间的24℃照样能用;尤其适合温度、湿度这种变很慢的参数。
- 缺点:要是数据变得快(比如测水流速度,每秒都在变),等你采完N个数据排好队,实际数据早就变了,相当于“刻舟求剑”。
(3)例程:排序挑中间,代码给你写好了
int Filter_Value;
// 中位值滤波法
#define FILTER_N 101 // 101是奇数,可改(建议不要太小)
int Filter() {
int filter_buf[FILTER_N]; // 存N个数据的数组
int i, j;
int filter_temp; // 临时变量,用来排序交换
// 先采N个数据存起来
for(i = 0; i < FILTER_N; i++) {
filter_buf[i] = Get_AD();
delay(1);
}
// 冒泡排序:把数据从小到大排好队
for(j = 0; j < FILTER_N - 1; j++) {
for(i = 0; i < FILTER_N - 1 - j; i++) {
if(filter_buf[i] > filter_buf[i + 1]) {
filter_temp = filter_buf[i];
filter_buf[i] = filter_buf[i + 1];
filter_buf[i + 1] = filter_temp;
}
}
}
// 返回中间的那个数
return filter_buf[(FILTER_N - 1) / 2];
}
4. 递推平均滤波法:给数据搞个“滚动队列”
(1)原理:新数据进队,老数据出局
想象有个固定长度的“数据队列”(比如能装12个数据),每次采集新数据,就把它放进队尾,同时把队首最老的数据扔掉,然后算现在队列里12个数据的平均值。比如队列里原来有1-12,新数据13进来,就扔掉1,队列变成2-13,再算这12个的平均。
(2)特点:能治“周期性杂波”,怕“脉冲突袭”
- 优点:对付那种“周期性反复出现”的杂波(比如每秒都跳一次的干扰)特别厉害,滤出来的数据平滑得像刚熨过的衣服;适合电机振动、高频振荡这种场景。
- 缺点:要是遇到偶尔的“脉冲杂波”(比如突然跳一次大的),它会把这个杂波算进平均里,相当于“一颗老鼠屎坏了一锅粥”;而且不适合开关电源电路,容易被电源干扰带偏。
(3)例程:滚动队列的代码实现
#define N 12 // 队列长度,可改
char value_buf[N]; // 存数据的队列
char i = 0; // 记录队尾位置
char filter(void) {
char count = 0;
int sum = 0; // 存队列数据总和
value_buf[i++] = get_ad(); // 新数据进队尾
if(i == N) {
i = 0; // 队列满了,下次从队首开始(先进先出)
}
// 算队列里所有数据的和
for(count = 0; count < N; count++) {
sum += value_buf[count];
}
return (char)(sum / N); // 返回平均值
}
5. 中位值平均滤波法:“先筛再算”,双保险
(1)原理:先去掉极端值,再算平均
相当于“中位值滤波”+“算术平均滤波”的组合:先采N个数据,排好队,去掉最大的和最小的两个极端值,再把剩下的N-2个数据算平均。比如采10个数据:1、2、3、4、5、6、7、8、9、10,去掉1和10,剩下8个算平均。
(2)特点:杂波的“全能杀手”,就是有点慢
- 优点:既继承了中位值的优点(能去掉极端脉冲杂波),又有平均的优点(能治周期性杂波),相当于“双buff加持”;平滑度高,适合高频振荡的系统。
- 缺点:步骤多,要采集、排序、删极值、算平均,比单独的滤波法慢很多,要是你急着要数据(比如实时控制),它就有点跟不上。
(3)例程:先筛后算的代码
#define N 12 // 采集12个数据,可改
char filter(void) {
char i = 0, j = 0, temp = 0;
char value_buf[N]; // 存数据的数组
int sum = 0; // 存剩下数据的总和
// 先采N个数据
for(i = 0; i < N; i++){
value_buf[i] = get_ad();
delay(); // 等一会儿再采下一个
}
// 冒泡排序,把数据排好队
for(j = 0; j < N - 1; j++) {
for(i = 0; i < N - j; i++) {
if(value_buf[i] > value_buf[i + 1]) {
temp = value_buf[i];
value_buf[i] = value_buf[i + 1];
value_buf[i + 1] = temp;
}
}
}
// 去掉最大(最后一个)和最小(第一个),算中间的和
for(i = 1; i < N - 1; i++) {
sum += value_buf[i];
}
// 除以剩下的数据个数(N-2)
return (char)(sum / (N - 2));
}
6. 限幅平均滤波法:“先拦后算”,稳上加稳
(1)原理:先设警戒线,再算平均
就是“限幅滤波”+“算术平均滤波”的组合:先给每个新采集的数据做“限幅检查”(没超A就留着,超了就用上次的数),等收集够N个“合格数据”后,再算它们的平均值。
(2)特点:杂波拦得住,就是费内存
- 优点:结合了两种滤波的优点,既能拦住偶尔的脉冲杂波(限幅),又能让数据平滑(平均),相当于“先把门守好,再整理房间”。
- 缺点:跟算术平均一样,要存N个数据,有点费内存;如果N选得大,内存占用会更明显。
(3)例程:带限幅的平均滤波代码
/**
* 限幅平均滤波法
* @param input 输入的原始采样数据数组
* @param input_len 输入数据的长度
* @param threshold 限幅阈值(“警戒线”),超过此值的变化被视为干扰
* @param window_size 平均窗口大小(算几个数据的平均)
* @param output 输出的滤波结果数组
* @param output_len 输出结果的长度(通过指针返回)
*/
void filter(float *input, int input_len, float threshold,
int window_size, float *output, int *output_len) {
// 先申请一块内存存“合格数据”
float *valid_data = (float *)malloc(input_len * sizeof(float));
// 第一个数据直接算合格,不用检查
valid_data[0] = input[0];
float last_valid = input[0]; // 记录上一个合格数据
int valid_count = 1; // 合格数据的个数
// 逐个检查数据,做限幅
for (int i = 1; i < input_len; i++) {
// 判断当前数据和上一个合格数据的差是否超阈值
if (fabs(input[i] - last_valid) > threshold) {
// 超了,用上次的合格数据
valid_data[valid_count++] = last_valid;
} else {
// 没超,用当前数据,更新上一个合格数据
last_valid = input[i];
valid_data[valid_count++] = input[i];
}
}
// 算合格数据的平均值
*output_len = valid_count - window_size + 1;
if (*output_len < 1) { // 数据不够,返回空
*output_len = 0;
free(valid_data);
return;
}
// 逐个窗口算平均
for (int i = 0; i < *output_len; i++) {
float sum = 0.0f;
// 算当前窗口里数据的和
for (int j = 0; j < window_size; j++) {
sum += valid_data[i + j];
}
output[i] = sum / window_size; // 平均结果存到输出数组
}
free(valid_data); // 释放内存,别占着不用
}
7. 加权递推平均滤波法:给新数据“加权重”
(1)原理:新数据“话语权更大”
对“递推平均滤波”做了点改进:给队列里的每个数据贴个“权重”——越新的数据,权重越大(比如最新的数据权重12,前一个11,最老的1),然后用“数据×权重”的总和,除以“权重总和”,得到结果。就像老师改卷,期末考占60%,期中占40%,新数据的“分量”更重。
(2)特点:适合“慢反应设备”,不适合“慢数据”
- 优点:适合两种场景:一是设备反应慢(比如大滞后的温度控制系统),二是采样周期短(数据更新快);新数据权重高,能更快反映实际变化。
- 缺点:要是设备反应快、采样周期长(比如测缓慢变化的室温,10分钟采一次),这方法就像“用大炮打蚊子”——新数据和老数据差别不大,加权重也没用,滤波效果反而差。
(3)例程:带权重的递推平均代码
/* coe数组是加权系数表,越后面的数权重越大(可自己改) */
#define N 12 // 队列长度
char coe[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 权重数组
char sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 权重总和
char filter(void) {
char i = 0;
char value_buf[N]; // 存数据的队列
int sum = 0; // 存“数据×权重”的总和
// 先采N个数据存队列
for(i = 0; i < N; i++) {
value_buf[i] = get_ad();
delay();
}
// 算“数据×权重”的总和
for(i = 0; i < N; i++) {
sum += value_buf[i] * coe[i];
}
// 除以权重总和,得到结果
return (char)(sum / sum_coe);
}
8. 消抖滤波法:给数据“一点耐心”
(1)原理:等数据“稳定下来”再认
搞个“消抖计数器”,每次采新数据就和当前的“有效数据”比:
- 如果一样,说明数据稳了,计数器清零;
- 如果不一样,计数器就+1,再等下次采样;
- 要是计数器加到上限(比如12),还不一样,就把新数据当成有效数据,计数器再清零。
就像你按开关,偶尔手抖按一下没反应,要按稳几秒才会触发——避免数据“手抖”。
(2)特点:能治“临界抖动”,怕“干扰撞上限”
- 优点:对付慢变数据的“临界抖动”特别管用,比如测水位在“报警线”附近跳来跳去,用它能让数据稳定下来,不会频繁触发报警;也能避免显示器上的数字“跳广场舞”。
- 缺点:不适合快变数据(比如测车速,一秒变一次);而且如果计数器刚满的时候,刚好采到的是杂波,就会把杂波当成有效数据,相当于“认错人”。
(3)例程:消抖计数器的代码
#define N 12 // 计数器上限,可改(越大越稳,但越慢)
char filter(void) {
char i = 0;
char new_value = 0, value = 0; // new_value:新数据;value:当前有效数据
new_value = get_ad(); // 先采第一个新数据
// 只要新数据和有效数据不一样,就继续等
while(value != new_value) {
i++; // 计数器+1
if(i > N) { // 计数器超上限,认新数据
return new_value;
}
delay(); // 等一会儿再采
new_value = get_ad(); // 采新的一次数据
}
// 数据一样,返回有效数据
return value;
}
9. 限幅消抖滤波法:“先拦后等”,双重保险
(1)原理:先拦瞎蹦的数据,再等它稳定
相当于“限幅滤波”+“消抖滤波”的组合:
- 先做限幅:新数据和上次有效数据比,超A就用上次的,没超就用新的;
- 再做消抖:用限幅后的“候选数据”和当前有效数据比,不一样就计数,超上限再更新有效数据。
(2)特点:杂波拦得严,就是慢一点
- 优点:比单独的消抖滤波更靠谱——先把“跳得太狠”的杂波拦在门外,再等数据稳定,不会把明显的干扰当成有效数据;继承了限幅和消抖的所有优点。
- 缺点:和消抖滤波一样,不适合快变数据;步骤多了一点,反应会比单独的滤波法慢。
(3)例程:限幅+消抖的组合代码
int Filter_Value;
int Value;
// 限幅消抖滤波法
#define FILTER_A 1 // 限幅阈值(警戒线)
#define FILTER_N 5 // 消抖计数器上限
int i = 0; // 消抖计数器
int Filter() {
int NewValue;
int new_value; // 限幅后的候选数据
NewValue = Get_AD(); // 采新数据
// 第一步:限幅
if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
new_value = Value; // 超阈值,用上次的
else
new_value = NewValue; // 没超,用新的
// 第二步:消抖
if(Value != new_value) {
i++; // 计数器+1
if(i > FILTER_N) { // 超上限,更新有效数据
i = 0;
Value = new_value;
}
}
else
i = 0; // 数据一样,计数器清零
return Value; // 返回有效数据
}
10. 一阶滞后滤波法:给数据“一点缓冲”
(1)原理:新数据和老数据“掺着用”
设个系数α(α在0到1之间,比如0.01),滤波后的结果=(1-α)×新采样值 + α×上次滤波结果。比如上次结果是25,新采样值是26,α=0.01,那这次结果=0.99×26 + 0.01×25≈25.99,既接近新数据,又不会突然跳变。
α越小,新数据占比越高,反应越快;α越大,老数据占比越高,越平滑。
(2)特点:能治“高频杂波”,有“延迟后遗症”
- 优点:对付周期性的高频杂波很厉害,数据平滑度高;适合波动频率高的场景(比如电机运行时的电流检测)。
- 缺点:有“相位滞后”——实际数据变了,滤波后的结果要慢一点才会跟上,像反应慢半拍;而且α越大,滞后越明显;另外,滤不掉频率比采样频率一半还高的杂波(比如采样每秒10次,滤不掉每秒5次以上的杂波)。
(3)例程:带缓冲的一阶滞后代码
int Filter_Value;
int Value;
// 一阶滞后滤波法
#define FILTER_A 0.01 // 系数α,0<α<1,可改
int Filter() {
int NewValue;
NewValue = Get_AD(); // 采新数据
// 按公式计算滤波结果
Value = (int)((float)NewValue * (1.0 - FILTER_A) + FILTER_A * (float)Value);
return Value; // 返回结果
}
最后:怎么选对滤波算法?记住这3条
- 看杂波类型:偶尔跳一次选“限幅”,周期性跳选“递推平均”,极端值多选“中位值”;
- 看数据速度:慢变数据(温度、湿度)用“中位值”“消抖”,快变数据(流量、速度)用“限幅”“一阶滞后”;
- 看实时性:要快就用“限幅”“一阶滞后”,不着急就用“中位值平均”“限幅平均”。
其实不用死记硬背,做项目时多试两次,比如测温度先试试中位值,不行再换中位值平均,很快就能找到适合自己的那一个~
更多推荐



所有评论(0)