前言
人生如逆旅,我亦是行人。
一、限幅滤波法(又称程序判断滤波法)
方法:
- 比较相邻 n 和 n - 1 时刻的两个采样值 y(n) 和 y(n – 1) ,根据经验确定两次采样允许的 最大偏差,如果 两次采样值的差值超过最大偏差范围,认为发生 可随机干扰,并认为后一次采样值
y(n)
为非法值,应予删除,删除 y(n) 后,可用 y(n – 1) 代替 y(n) ;若未超过所允许的最大偏差范围,则认为本次采样值有效。
注:通俗地来讲,就是先确定一个最大的偏值差(设为A)
每次检测到新值时判断:
如果本次值与上次值之差<=A,则本次值有效,
如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值。
优点:
缺点:
程序:
#include <stdio.h>
// 声明最大误差:可根据实际情况进行调整
#define DELAT_MAX 10
// 定义滤波数据类型
typedef int filter_type;
// 函数声明
filter_type filter(filter_type effective_value, filter_type new_value, filter_type delat_max);
// 限幅滤波算法函数
filter_type filter(filter_type effective_value, filter_type new_value, filter_type delat_max)
{
if (( new_value - effective_value > delat_max ) || ( effective_value - new_value > delat_max ))
return effective_value; //effective_value为有效值,用有效值代替新值,舍去
else
return new_value; //new_value 为当前采样值滤波程序返回有效的实际值
}
// 打印测试
int main()
{
filter_type a = 8;
filter_type b = -3;
filter_type c;
c = filter(a, b, DELAT_MAX);
printf("%d", c);
return 0;
}
int Filter_Value;
int Value;
void setup() {
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
Value = 300;
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1
int Filter() {
int NewValue;
NewValue = Get_AD();
if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
return Value;
else
return NewValue;
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Value = Filter_Value; // 最近一次有效采样的值,该变量为全局变量
Serial.println(Filter_Value); // 串口输出
delay(50);
}
二、中值滤波算法
- 中值滤波是对一个滑动窗口内的诸像素灰度值排序,用其中值代替窗口中心象素的原来灰度值,它是一种非线性的图像平滑法,它对脉冲干扰级椒盐噪声的抑制效果好,在抑制随机噪声的同时能有效保护边缘少受模糊。
- 中值滤波可以过滤尖峰脉冲。目的在于我们对于滤波后的数据更感兴趣。滤波后的数据保留的原图像的变化趋势,同时去除了尖峰脉冲对分析造成的影响。
- 方法:
连续采样 N 次(N 取整数),然后把 N 个采样值按大小顺序排序,取中间值作为本次的有效值。 - 优点:
能克服因偶然因素引起的波动干扰;对温度、液位的变化缓慢的被测参数有良好的滤波效果。 - 缺点:
对流量、速度等快速变化的参数不宜。 - 程序:
int Filter_Value;
void setup()
{
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
}
// 用于随机产生一个300左右的当前值
int Get_AD()
{
return random(295, 305);
}
// 中位值滤波法
#define FILTER_N 101
int Filter()
{
int filter_buf[FILTER_N];
int i, j;
int filter_temp;
for (i = 0; i < FILTER_N; i++)
{
filter_buf = Get_AD();
delay(1);
}
// 采样值从小到大排列(冒泡排序法)
for (j = 0; j < FILTER_N - 1; j++)
{
for (i = 0; i < FILTER_N - 1 - j; i++)
{
if (filter_buf > filter_buf[i + 1])
{
filter_temp = filter_buf;
filter_buf = filter_buf[i + 1];
filter_buf[i + 1] = filter_temp;
}
}
}
//算出平均值作为最后的有效值
return filter_buf[(FILTER_N - 1) / 2];
}
void loop()
{
Filter_Value = Filter(); //获得滤波器输出值
Serial.println(Filter_Value); //串口输出
delay(50);
}
-
bArray
是一个整形指针,我们传入的一般是一个数组,用来存储待排序的数据 ;iFilterLen
是滤波器的长度 ; - 用在图像处理中时,由于像素的取值范围是
0~255
,刚好是unsigned char
的范围,所以函数的返回值是unsigned char
,如果我们要处理的数是float
型,或其他类型,返回值也可以更改返回值是temp
,也即是我们想得到的中值
#include<stdio.h>
unsigned char GetMedianNum(int * bArray, int iFilterLen)
{
unsigned char temp;
//利用冒泡排序进行排序
for (int i = 0; i < iFilterLen - 1; i++)
{
/* code */
for (int j = 0; i < iFilterLen - 1 - i; j++)
{
/* code */
if (bArray[i] > bArray[i+1])
{
/* 互换 */
temp = bArray[i];
bArray[i] = bArray[i+1];
bArray[i+1] = temp;
}
}
}
//计算中值
if ((iFilterLen & 1) > 0)
{
//数组中有奇数个元素,返回中间一个元素
temp = bArray[(iFilterLen+1) / 2];
}
else
{
// 数组有偶数个元素,返回中间两个元素平均值
temp = (bArray[iFilterLen/2] + bArray[iFilterLen/2 + 1]) / 2;
}
return temp;
}
三、算术平均滤波法
- 方法:
连续取 N 个采样值进行算术平均值运算:
- N 值较大时:信号平滑度较高,但灵敏度较低;
- N 值较小时:信号平滑度较低,但灵敏度较高;
- N值的选取:一般流量,N=12;压力:N=4。
- 优点
适用于对一般具有随机干扰的信号进行滤波;
这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动 - 缺点:
- 对于测量速度较慢或要求数据计算速度较快的实时控制不适用;比较浪费RAM、
- 对于偶发异常,比如某次的采样值明显偏大,经过算数平均值导致最终值存在较大偏差。针对这种极大值或极小值的问题,可以在算数平均前先进行剔除。
- 在实际如现场比赛打分中,就是去掉最高分和最低分之后的平均值。但是对于一次采样周期中出现多个异常数据则无法排除,正如比赛中,恰好某选手有2个以上的评委干爹,这是无解的。不过可加大采样量,1分钟内采集4次改为采集10次,排序后去掉最大的2个值和最小的2个值,再对剩下的6个值求算数平均值。
- 程序:
int Filter_Value;
void setup()
{
Serial.begin(9600); //初始化串口通信
randomSeed(analogRead(0)); //产生随机种子
}
//用于随机产生一个300左右的当前值
int Get_AD()
{
return random(295,305);
}
//算术平均滤波
#define FILTER_N 12
int Filter(void)
{
int filter_sum = 0;
for(int i=0; i<FILTER_N; i++)
{
filter_sum += Get_AD();
delay(1); //延时一毫秒
}
return (int)(filter_sum/FILTER_N);
}
void loop()
{
Filter_Value = Filter(); //获得滤波器输出值
Serial.println(Filter_Value); //串口输出
delay(50);
}
- 改进后的算法:剔除极大值和极小值;
int filter(void)
{
int sum=0;
int min=0,max=0;
for(int i=0; i<FILTER_N; i++)
{
if(vBat[i]>max)
{
max=vBat[i];
}
if(vBat[i]<min)
{
min==vBat[i];
}
sum+=vBat[i];
}
sum=sum-min-max;
return sum/SAMPLE_NUM;
}
四、递推平均滤波法(又称滑动平均滤波法)
- 方法:
- 把连续取得的N个采样值看成一个队列,队列的长度固定为N
- 每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出:
FIFO
) - 把队列中的N个数据进行算术平均运算,获得新的滤波结果
- 注:
N
值的选取:流量,N = 12
;压力,N = 4
;液面,N = 4~12
;温度,N = 1~4
- 优点:
- 对 周期性干扰 有良好的抑制作用,平滑度高
- 适用于 高频振荡的系统
- 缺点:
- 灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差;
- 不易消除由于脉冲干扰所引起的采样值偏差;
- 不适用于脉冲干扰比较严重的场合;
- 比较浪费 RAM ;
- 程序:
int Filter_Value;
void setup() {
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 递推平均滤波法(又称滑动平均滤波法)
#define FILTER_N 12
int filter_buf[FILTER_N + 1];
int Filter() {
int filter_sum = 0;
filter_buf[FILTER_N] = Get_AD();
for(int i = 0; i < FILTER_N; i++) {
filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
filter_sum += filter_buf;
}
return (int)(filter_sum / FILTER_N);
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}
- 参考代码:
/*定义滤波数据类型*/
typedef int filter_type;
// 函数声明
filter_type filter(filter_type value_buf[], filter_type new_value, int num);
/* 递推平均滤波函数 */
filter_type filter(filter_type value_buf[], filter_type new_value, int num)
{
static int i;
int count;
filter_type sum = 0;
value_buf[i++] = new_value;
if (i == num)
i = 0;
for (count=0; count<num; count++)
sum += value_buf[count];
return (filter_type)(sum/num);
}
五、中位值平均滤波法(又称防脉冲干扰平均滤波法)
- 方法:(相当于 “中位值滤波法” + “算术平均滤波法” )
- 采一组队列去掉最大值和最小值后取平均值
- 连续采样N个数据,去掉一个最大值和一个最小值
- 然后计算N-2个数据的算术平均值
- 注:
N
值的选取:3~14
- 优点:
- 融合了“中位值滤波法”+“算术平均滤波法”两种滤波法的优点
- 对于偶然出现的脉冲性干扰,可消除由其所引起的采样值偏差
- 对周期干扰有良好的抑制作用
- 平滑度高,适于高频振荡的系统
- 缺点:
- 计算速度较慢,和算术平均滤波法一样;
- 比较浪费 RAM ;
- 程序:
方法一:
int Filter_Value;
void setup() {
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
#define FILTER_N 100
int Filter() {
int i, j;
int filter_temp, filter_sum = 0;
int filter_buf[FILTER_N];
for(i = 0; i < FILTER_N; i++) {
filter_buf = Get_AD();
delay(1);
}
// 采样值从小到大排列(冒泡法)
for(j = 0; j < FILTER_N - 1; j++) {
for(i = 0; i < FILTER_N - 1 - j; i++) {
if(filter_buf > filter_buf[i + 1]) {
filter_temp = filter_buf;
filter_buf = filter_buf[i + 1];
filter_buf[i + 1] = filter_temp;
}
}
}
// 去除最大最小极值后求平均
for(i = 1; i < FILTER_N - 1; i++)
filter_sum += filter_buf;
return filter_sum / (FILTER_N - 2);
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}
方法二:
#define FILTER_N 100
int Filter()
{
int i;
int filter_sum = 0;
int filter_max, filter_min;
int filter_buf[FILTER_N];
for (i = 0; i < FILTER_N; i++)
{
filter_buf = Get_AD();
delay(1);
}
filter_max = filter_buf[0];
filter_min = filter_buf[0];
filter_sum = filter_buf[0];
for (i = FILTER_N - 1; i > 0; i--)
{
if (filter_buf > filter_max)
filter_max = filter_buf;
else if (filter_buf < filter_min)
filter_min = filter_buf;
filter_sum = filter_sum + filter_buf;
filter_buf = filter_buf[i - 1];
}
i = FILTER_N - 2;
filter_sum = filter_sum - filter_max - filter_min + i / 2; // +i/2 的目的是为了四舍五入
filter_sum = filter_sum / i;
return filter_sum;
}
六、限幅平均滤波算法法
- 方法:(相当于“限幅滤波法”+“递推平均滤波法”)
- 每次采样到的新数据先进行限幅处理
- 再送入队列进行递推平均滤波处理
- 优点:
- 融合了 “限幅滤波法” + “递推平均滤波法” 两种滤波法的优点
- 对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差
- 缺点:
- 比较浪费
RAM
;
- 程序:
#define FILTER_N 12
int Filter_Value;
int filter_buf[FILTER_N];
void setup()
{
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
filter_buf[FILTER_N - 2] = 300;
}
// 用于随机产生一个300左右的当前值
int Get_AD()
{
return random(295, 305);
}
//限幅平均滤波法
#define FILTER_A 1
int Filter()
{
int filter_sum = 0;
filter_buf[FILTER_N - 1] = Get_AD();
if(((filter_buf[FILTER_N - 1] - filter_buf[FILTER_N - 2]) > FILTER_A) ||
((filter_buf[FILTER_N - 2] - filter_buf[FILTER_N - 1]) > FILTER_A))
filter_buf[FILTER_N - 1] = filter_buf[FILTER_N - 2];
for(int i=0; i<FILTER_N; i++)
{
filter_buf = filter_buf[i + 1];
filter_sum += filter_buf;
}
return (int)filter_sum/(FILTER_N - 1);
}
- 参考代码:
#define LIMIT 200
#define SIZE 20
//返回两个数差
typedef unsigned int filter_type;
filter_type num_sub( filter_type a, filter_type b )
{
return ( a >= b ? ( a - b ) : ( b - a ) );
}
filter_type filter( void )
{
static filter_type value_buf[SIZE];
static unsigned int i = 0;
filter_type count;
filter_type new_value = 0;
static filter_type last_value = 0;
int sum = 0;
new_value = ReadVol_CH2();
if( num_sub( new_value, last_value ) < LIMIT )
{
value_buf[i++] = new_value;
last_value = new_value;
}
else
{
value_buf[i++] = last_value;
}
if( i == SIZE )
{
i = 0;
}
for( count = 0; count < SIZE; count++ )
{
sum += value_buf[count];
}
return ( last_value )( sum / SIZE );
}
七、一阶滞后滤波法(又叫一阶惯性滤波)
- 算法公式:
注:a 为滤波系数,X(n)= 本次采样值,Y(n-1) = 上次滤波输出值,Y(n)= 本次滤波输出值。 - 方法:
- 取 a = 0 ~ 1;
- 本次滤波结果 = (1 - a)* 本次采样值 + a * 上次滤波结果
- 优点:
- 对周期性干扰具有良好的抑制作用;
- 适用于波动频率较高的场合
- 缺点:
- 相位滞后,灵敏度低;
- 滞后程度取决于 a 值大小;
- 不能消除滤波频率高于采样频率 1/2 的干扰信号。
- 程序:
int Filter_Value;
int Value;
void setup()
{
Serial.begin(9600); //初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
Value = 300;
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 一阶滞后滤波法
#define FILTER_A 0.01
int Filter() {
int NewValue;
NewValue = Get_AD();
Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value);
return Value;
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}
八、加权递推平均滤波法
- 方法:
- 是对 递推平均滤波法 的改进,即不同时刻的数据加以不同的权
- 通常是,越接近现时刻 的数据,权取得越大
- 给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低
- 优点:
- 适用于有较大纯滞后时间常数的对象,和采样周期较短的系统
- 缺点:
- 对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号
- 不能迅速反应系统当前所受干扰的严重程度,滤波效果差
- 程序:
int Filter_Value;
void setup()
{
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 加权递推平均滤波法
#define FILTER_N 12
int coe[FILTER_N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 加权系数表
int sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 加权系数和
int filter_buf[FILTER_N + 1];
int Filter() {
int i;
int filter_sum = 0;
filter_buf[FILTER_N] = Get_AD();
for(i = 0; i < FILTER_N; i++) {
filter_buf = filter_buf[i + 1]; // 所有数据左移,低位仍掉
filter_sum += filter_buf * coe;
}
filter_sum /= sum_coe;
return filter_sum;
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}
- 参考代码:
define N 12
typedef unsigned int filter_type;
filter_type coe[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
filter_type sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12;
extern filter_type Read[N]; /采样得到的数组/
filter_type filter( filter_type * read )
{
filter_type count;
filter_type value_buf[N];
unsigned long sum = 0;
for( count = 0; count < N; count++ )
{
value_buf[count] = Read[count];
}
for( count = 0; count < N; count++ )
{
sum += value_buf[count] * coe[count];
}
return ( filter_type )( sum / sum_coe );
}
注:coe
为数组加权系数表,存在程序存储区,sum_coe
为加权系数和。
九、消抖滤波算法
- 方法:
- 设置一个滤波计数器,将每次采样值与当前有效值比较
- 如果采样值
=
当前有效值,则计数器清零 - 如果采样值
!=
当前有效值,则计数器+1
,并判断计数器是否>=
上限N
(溢出) - 如果计数器溢出,则将本次采样值替换为当前有效值,并清计数器
- 优点:
- 对于变化缓慢的被测参数有较好的滤波效果
- 可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动
- 缺点:
- 对于快速变化的参数不宜
- 如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统
- 程序:
int Filter_Value;
int Value;
void setup() {
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
Value = 300;
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 消抖滤波法
#define FILTER_N 12
int count = 0;
int Filter() {
int new_value;
new_value = Get_AD();
if(Value != new_value) {
count ++;
if(count > FILTER_N) {
count = 0;
Value = new_value;
}
}
else
count = 0;
return Value;
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}
十、限幅消抖滤波法
- 方法:
- 相当于 “限幅滤波法” + “消抖滤波法” ;
- 先限幅,后消抖。
- 优点:
- 继承了“限幅”和“消抖”的优点
- 改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统。
- 缺点:
- 对于快速变化的参数不宜
- 程序:
int Filter_Value;
int Value;
void setup() {
Serial.begin(9600); // 初始化串口通信
randomSeed(analogRead(0)); // 产生随机种子
Value = 300;
}
// 用于随机产生一个300左右的当前值
int Get_AD() {
return random(295, 305);
}
// 限幅消抖滤波法
#define FILTER_A 1
#define FILTER_N 5
int count = 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) {
count ++;
if(count > FILTER_N) {
count = 0;
Value = new_value;
}
}
else
count = 0;
return Value;
}
void loop() {
Filter_Value = Filter(); // 获得滤波器输出值
Serial.println(Filter_Value); // 串口输出
delay(50);
}