当前位置: 首页 > news >正文

常见信号滤波方法(卡尔曼滤波、滑动平均、异常值剔除)的原理解析与C语言实现

常见信号滤波方法(卡尔曼滤波、滑动平均、异常值剔除)的原理解析与C语言实现

日期作者版本备注
2023.09.04Dog TaoV1.0完成文档的初始版本。

文章目录

  • 常见信号滤波方法(卡尔曼滤波、滑动平均、异常值剔除)的原理解析与C语言实现
    • 前言
    • 卡尔曼滤波
      • 算法简述
      • 参考源码
      • 调用示例
    • 滑动平均滤波
      • 算法简述
      • 参考源码
      • 调用示例
    • 异常值剔除滤波
      • 算法简述
      • 参考源码
      • 调用示例

前言

卡尔曼滤波、滑动平均和异常值剔除是信号处理和数据分析中常用的滤波和平滑技术。这些方法旨在从测量或采集的数据中提取有价值的信息,同时减少噪声和不必要的波动。

卡尔曼滤波起源于1960年,由Rudolf E. Kalman开发,用于解决航天任务中的导航和控制问题。卡尔曼滤波为线性动态系统提供了一个递归的解决方案,用于预测和估计系统的状态。在存在噪声的情况下,它可以提供对系统状态的最优估计。它广泛应用于导航、经济学、机器人技术和许多其他领域。

滑动平均是时间序列分析中的一种基本技术,存在已久,被广泛应用于各种领域,从经济学到工程学。通过计算数据点的局部集合的平均值来平滑数据,从而降低随机波动和噪声。滑动平均简单、直观且计算高效,适用于实时系统和任何需要平滑数据的应用,从股市预测到传感器数据处理。

在数据分析中,数据往往包含错误、噪声或异常值,这些值可能是由测量错误、设备故障或其他非标准事件引起的。通过识别和删除(或替换)这些异常值,可以减少数据的误差,提高分析的准确性。异常值剔除滤波保证了数据的质量和可靠性,为后续的分析、预测和决策提供了更稳健的基础。

本文档使用C语言(运行平台Cortex-M3/M4内核单片机)针对最简单的一维线性信号(例如温度、压力、流量等数据采集)实现了上述三种信号处理方法。为了最大程度上实现示例与教学的效果,三种算法的实现分别基于构造类型结构体、定义缓存数组、动态内存分配等方式,可供读者对比分析或加以修改利用。

卡尔曼滤波

算法简述

卡尔曼滤波 (Kalman Filter)是一个递归的算法,用于从一系列的测量中估计过程的状态,即使测量中含有噪声。它最初被用于航空和宇宙工程,但现在在各种领域都有应用,包括经济学、控制理论和机器人技术。它旨在优化给定的不完美测量和估计状态之间的权重。卡尔曼滤波有很多变种,如扩展卡尔曼滤波、无迹卡尔曼滤波等,适用于非线性和非高斯问题。

卡尔曼滤波是一个迭代过程,用于估计线性动态系统的状态,当存在测量噪声和过程噪声时。它由两个主要步骤组成:预测(或时间更新)和更新(或测量更新)。假设我们有一个系统状态 x x x和测量 z z z,卡尔曼滤波的基本思想是结合预测的状态和通过测量获得的新信息来估计系统的真实状态。

以下是卡尔曼滤波的基本步骤:

  1. 初始化:

    • 估计的初始状态: x ^ 0 \hat{x}_0 x^0
    • 估计的初始状态的协方差: P 0 P_0 P0
  2. 预测 (时间更新):

    • 状态预测:
      x ^ k ∣ k − 1 = A x ^ k − 1 ∣ k − 1 + B u k \hat{x}_{k|k-1} = A \hat{x}_{k-1|k-1} + B u_k x^kk1=Ax^k1∣k1+Buk
    • 协方差预测:
      P k ∣ k − 1 = A P k − 1 ∣ k − 1 A T + Q P_{k|k-1} = A P_{k-1|k-1} A^T + Q Pkk1=APk1∣k1AT+Q
      其中:
      A A A 是状态转移矩阵,
      B B B 是控制输入矩阵,
      u k u_k uk 是控制输入,
      Q Q Q 是过程噪声的协方差矩阵。
  3. 更新 (测量更新):

    • 计算卡尔曼增益:
      K k = P k ∣ k − 1 H T ( H P k ∣ k − 1 H T + R ) − 1 K_k = P_{k|k-1} H^T (H P_{k|k-1} H^T + R)^{-1} Kk=Pkk1HT(HPkk1HT+R)1
    • 状态更新:
      x ^ k ∣ k = x ^ k ∣ k − 1 + K k ( z k − H x ^ k ∣ k − 1 ) \hat{x}_{k|k} = \hat{x}_{k|k-1} + K_k (z_k - H \hat{x}_{k|k-1}) x^kk=x^kk1+Kk(zkHx^kk1)
    • 协方差更新:
      P k ∣ k = ( I − K k H ) P k ∣ k − 1 P_{k|k} = (I - K_k H) P_{k|k-1} Pkk=(IKkH)Pkk1
      其中:
      H H H 是观测矩阵,
      R R R 是测量噪声的协方差矩阵。

其中,预测步骤负责推进状态和协方差到下一时刻。更新步骤则是当新的测量数据可用时,利用这些数据来校正预测,提供了一个更新的估计。

: 这是线性卡尔曼滤波的简化描述,适用于线性系统和高斯噪声。对于非线性系统或非高斯噪声,需要使用扩展卡尔曼滤波 (EKF)、无迹卡尔曼滤波 (UKF) 等变种。

参考源码

  • 头文件
#ifndef __FILTER_H__
#define __FILTER_H__#include "platform.h"#include "FreeRTOS.h"
#include "portable.h"/// @brief 定义滤波算法库中使用的动态内存分配方法
#define FILTER_MALLOC  pvPortMalloc     // 使用FreeRTOS实时系统的内存分配方法
// #define FILTER_MALLOC  malloc        // 使用标准C库的内存分配方法/*** @brief 卡尔曼滤波结构体*      为每个卡尔曼滤波的通道都定义一个卡尔曼滤波结构体变量*/
typedef struct
{/// @brief Q代表预测值的方差,R代表测量值的方差,更小方差对应的量对下次的预测值有更大的影响权重float Q_ProcessNoise;                      // 预测值的方差, Q_ProcessNoise:过程噪声,Q增大,动态响应变快,收敛稳定性变坏float R_MeasureNoise;                      // 测量值的方差, R_MeasureNoise:测量噪声,R增大,动态响应变慢,收敛稳定性变好float estimate_value_init;    // 初始时刻温度的最优估计值float estimate_variance_init; // 初始时刻温度的估计方差float estimate_value_curr;    // 当前温度的最优估计值float estimate_variance_curr; // 当前温度的估计方差float predict_value_next;    // 下一个状态的预测值float predict_variance_next; // 下一个状态的估计方差
} Kalman_Filter_Struct;/*** @brief 卡尔曼滤波初始化,主要是实现卡尔曼滤波结构体变量的成员初始化*      针对温度采集信号等类似问题设计,即初始状态选取为初始测量值、当前时刻的温度估计值为上一时刻的温度。* @param kalman_filter 卡尔曼滤波结构体变量* @param Q_ProcessNoise Q值,过程噪声,预测值的方差* @param R_MeasureNoise R值,测量噪声,测量值的方差* @param init_estimate_value 初始时刻温度的估计值* @param init_estimate_variance 初始时刻温度的估计方差*/
void Kalman_Filter_Init(Kalman_Filter_Struct *kalman_filter, float Q_ProcessNoise, float R_MeasureNoise, float init_estimate_value, float init_estimate_variance);/*** @brief 卡尔曼滤波迭代*      滤波器估计过程中某一个时刻的状态,利用量测更新的值作为反馈。所以卡尔曼滤波过程又可以分为状态跟新和测量更新两个部分来实现。*      状态更新中主要是为了获得下个时刻的先验估计,量测更新则是为了通过先验估计和量测值获取后验估计。* @param kalman_filter 卡尔曼滤波结构体变量* @param measure_value_curr 当前量测值* @return float 卡尔曼滤波值*/
float Kalman_Filter_Iterate(Kalman_Filter_Struct *kalman_filter, float measure_value_curr);#endif
  • 源文件
void Kalman_Filter_Init(Kalman_Filter_Struct *kalman_filter, float Q_ProcessNoise, float R_MeasureNoise, float init_estimate_value, float init_estimate_variance)
{kalman_filter->Q_ProcessNoise = Q_ProcessNoise;kalman_filter->R_MeasureNoise = R_MeasureNoise;kalman_filter->estimate_value_init = init_estimate_value;kalman_filter->estimate_variance_init = init_estimate_variance;kalman_filter->estimate_value_curr = kalman_filter->estimate_value_init;kalman_filter->estimate_variance_curr = kalman_filter->estimate_variance_init;
}float Kalman_Filter_Iterate(Kalman_Filter_Struct *kalman_filter, float measure_value_curr)
{// 利用当前估计值迭代下次预测值kalman_filter->predict_value_next = kalman_filter->estimate_value_curr;kalman_filter->predict_variance_next = kalman_filter->estimate_variance_curr + kalman_filter->Q_ProcessNoise;// 计算卡尔曼增益float k_index = kalman_filter->predict_variance_next / (kalman_filter->predict_variance_next + kalman_filter->R_MeasureNoise);// 利用下次预测值迭代下次估计值// 例如在进行温度预测时,因为温度是一个连续的状态,我们认为上一时刻的温度和当前时刻的温度相等,则有T(k)=T(k-1)kalman_filter->estimate_value_curr = k_index * measure_value_curr + (1 - k_index) * kalman_filter->predict_value_next;kalman_filter->estimate_variance_curr = (1 - k_index) * kalman_filter->predict_variance_next;// 输出下次估计值return kalman_filter->estimate_value_curr;
}

调用示例

  • 初始化滤波器(通过声明卡尔曼结构体变量的方式)

定义卡尔曼滤波结构体变量

Kalman_Filter_Struct kalman_filter_pt1 =
{.Q_ProcessNoise = 0.001,.R_MeasureNoise = 0.1,.estimate_value_init = 25,.estimate_variance_init = 1,.estimate_value_curr = 25,.estimate_variance_curr = 1,
};

初始化卡尔曼滤波器

	Kalman_Filter_Init(&kalman_filter_pt1, 0.001, 2, 25, 1);
  • 使用滤波器
	float pt1_KMfilter = Kalman_Filter_Iterate(&kalman_filter_pt1, SenPt100Array[0].CalibValue);

滑动平均滤波

算法简述

滑动平均滤波 (Moving Average Filter)是一种简单的滤波方法,其核心思想是用一组最近的数据点的算术平均值来估计当前的值。这种方法的优点是简单且实现起来容易。它能有效地减少随机噪声。但滑动平均滤波对于处理"尖峰值"或突发性的数据变化可能不是非常有效,因为它可能会对数据产生滞后。

下面是滑动平均滤波的基本实现方法:

  1. 选择窗口大小:首先,选择一个窗口大小 N N N,这决定了用于计算平均值的连续数据点的数量。例如,对于一个大小为3的窗口,你会使用当前点及其前两个数据点来计算平均值。

  2. 计算平均值:对于数据中的每个点(从第 N N N个点开始,因为前 N − 1 N-1 N1个点没有足够的前数据点来计算滑动平均),计算该点及其前 N − 1 N-1 N1个点的算术平均值。

假设数据序列为 x 1 , x 2 , x 3 , … , x i , … x_1, x_2, x_3, \dots, x_i, \dots x1,x2,x3,,xi,,其对应的滑动平均序列为 m a 1 , m a 2 , m a 3 , … , m a i , … ma_1, ma_2, ma_3, \dots, ma_i, \dots ma1,ma2,ma3,,mai,,对于每个点 x i x_i xi(其中 i ≥ N i \geq N iN),其滑动平均值计算如下:

m a i = x i − N + 1 + x i − N + 2 + ⋯ + x i N ma_i = \frac{x_{i-N+1} + x_{i-N+2} + \dots + x_i}{N} mai=NxiN+1+xiN+2++xi

  1. 滑动窗口:每次新的数据点进入窗口时,最旧的数据点离开窗口。然后再次计算新窗口中数据的平均值。

注意:

  • 选择不同的窗口大小 N N N会影响结果。较大的 N N N会导致更平滑的数据,但可能引入更大的滞后;较小的 N N N会减少滞后,但结果可能较为嘈杂。
  • 有许多滑动平均滤波的变种,如加权移动平均,其中数据点的权重不同,使得某些点在平均值中的影响更大。
  • 滑动平均滤波非常简单且计算量小,适用于实时系统和资源受限的环境。

简单的Python实现示例:

def moving_average(data, N=3):return [sum(data[i-N:i]) / N for i in range(N, len(data)+1)]

参考源码

  • 头文件
#ifndef __FILTER_H__
#define __FILTER_H__#include "platform.h"#include "FreeRTOS.h"
#include "portable.h"/// @brief 定义滤波算法库中使用的动态内存分配方法
#define FILTER_MALLOC  pvPortMalloc     // 使用FreeRTOS实时系统的内存分配方法
// #define FILTER_MALLOC  malloc        // 使用标准C库的内存分配方法/// 由于滑动平均滤波算法采用了静态数组空间作为工作内存,因此需要提前预设好最大数组通道数与数组容量。
/// 为了保证算法库的封装性,一般在本源码的外部(项目业务代码中)定义下述变量(FILTER_MA_QueueNum, FILTER_MA_QueueSize),
/// 并将下述指针(MAFilter_DataQueue_P, MAFilter_Flag_IsDataQueueFull_P, MAFilter_Flag_DataCount_P, MAFilter_Sum_P)
/// 赋值为在本源码的外部(项目业务代码中)定义的数组地址。
extern uint8_t const FILTER_MA_QueueNum; // 滑动平均滤波 最大通道数
extern uint16_t const FILTER_MA_QueueSize; // 滑动平均滤波 最大队列深度extern int32_t *const MAFilter_DataQueue_P; // 滑动平均滤波 数据队列二维数组 e.g. MAFilter_DataQueue[FILTER_MA_QUEUENUM][FILTER_MA_QUEUESIZE]
extern uint8_t *const MAFilter_Flag_IsDataQueueFull_P;  // 滑动平均滤波 队列满标志位数组
extern uint8_t *const MAFilter_Flag_DataCount_P;    // 滑动平均滤波 队列当前容量标志数组
extern int64_t *const MAFilter_Sum_P;       // 滑动平均滤波 用于存储int32_t类型的队列数据累加/*** @brief 滑动平均滤波功能初始化,将数据缓存清空置零* */
void Filter_MovingAverage_Init(void);/*** @brief 按照固定的队列深度进行滑动平均滤波(输入原始数据,输出滤波后的数据)* @param _data 输入的数据* @param channel 输入的通道* @return int32_t 输出滤波后的数据*/
int32_t Filter_MovingAverage(int32_t _data, uint8_t channel);/*** @brief 按照可变的队列深度进行滑动平均滤波(输入原始数据,输出滤波后的数据)* @param _data 输入的数据* @param channel 输入的通道* @param size 滤波队列的深度* @return int32_t 输出滤波后的数据*/
int32_t Filter_MovingAverage_VariantSize(int32_t _data, uint8_t channel, uint8_t size);#endif
  • 源文件
void Filter_MovingAverage_Init()
{uint8_t i, j;for (i = 0; i < FILTER_MA_QueueNum; i++){MAFilter_Flag_IsDataQueueFull_P[i] = 0;MAFilter_Flag_DataCount_P[i] = 0;for (j = 0; j < FILTER_MA_QueueSize; j++){
//			MAFilter_DataQueue_P[i][j] = 0;MAFilter_DataQueue_P[i*FILTER_MA_QueueSize+j] = 0;}}
}int32_t Filter_MovingAverage(int32_t _data, uint8_t channel)
{int16_t sumNum;MAFilter_Sum_P[channel] = 0;//	MAFilter_DataQueue_P[channel][MAFilter_Flag_DataCount_P[channel]] = _data;MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+MAFilter_Flag_DataCount_P[channel]] = _data;MAFilter_Flag_DataCount_P[channel]++;if (MAFilter_Flag_DataCount_P[channel] == FILTER_MA_QueueSize){MAFilter_Flag_DataCount_P[channel] = 0;MAFilter_Flag_IsDataQueueFull_P[channel] = 1;}if (!MAFilter_Flag_IsDataQueueFull_P[channel]){sumNum = MAFilter_Flag_DataCount_P[channel];}else{sumNum = FILTER_MA_QueueSize;}for (uint8_t i = 0; i < sumNum; i++){
//		MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}return (int32_t) (MAFilter_Sum_P[channel] / sumNum);
}int32_t Filter_MovingAverage_VariantSize(int32_t _data, uint8_t channel, uint8_t size)
{int16_t sumNum;MAFilter_Sum_P[channel] = 0;//	MAFilter_DataQueue_P[channel][MAFilter_Flag_DataCount_P[channel]] = _data;MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+MAFilter_Flag_DataCount_P[channel]] = _data;MAFilter_Flag_DataCount_P[channel]++;if (MAFilter_Flag_DataCount_P[channel] == FILTER_MA_QueueSize){MAFilter_Flag_DataCount_P[channel] = 0;MAFilter_Flag_IsDataQueueFull_P[channel] = 1;}if (!MAFilter_Flag_IsDataQueueFull_P[channel]){sumNum = MAFilter_Flag_DataCount_P[channel];}else{sumNum = FILTER_MA_QueueSize;}
/Variant Size///data queue is not full and data num is less than variant size//sumNum <= sizeif (size >= sumNum){for (uint8_t i = 0; i < sumNum; i++){
//			Filter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}}//sumNum > sizeelse{//data queue is not full//size < sumNum < QueueSizeif (!MAFilter_Flag_IsDataQueueFull_P[channel]){for (uint8_t i = sumNum - size; i < sumNum; i++){
//				MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}}//data queue is full//sumNum = QueueSizeelse{if (MAFilter_Flag_DataCount_P[channel] >= size){for (uint8_t i = MAFilter_Flag_DataCount_P[channel] - size; i < MAFilter_Flag_DataCount_P[channel]; i++){
//					MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}}else{for (uint8_t i = sumNum - (size - MAFilter_Flag_DataCount_P[channel]); i < FILTER_MA_QueueSize; i++){
//				    MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}for (uint8_t i = 0; i < MAFilter_Flag_DataCount_P[channel]; i++){
//				    MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel][i];MAFilter_Sum_P[channel] += MAFilter_DataQueue_P[channel*FILTER_MA_QueueSize+i];}}}sumNum = size;}
//return (int32_t) (MAFilter_Sum_P[channel] / sumNum);
}

调用示例

  • 初始化滤波器(通过定义缓存数组的方式)

滤波器用户文件-头文件

#ifndef _FILTER_USER_H_
#define _FILTER_USER_H_#include "filter.h"#define FILTER_MA_QUEUENUM 				4
#define FILTER_MA_QUEUESIZE 			50#endif

滤波器用户文件-源文件

#include "filter_user.h"uint8_t const FILTER_MA_QueueNum = FILTER_MA_QUEUENUM;
uint16_t const FILTER_MA_QueueSize = FILTER_MA_QUEUESIZE;static int32_t MAFilter_DataQueue[FILTER_MA_QUEUENUM][FILTER_MA_QUEUESIZE] = { 0 };
static uint8_t MAFilter_Flag_IsDataQueueFull[FILTER_MA_QUEUENUM] = { 0 };
static uint8_t MAFilter_Flag_DataCount[FILTER_MA_QUEUENUM] = { 0 };
static int32_t MAFilter_Sum[FILTER_MA_QUEUENUM] = { 0 };int32_t *const MAFilter_DataQueue_P = (int32_t *)MAFilter_DataQueue;
uint8_t *const MAFilter_Flag_IsDataQueueFull_P = MAFilter_Flag_IsDataQueueFull;
uint8_t *const MAFilter_Flag_DataCount_P = MAFilter_Flag_DataCount;
int32_t *const MAFilter_Sum_P = MAFilter_Sum;

初始化滤波器

    Filter_MovingAverage_Init();
  • 使用滤波器
float Temp1_filter = Filter_MovingAverage(Temp1 * 10.0, 2) / 10.0;     
float BatVol_filter = Filter_MovingAverage_VariantSize(BatVol * 10.0, 3, 50) / 10.0;

异常值剔除滤波

算法简述

异常值剔除滤波(Outlier Reject Filter)是一种基于统计方法的滤波技术。其核心思想是先识别出数据中的异常值或噪声,然后将其剔除或替换。通常,这种方法涉及到设定一个阈值。任何超过这个阈值的数据都被视为异常值。异常值剔除滤波的一种常见的实现方法是剔除数据集中的最大和最小值。当结合滑动平均时,这种方法可以进一步平滑数据,同时减少由于极端值导致的不必要的波动。

以下是该方法的详细实现步骤:

  1. 选择窗口大小:首先,确定一个窗口大小 N N N。这决定了用于计算平均值的连续数据点的数量。

  2. 选择剔除数量:确定要从每个窗口中剔除的最大值和最小值的数量,称之为 M M M。这意味着,从每个窗口中,你将剔除 M M M个最大值和 M M M个最小值。

  3. 滑动窗口 & 剔除异常值:对于数据中的每个窗口(从第 N N N个点开始),做以下操作:

    • 从窗口中找出 M M M个最大值和 M M M个最小值。
    • 从窗口中剔除这些极端值。
    • 计算窗口中剩余数据点的平均值。
  4. 滑动窗口:每次新的数据点进入窗口时,最旧的数据点离开窗口。然后再次执行上述步骤。

简单的Python实现示例:

def outlier_removal_moving_average(data, N=5, M=1):# 初始化一个空列表存放滤波后的结果filtered_data = []for i in range(N-1, len(data)):window = data[i-N+1:i+1]  # 获取窗口数据# 剔除M个最大值和M个最小值for _ in range(M):window.remove(max(window))window.remove(min(window))# 计算平均值并加入到滤波后的数据列表中filtered_data.append(sum(window) / len(window))return filtered_data

此函数使用窗口大小 (N) 和剔除数量 (M) 来对数据进行滤波。这是一个简单的实现,可能需要根据实际数据和应用进行一些调整和优化。

参考源码

  • 头文件
#ifndef __FILTER_H__
#define __FILTER_H__#include "platform.h"#include "FreeRTOS.h"
#include "portable.h"/// @brief 定义滤波算法库中使用的动态内存分配方法
#define FILTER_MALLOC  pvPortMalloc     // 使用FreeRTOS实时系统的内存分配方法
// #define FILTER_MALLOC  malloc        // 使用标准C库的内存分配方法/*** @brief 异常剔除滤波功能初始化,分配数据缓存空间* @param channel_size 输入数据队列深度* @param channel_num 输入通道数量* @return int 内存分配结果*  @arg 0 内存分配成功*  @arg -1 内存分配失败*/
int Filter_OutlierReject_Init(uint16_t channel_size, uint16_t channel_num);/*** @brief 异常剔除滤波(输入原始数据,输出滤波后的数据)*      构造FIFO数据队列,往队列中输入数据。将队列中的数据排序,取中间部分的数据计算均值输出。*      如此可以剔除队列中最大、最小的一个或几个异常数据,并将剩余数据进行了滑动平均处理。* @param data 输入的数据* @param channel 输入的通道* @param avg_size 计算均值的数据长度* @return int32_t 输出滤波后的数据*/
int32_t Filter_OutlierReject(int32_t _data, uint16_t channel, uint16_t avg_size);#endif
  • 源文件

static int32_t *Filter_OutlierReject_Buffer = NULL;		// 异常值剔除滤波 数据缓存指针
static int32_t *Filter_OutlierReject_SortBuffer = NULL; // 异常值剔除滤波 数据排序缓存指针
static uint8_t *Filter_OutlierReject_IsDataQueueFull = NULL; // 异常值剔除滤波 队列满标志位指针
static uint8_t *Filter_OutlierReject_DataCount = NULL;	// 异常值剔除滤波 队列数据量标志位指针
static int16_t Filter_OutlierReject_BufferSize = 0;		// 异常值剔除滤波 队列深度
static int16_t Filter_OutlierReject_BufferChannel = 0;	// 异常值剔除滤波 队列数int Filter_OutlierReject_Init(uint16_t channel_size, uint16_t channel_num)
{Filter_OutlierReject_Buffer = (int32_t *)FILTER_MALLOC(sizeof(int32_t)*channel_size*channel_num);Filter_OutlierReject_SortBuffer = (int32_t *)FILTER_MALLOC(sizeof(int32_t)*channel_size);Filter_OutlierReject_IsDataQueueFull = (uint8_t *)FILTER_MALLOC(sizeof(uint8_t)*channel_num);Filter_OutlierReject_DataCount = (uint8_t *)FILTER_MALLOC(sizeof(uint8_t)*channel_num);Filter_OutlierReject_BufferSize = channel_size;Filter_OutlierReject_BufferChannel = channel_num;// 内存是否分配成功if(Filter_OutlierReject_Buffer == NULL || Filter_OutlierReject_IsDataQueueFull == NULL || Filter_OutlierReject_DataCount == NULL || Filter_OutlierReject_SortBuffer == NULL){return -1;}// 初始化为0memset(Filter_OutlierReject_Buffer, 0, sizeof(int32_t)*channel_size*channel_num);memset(Filter_OutlierReject_SortBuffer, 0, sizeof(int32_t)*channel_size);memset(Filter_OutlierReject_IsDataQueueFull, 0, sizeof(uint8_t)*channel_num);memset(Filter_OutlierReject_DataCount, 0, sizeof(uint8_t)*channel_num);return 0;
}int32_t Filter_OutlierReject(int32_t _data, uint16_t channel, uint16_t avg_size)
{int16_t sumNum = 0;int32_t dataTemp_sort = 0;int64_t dataTemp_sum = 0;		// 用于存储int32类型的数值累加int32_t dataReturn = 0;// 内存是否分配成功if(Filter_OutlierReject_Buffer == NULL || Filter_OutlierReject_IsDataQueueFull == NULL || Filter_OutlierReject_DataCount == NULL || Filter_OutlierReject_SortBuffer == NULL){return _data;}Filter_OutlierReject_Buffer[channel*Filter_OutlierReject_BufferSize+Filter_OutlierReject_DataCount[channel]] = _data;Filter_OutlierReject_DataCount[channel]++;if (Filter_OutlierReject_DataCount[channel] == Filter_OutlierReject_BufferSize){Filter_OutlierReject_DataCount[channel] = 0;Filter_OutlierReject_IsDataQueueFull[channel] = 1;}if (!Filter_OutlierReject_IsDataQueueFull[channel]){sumNum = Filter_OutlierReject_DataCount[channel];}else{sumNum = Filter_OutlierReject_BufferSize;}// 将数据拷贝到排序缓存中进行排序,以免影响原FIFO队列memcpy(Filter_OutlierReject_SortBuffer, Filter_OutlierReject_Buffer + channel*Filter_OutlierReject_BufferSize, sizeof(int32_t)*sumNum);// Sort the data arrayfor(uint16_t i = 0; i < sumNum - 1; i++){for(uint16_t j = 0; j < sumNum-1-i; j++){if(Filter_OutlierReject_SortBuffer[j] > Filter_OutlierReject_SortBuffer[j+1]){dataTemp_sort = Filter_OutlierReject_SortBuffer[j];Filter_OutlierReject_SortBuffer[j] = Filter_OutlierReject_SortBuffer[j+1];Filter_OutlierReject_SortBuffer[j+1] = dataTemp_sort;}}}// return the median of the data arrayif(avg_size >= sumNum || avg_size <= 1){dataReturn = Filter_OutlierReject_SortBuffer[sumNum/2];}else{for(uint16_t i = sumNum/2 - avg_size/2; i < sumNum/2 + avg_size/2; i++){dataTemp_sum += Filter_OutlierReject_SortBuffer[i];}dataReturn = dataTemp_sum/((avg_size/2)*2);}return dataReturn;
}

调用示例

  • 初始化滤波器(通过动态分配内存的方式)
	/*** @brief 异常剔除滤波算法,队列长度为10,队列通道为10* 		10个通道依次为:电阻测量通道*4,温度测量通道*4,温湿度传感器通道*2*/int rest = Filter_OutlierReject_Init(10, 10);
  • 使用滤波器

下述示例中演示了如何在仅支持int32_t类型数据输入的滤波器中实现浮点数保留指定位数的小数。

	//通道1-电阻值数据InputReg_SetData_Float(8, Filter_OutlierReject(SenResMeasArray[0].CalibValue * 1000, 0, 6) / 1000.0);//通道2-电阻值数据InputReg_SetData_Float(9, Filter_OutlierReject(SenResMeasArray[1].CalibValue * 1000, 1, 6) / 1000.0);//通道3-电阻值数据InputReg_SetData_Float(10, Filter_OutlierReject(SenResMeasArray[2].CalibValue * 1000, 2, 6) / 1000.0);//通道4-电阻值数据InputReg_SetData_Float(11, Filter_OutlierReject(SenResMeasArray[3].CalibValue * 1000, 3, 6) / 1000.0);

相关文章:

常见信号滤波方法(卡尔曼滤波、滑动平均、异常值剔除)的原理解析与C语言实现

常见信号滤波方法&#xff08;卡尔曼滤波、滑动平均、异常值剔除&#xff09;的原理解析与C语言实现 日期作者版本备注2023.09.04Dog TaoV1.0完成文档的初始版本。 文章目录 常见信号滤波方法&#xff08;卡尔曼滤波、滑动平均、异常值剔除&#xff09;的原理解析与C语言实现前…...

WebGL模型矩阵

前言&#xff1a;依赖矩阵库 WebGL矩阵变换库_山楂树の的博客-CSDN博客 先平移&#xff0c;后旋转的模型变换&#xff1a; 1.将三角形沿着X轴平移一段距离。 2.在此基础上&#xff0c;旋转三角形。 先写下第1条&#xff08;平移操作&#xff09;中的坐标方程式。 等式1&am…...

Flutter:WebSocket封装-实现心跳、重连机制

前言Permalink Flutter简介 Flutter 是 Google推出并开源的移动应用开发框架&#xff0c;主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App&#xff0c;一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口&#xff0c;开发者可以很快地为 F…...

c语言中:struct timespec

在C语言中&#xff0c;struct timespec 是一个结构体&#xff0c;通常用于处理时间和时间间隔。这个结构体通常包含以下两个成员&#xff1a; tv_sec&#xff1a;这是一个长整型&#xff08;long&#xff09;&#xff0c;用于存储秒数。它表示时间的整数部分&#xff0c;即秒数…...

Mendix如何实现导出文件

刚刚接触Mendix低代码两周&#xff0c;花了一周在b站看初级视频然后考完初级&#xff0c;第二周开始做个列表查询感觉照葫芦画瓢没啥难度。但最近要求写个导出列表数据&#xff0c;在mendix社区翻了翻&#xff0c;这个功能算是常见的。找了mendix官方提供的Docs磕磕盼盼才实现了…...

在IIS服务器上安装SSL证书(2023配置启用HTTPS部署教程)内容来源SSL市场网

https://www.sslmarket.com.cn/146.html...

如何处理ChatGPT与用户之间的互动和反馈?

处理ChatGPT与用户之间的互动和反馈是关于改进和优化用户体验的关键方面。这涉及到在聊天、对话和交互中建立积极的用户关系&#xff0c;同时利用用户的反馈来不断改进ChatGPT的性能和功能。本文将探讨如何有效地处理ChatGPT与用户之间的互动和反馈&#xff0c;以提供更好的用户…...

微服务-gateway鉴权

文章目录 一、前言二、gateway鉴权1、依赖配置2、编写代码3、GlobalFilter详解3.1、GlobalFilter简介3.2、GlobalFilter自定义执行顺序3.2.1、实现Order接口实现自定义执行顺序 一、前言 网关是介于客户端和服务器端之间的中间层&#xff0c;所有的外部请求都会先经过 网关这一…...

NET7快速开发一个商品管理模块-商品列表开发(一)

商品管理模块&#xff0c;一般包含以下几个模块&#xff1a; 商品列表&#xff1a;这里可以看到所有已发布的商品信息列表。 商品管理&#xff1a;添加商品、编辑商品以及删除商品。 具体功能如下图&#xff1a; 1.商品列表 2.添加商品 3.商品SKU编辑...

0829|C++day7 auto、lambda、C++数据类型转换、C++标准模板库(STL)、list、文件操作

一、思维导图 二、【试编程】将实例化类对象写入容器后&#xff0c;写入.txt文本中&#xff0c;再重新定义一个类容器&#xff0c;将.txt中的内容读取出来&#xff0c;输出到终端 封装一个学生的类&#xff0c;定义一个学生这样类的vector容器, 里面存放学生对象&#xff08;至…...

SpringBoot连接MySQL数据库实例【tk.mybatis连接mysql数据库】

文章目录 一、数据库表二、引入依赖三、修改配置文件四、公共组件1、BaseController2、BaseService3、IService4、BaseMapper 五、代码1、Application2、Student实体类3、Controller4、Service5、ServiceImpl6、Mapper7、Mapper.xml 一、数据库表 CREATE TABLE student (id i…...

node基础之三:http 模块

// 1. 导入模块 const http require("http"); // 2. 创建服务 const server http.createServer((request, response) > {// 获取请求方法request.method;// 获取请求 url&#xff08;只包含url中的路径和查询字符串&#xff09;request.url;// 获取 HTTP 协议版…...

【高阶数据结构】AVL树 {概念及实现;节点的定义;插入并调整平衡因子;旋转操作:左单旋,右单旋,左右双旋,右左双旋;AVL树的验证及性能分析}

AVL树 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明…...

Netty—FuturePromise

Netty—Future&Promise 一、JDK原生 Future二、Netty包下的 Future三、Promise1、使用Promise同步获取结果2、使用Promise异步获取结果.3、使用Promise同步获取异常 - sync & get4、使用Promise同步获取异常 - await5、使用Promise异步获取异常 在异步处理时&#xff0…...

固定资产卡片乱怎么管理

固定资产卡片是记录公司固定资产信息的重要工具&#xff0c;如果管理不善&#xff0c;容易造成卡片混乱、数据错误等问题。 为了避免这种情况的发生&#xff0c;可以采取以下措施&#xff1a;  建立完善的资产管理制度&#xff0c;明确固定资产的分类、标准和使用情况&#x…...

AutoHotkey(AHK)脚本,自动截图当前屏幕并发送给微信窗口

前提先安装好 AutoHotkey &#xff0c;本脚本依赖AutoHotkey 环境 首先 &#xff0c;设置微信的快捷键 执行代码如下&#xff1a; Loop {SendInput, {Alt down}s{Alt up}Sleep, 2000 ; 等待2秒; 双击鼠标左键Click, 2Sleep, 1000 ; 等待1秒SendInput, {Alt down}a{Alt up}Sl…...

Golang - go build打包文件

Go编译打包文件 1、简单打包 程序 main1.go&#xff1a; package mainimport "fmt"func main() {fmt.Println("Hello World!") } 打包&#xff1a; # 在linux服务上执行下面的3个命令 # linux平台,生成main1可执行程序 CGO_ENABLED0 GOOSlinux GOARCHam…...

Java的归并排序

不爱生姜不吃醋⭐️⭐️⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 &#x1f334;前言&#x1f334;一.归并排序1.概念2.时间复杂度3.代码实现 &#x1f334;二、小和问题1.概念2.举例3.代码实现 &#x1f334…...

B. The Walkway Codeforces Round 893 (Div. 2)

Problem - B - Codeforces 题目大意&#xff1a;小明在数轴上要从1走到n&#xff0c;其中某些坐标上有一些饼干店&#xff0c;共m个&#xff0c;小明身上也有无限多的饼干&#xff0c;它首先一定会在1的位置吃一个饼干&#xff0c;在每个饼干店的位置会吃一个&#xff0c;在前…...

第四篇 DirectShow 采集调用结构关系

第一篇: DirectShow视频采集_会头痛的可达鸭的博客-CSDN博客 一、GraphBuilder 1、IFilterGraph2、IGraphBuilder、ICaptureGraphBuiler2 (1)、CLSID IFilterGraph CLSID_FilterGraphIFilterGraph2 CLSID_CaptureGraphBuilderIGraphBuilder CL…...

2605. 从两个数字数组里生成最小数字

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;枚举比较法方法二&#xff1a;集合的位运算表示法 写在最后 Tag 【贪心】【位运算】【数组】 题目来源 2605. 从两个数字数组里生成最小数字 题目解读 给定两个各自只包含数字 1 到 9 的两个数组&#xff0c;每个数组…...

服务器发送事件Server-sent events详解与示例

Server-sent events 服务端进行数据推送除了WebSocket之外&#xff0c;还可以使用Server-Send-Event方案。 与 WebSocket不同的是&#xff0c;服务器发送事件是单向的。数据消息只能从服务端到发送到客户端&#xff08;如用户的浏览器&#xff09;。这使其成为不需要从客户端…...

SOLIDWORKS 多实体的建模方式

SOLIDWORKS多实体是SOLIDWORKS中一个非常有用的功能。在SOLIDWORKS中&#xff0c;对于模型的设定通常被大家所熟知的有以下几种类型&#xff1a;零件、装配体以及工程图。 其实还有一种划分&#xff0c;就是多实体。严格意义上来说&#xff0c;多实体既不属于零件也不属于装配体…...

NSSCTF web 刷题记录1

文章目录 前言题目[GXYCTF 2019]禁止套娃方法一方法二 [NCTF 2019]Fake XML cookbook[NSSRound#7 Team]ec_RCE[NCTF 2018]Flask PLUS 前言 今天是2023.9.3&#xff0c;大二开学前的最后一天。老实说ctf的功力还是不太够做的题目太少&#xff0c;新学期新气象。不可急于求成&am…...

遥感指数数据库

目前遥感指数多种多样&#xff0c;那怎么针对不同的应用领域选择合适的植被指数&#xff1f;不同卫星又有哪些可以反演的指数&#xff1f; Henrich等人开发了Index Database(网址&#xff1a;https://www.indexdatabase.de/)&#xff0c;总结了目前主流的遥感指数&#xff0c;…...

如何让insert程序速度快,可以试试联合SQL(insert 和 select 一起使用)?

查询添加可选择SQL执行&#xff0c;速度远超程序执行 insert 和 select案例 insert into 表1(列1,列2,列3,...) select 列1,列2,列3,...from表2(GROUP BY 列)116511 条数据 耗时45秒&#xff0c; 如果是程序查询然后再insert&#xff0c;则需要30分钟左右&#xff01;&#x…...

IP地址、网关、网络/主机号、子网掩码关系

一、IP地址 IP地址组成 IP地址分为两个部分&#xff1a;网络号和主机号 &#xff08;1&#xff09;网络号:标识网段&#xff0c;保证相互连接的两个网段具有不同的标识。 &#xff08;2&#xff09;主机号:标识主机&#xff0c;同一网段内&#xff0c;主机之间具有相同的网…...

使用skvideo.io.vread读取avi视频,报错“No way to determine width or height from video...”

问题描述&#xff1a; 一开始安装sk-video&#xff0c;在使用skvideo.io.vread读取avi视频&#xff0c;报错“No way to determine width or height from video. Need -s in inputdict. Consult documentation on I/O.” 解决方案&#xff1a; 1. 卸载sk-video pip uninsta…...

Nomad 系列-安装

系列文章 Nomad 系列文章 Nomad 简介 开新坑&#xff01;近期算是把自己的家庭实验室环境初步搞好了&#xff0c;终于可以开始进入正题研究了。 首先开始的是 HashiCorp Nomad 系列&#xff0c;欢迎阅读。 关于 Nomad 的简介&#xff0c;之前在 大规模 IoT 边缘容器集群管…...

网络版五子棋C++实现

目录 1.项目介绍 2.开发环境 3.核心技术 4.环境搭建 5.WebSocketpp介绍 5.1WebSocketpp是什么 5.2为什么使用WebSocketpp 5.3原理解析&#xff1a; 5.4WebSocketpp主要特性 6.WebSocketpp使用 7.JsonCpp使用 8.MySQL API 9.项目模块设计以及流程图 10.封装日志宏…...