Search

顯示具有 Algorithm 標籤的文章。 顯示所有文章
顯示具有 Algorithm 標籤的文章。 顯示所有文章

2019年11月9日 星期六

Atrial Fibrillation Detection Blood Pressure Monitor Oscillometric Method


This project is electronic blood pressure monitor research platform with Oscillometric method, include SBP and DBP estimate algorithm, Atrial Fibrillation detection algorithm, calibration curve, GUI tool for fine-tune BP algorithm...etc, ofcourse, this is research use only.














2019年9月29日 星期日

wxECGAnalyzer - cross platform ECG signal process tool

Detection of abnormal rhythm morphologies is more difficult than normal beat, therefore, we need to collect abnormal rhythm signals in clinical practice  to improve the detection of QRS-complex.

This project is for Electrocardiogram(ECG) signal algorithms design and validation, include preprocessing, QRS-Complex detection, embedded system validation, ECG segmentation, label your machine learning dataset, and clinical trial...etc.

For algorithm performance, in ANSI/AAMI EC38,it is required that the detected QRS shall in the 150ms range of the signed point from annotation by human exper.

目前僅用MIT-BIH等標準心律不整資料庫或是生理訊號挑戰賽提供的標註資料,理論上很難得到超越其標注的學習模型,實務上還是需要配合其他臨床實驗搜集更多案例強化模型,下個To-Do會用標準資料庫訓練分類模型增加基本的自動化標註,目前僅針對臨床實時運行的特殊案例配合人工選擇QRS-Complex演算法自動切片。






2018年12月20日 星期四

Pulse Oximeter with MAX3010X

在同一波長下針對Hb和HbO2的吸收(extinction coefficient or absorption coefficient)有差異,而如果要求SPo2量測兩個不同波長的吸收就可以求解一個未知數。

因為光對於動脈微血管血液的變化(隨著心率)會有交流訊號,而其他的組織(吸收散射反射)則是直流成分,因此將兩波長反射量變化正規化後的結果作為R-Ration(SPo2為非線性),利用經驗公式A, B係數可得一SPo2曲線,例如660nm/940nm = 1, SPo2 = 85%,例如A - B = 110 - 25 斜率。
 具體實現只要動態的將直流成分除去,並且將兩波長光直流水平控制在一範圍(SPo2都有類似PGA的功能),得到的兩波長交流訊號RMS比值即可推算SPo2。





以下Github包含STM32F4 Driver for MAX30100(移植Reference 1.範例),和MAC OS X上的C++ wxWidgets GUI(左下角顯示SPo2)可透過VCP與STM32F4通訊,當然除了此方法使用更多的波長可以使SPo2求解聯立方程更加準確,並且實務上會做更高精度的SPo2曲線的建立以及motion artifact除去等。



Download : 


Demo : 

Reference : 
1. https://morf.lv/implementing-pulse-oximeter-using-max30100 
2. http://www.ti.com/lit/an/slaa274b/slaa274b.pdf 
3. http://www.ti.com/lit/ug/tidu542/tidu542.pdf 
4. http://sam-koblenski.blogspot.com/2015/11/everyday-dsp-for-programmers-dc-and.html



2018年10月4日 星期四

Continuous Non Invasive Blood Pressure Research Platform (ECG and PPG) Pulse Arrival Time Based

This project is cuffless cNIBP research platform with ECG(two-electrode) and PPG

In cuffless non-invasive blood pressure monitor field, we with the accurate calibration of PAT to BP, beat-to-beat BP can be estimated from PAT. On the basis of the theoretical relationship between PAT and BP and their experimental or empirical relationship, various models that correlate PAT with BP have been established.

PAT and BP with Regression:


Pulse transit time (PTT) is the time it takes for the pressure or flow wave to propagate between two arterial sites. PTT measured as the time delay between invasive proximal and distal blood pressure or flow, and PTT(propagate time) can be converted to Pulse Wave Velocity(PWV is speed unit).
the most used is the R-wave(QRS Complex) in the electrocardiogram (ECG) and combine pulsemeter(piezo or PPG), leading to the so called pulse arrival time (PAT). PAT is not exactly the PTT, as it includes the time interval between ventricular depolarization and the opening of the aortic valve which is known as pre-ejection period (PEP) and it varies beat-to-beat.

Most efforts have employed the time delay between ECG and finger photoplethysmography (PPG) waveforms as a convenient surrogate of PTT.

However, these conventional pulse arrival time (PAT) measurements include the pre-ejection period (PEP) and the time delay through small, muscular arteries and may thus be an unreliable marker of BP.

Shortcoming is that PAT includes the pre-ejection period (PEP) in addition to PTT. Since the PEP component depends on the electromechanical functioning of the heart, it can change independently of PTT and thus BP.

Because the ECG QRS-Complex is not the starting point for blood to actually enter the radial artery, it is a biopotential to depolarization of the sinus node


This project only for research




Reference : 
[1] Continuous Blood Pressure Measurement from Invasive to Unobtrusive: Celebration of 200th Birth Anniversary of Carl Ludwig
[2] Cuff-Less and Continuous Blood Pressure Monitoring: A Methodological Review






2016年1月7日 星期四

Digital Holography - Distance Meter


Grating製作與原理

sensitive to energy(exposure)= intensity * time = |Uo(x,y)|^2 * t = |a(x,y)|^2

z0 = film
object wave Uo(x,y) = |a(x,y)|*e^j fi(x,y)

reference wave Ur(x,y) = |A(x,y)|*e^j fi(x,y)

Exposure E(x,y) = I(x,y)*t = |Uo(x,y) + Ur(x,y)|^2 * t

tA(x) = BetaE^(x)
= Beta * t * I(x) 
= Beta * t * |A|^2 + Beta*t{|a(x)|^2 + A_star(x)a(x) + A(x)a_star(x)}
=t_beta + Beta’*{|a(x)|^2 + a|A||a(x) [fi_r-fi_o]|}

當重建時的調變元素

Reconstruct of object wave Illumination wave Up(x)照Hologram film重建,穿過去的光
Transmitted wave Ut(x)
= Up(x)*tA(x)
= Up(x)*t{|a(x)|^2 + A_star(x)a(x) + A(x)a_star(x)}
= A(x)*{t_beta + beta’*[|a(x)|^2 + A_star(x)a(x) + A(x)a_star(x)]}
= U1(x)+U2(x)+U3(x)+U4(x)
{
U1(x) = A(x)*t_beta = directing transmitted A(x)
U2(x) = beta’*|a(x)|^2*A(x) = Modulated A(x)
U3(x) = beta’*|A|^2*a(x) = original object wave(primary image,virtual image)
U4(x) = beta’*|A|^2*e^j fi_r(x,y)*a(x)^x = conjugate image,real image
}

由不同角度的共軛光去重建
U4(x) = beta’*|A|^2*a(x)^x,conjugate image a(x)^2 real image會出現在virtual image方向,real image看起來會跟virtual image相反,例如凹凸前後相反看起來不合日常邏輯。

Fourier Transform 待補

Fresnel Equation 待補


由三角原理求距離



其中在此範例 theta i = 0,grating light pair = 1/125*1/2mm,波長 = 532nm,CCD Pixel size = 1.75um,theta A = 4.5mm = 雷射通過光圈,O0~O1|O-1 = 中間強度干涉分佈最強與旁邊兩點的距離任取一。





2015年9月21日 星期一

DCMotor PID Speed Controller

    最近實驗需要弄一個簡易的有機溶液攪拌的裝置,就是在馬達轉子有兩個磁鐵並在溶液中放入小的鐵條,馬達轉動時就會帶動鐵條旋轉就能攪拌溶液了,只不過我這邊需要控制轉速否則太快可能破壞有機分子的三維結構。

    實作上用霍爾IC去感測馬達轉速,並且將結果回授到PID控制器,再由PID計算出PWM大小去控制,這邊我PWM OUTPUT直接接上L293D的ENABLE腳位即可,或者接上電晶體的控制Base Pin也可以。




code:


Reference:

2013年1月24日 星期四

PID Controller

  去年暑假無意間看到有paper用基因演算法去自動調整PID控制參數,感覺上非常實用也很有趣不過工作很忙一直沒時間研究,這幾天又想趁有空閒來實現那個paper,又突然看到Arduino PID Library的作者的Blog解說,發現跟一般普通理想化的PID公式不同,因此讀完了以下參考前八篇(Arduino PID Library)以及第九篇(補充說明),跟著每一篇實作code與推導公式收益良多!

PID.h


#ifndef __PID__
#define __PID__

const bool DIRECT = true;
const bool REVERSE = false;

const bool ON = true;
const bool OFF = false;

class PID
{
   public:
      PID(double*,double*,double*,double,double,double,unsigned long,bool,double,double,double);
      void Computing();
      void SetTunings(double,double,double);
      void SetSampleTime(unsigned long);
      void SetOutputLimits(double,double);
      void SetOnOff(bool);
      void SetDirection(bool);

   private:
      double Kp,Ki,Kd;
      double *input,*output;
      double *setpoint;

      double integral;
      double last_input;

      double min,max;

      unsigned long last_time;
      unsigned long sample_time;

      bool on_off;
      bool direction;
};

#endif


PID.cpp


#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#include "PID.h"

PID::PID(double *_input,double *_output,double *_setpoint,
      double _Kp,double _Ki,double _Kd,
      unsigned long _sample_time = 100000,
      bool _on_off = OFF,double _direction = DIRECT,
      double _min = 0,double _max = 255)
{
   input = _input;
   output = _output;
   setpoint = _setpoint;

   sample_time = _sample_time;

   on_off = _on_off;

   SetOutputLimits(_min,_max);

   SetDirection(_direction);

   SetTunings(_Kp,_Ki,_Kd);

   last_time = (micros() - sample_time);
}

void PID::Computing()
{
   if(on_off == OFF){
      return ;
   }

   unsigned long now = micros();

   if((now - last_time) >= sample_time){
      double error = (*setpoint - *input);

      integral += (Ki * error);
      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }

      double diff_input = (*input - last_input);

      *output = (Kp * error) + integral - (Kd * diff_input);

      if(*output > max){
*output = max;
      }
      else if(*output < min){
*output = min;
      }

      last_input = *input;
      last_time = now;
   }
}

void PID::SetTunings(double _Kp,double _Ki,double _Kd)
{
   if((_Kp < 0) || (_Ki < 0) || (_Kd < 0)){
      return ;
   }

   double SampleTimeInSec = ((double)sample_time) / 1000000.0f;

   Kp = _Kp;
   Ki = _Ki * SampleTimeInSec;
   Kd = _Kd / SampleTimeInSec;

   if(direction != REVERSE){
      Kp = (0 - Kp);
      Ki = (0 - Ki);
      Kd = (0 - Kd);
   }
}

void PID::SetSampleTime(unsigned long _sample_time)
{
   if(_sample_time > 0){
      double ratio = ((double)_sample_time / (double)sample_time);

      Ki *= ratio;
      Kd /= ratio;

      sample_time = _sample_time;
   }
}

void PID::SetOutputLimits(double _min,double _max)
{
   if(_min > _max){
      return ;
   }

   min = _min;
   max = _max;

   if(on_off == ON){
      if(*output > max){
*output = max;
      }
      else if(*output < min){
*output = min;
      }

      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }
   }
}

void PID::SetOnOff(bool _on_off)
{
   bool new_state = (_on_off == ON);

   if(new_state == (!on_off)){
      integral = *output;
      last_input = *input;

      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }
   }

   on_off = new_state;
}

void PID::SetDirection(bool _direction)
{
  if((on_off == ON) && (_direction != direction)){
      Kp = (0 - Kp);
      Ki = (0 - Ki);
      Kd = (0 - Kd);
   }

   direction = _direction;
}




參考:

  第一篇是基本的PID公式,第二篇將採樣時間固定而消去幾次除法運算(Kalman Filter等等的微積分時間也可以這樣簡化計算),第三篇說明如何做Anti-Derivative Kick,第四篇說明如何消去中途修改PID參數的落差,第五篇說明如何做Anti-Reset Windup,第六篇其實就是說中途中止計算要記得直接跳出不要更改狀態,第七篇接續前一篇如果要恢復記得要回到上次計算的狀態,第八篇說明計算結果方向(正負號),例如Input大於Setpoint就增加Output(正向),或者Input小於Setpoint就增加Output(反向),第九篇算是補充說明積分項計算時間點的差異。

  1. Improving the Beginner’s PID – Introduction
  2. Improving the Beginner’s PID – Sample Time
  3. Improving the Beginner’s PID – Derivative Kick
  4. Improving the Beginner’s PID: Tuning Changes
  5. Improving the Beginner’s PID: Reset Windup
  6. Improving the Beginner’s PID: On/Off
  7. Improving the Beginner’s PID: Initialization
  8. Improving the Beginner’s PID: Direction
  9. PID: When Should I Compute the Integral Term?

2012年12月7日 星期五

Kalman Filter

    基本上所有參數跟成員的意思跟Simple Kalman Filter都差不多(參考預測與更新七個步驟),初始化r_measure是觀測誤差q_angle與q_bias是在加速度計與陀螺儀的預測選擇較相信誰arg_angle是初始角度,而member function 的 Update沒有空行的code表示是同一組算式,第一組計算當前陀螺儀的角速度減去前次計算出的偏移,接著將角度加上這個差的角度,第二組P是一個2x2 Matrix,內容是推導後的結果用來計算預測估計誤差,第三組量測現在加速度計角度與上次角度的差,第四組計算預測與觀測的Covariance,第五組計算最佳卡爾曼增益,第六組由卡爾曼增益乘上前次與這次角度差決定角度與偏移要更新多少,第七組則是用卡爾曼增益乘上當前預測估計誤差然後更新下個預測估計誤差,這七個步驟的前兩個是計算預測,後五個才是真正的更新,順序都與上面的wiki參考預測與更新七個步驟一樣,然後這整個系統的觀測時間統計是個隱馬爾可夫模型


Acc只有加速度計,CF用互補濾波加上陀螺儀修正,KF用卡爾曼濾波加上陀螺儀修正。


Kalman Filter:


#ifndef __KALMAN_FILTER__
#define __KALMAN_FILTER__

class KalmanFilter
{
  public:

  KalmanFilter(double r_measure,double q_angle,double q_bias,double arg_angle)
  {
    Q_angle = q_angle;
    Q_bias = q_bias;
    R_measure = r_measure;
    angle = arg_angle;
 
    bias = 0;
    P[0][0] = 0;
    P[0][1] = 0;
    P[1][0] = 0;
    P[1][1] = 0;
  }

  /* Accelerometer Angle , Gyro Rate , dt */
  double Update(double acc,double gyro,double dt)
  {
    rate = gyro - bias;
    angle += rate * dt;
 
    P[0][0] += dt * (dt * P[1][1] - P[0][1] - P[1][0] + Q_angle);
    P[0][1] -= dt * P[1][1];
    P[1][0] -= dt * P[1][1];
    P[1][1] += Q_bias * dt;
 
    angle_diff = acc - angle;
 
    S = P[0][0] + R_measure;
 
    K[0] = P[0][0] / S;
    K[1] = P[1][0] / S;
 
    angle += K[0] * angle_diff;
    bias += K[1] * angle_diff;
 
    P[0][0] -= K[0] * P[0][0];
    P[0][1] -= K[0] * P[0][1];
    P[1][0] -= K[1] * P[0][0];
    P[1][1] -= K[1] * P[0][1];
 
    return angle;
  }

  private:

  double R_measure;
  double Q_angle;
  double Q_bias;

  double angle;
  double bias;
  double rate;

  double angle_diff;
  double S;
  double P[2][2];
  double K[2];
};

#endif


Used:

    下面初始參數適用幾乎所有的IMU除非有特別要自己設定。


KalmanFilter KFx(0.03f,0.001f,0.003f,0.0f);
KalmanFilter KFy(0.03f,0.001f,0.003f,0.0f);
unsigned long last_KF_computing_time;     //us





void loop()
{
  double KF_x_angle = KFx.Update(AccAngleX,GyroRateYaw,
  (double)(micros() - last_KF_computing_time) / 1000000.0f);

  double KF_y_angle = KFx.Update(AccAngleY,GyroRatePitch,
  (double)(micros() - last_KF_computing_time) / 1000000.0f);

  last_KF_computing_time = micros();
}

2012年12月6日 星期四

Complementary Filter

    互補濾波是另一種比較簡單取得平滑的採樣值的方法,計算起來比卡爾曼濾波簡單,要將兩個感測器的值互補只要對一方做高通濾波(陀螺儀瞬時動態較靈敏所以高頻的值較有效)一方做低通濾波(加速度計長時間靜態的值較準確所以低頻的值較有效)然後相加,但兩者乘上的係數相加要等於一,這篇文章有比較詳細的說明我是參考他的,將陀螺儀的角速度乘上時間微分dt然後連續積分就可以得到該時間累積的角度,再與加速度計的角度做互補濾波即可,另外WMP與Wii Nunchuk使用延伸模式時對I2C操作跟單純使用單一個不一樣(參考Activated Wii Motion Plus in Nunchuck passthrough mode),使用這個模式兩者好像會有3ms左右的採樣延遲(MultiWii Source Code - IMU.ino內的computeIMU注解說的,我沒計算過驗證)。

方向


Acc只有加速度計Noise突出很多,AccGyro有用陀螺儀做互補所以較平滑。


Complementary Filter.ino


#include <Wire.h>

uint8_t buffer[6];
int xID;

double AccAngleX = 0,AccAngleY = 0;

int GyroLastYaw = 0,GyroLastPitch = 0,GyroLastRoll = 0;
double GyroAngleYaw = 0,GyroAnglePitch = 0,GyroAngleRoll = 0;

double HighPass = 0.93f,LowPass = 0.07f;
double AngleX = 0,AngleY = 0;

unsigned long last_loop_time;       //ms
unsigned long last_gyro_computing_time;  //us

void setup()
{
  Serial.begin(115200);
  Wire.begin();
  Init6DOF();
  last_loop_time = millis();
  last_gyro_computing_time = micros();
}

void loop()
{
  if(millis() > (last_loop_time + 10)){
    IICRead(buffer,0x52,6);
 
    if((buffer[5] & 0x03) == 0x00){
      ComputingNunchuk(buffer);
    }
    else if((buffer[5] & 0x03) == 0x02){
      ComputingWMP(buffer);
    }
 
    ComplementaryFilter();
 
    IICWrite(0x52,0x00);
    last_loop_time = millis();
  }
}

void Init6DOF()
{
  delay(100);

  IICWrite(0x53,0xFE,0x05);
  Serial.println("Passthrough Mode Ok!");
  delay(100);

  IICWrite(0x53,0xF0,0x55);
  Serial.println("Init WMP Ok!");
  delay(100);

  IICWrite(0x52,0xFA);
  Serial.println("Set Reading Address 0xFA Ok!");
  delay(100);

  IICRead(buffer,0x52,6);
  xID = buffer[0] + buffer[1] + buffer[2] +
  buffer[3] + buffer[4] + buffer[5];
  Serial.print("Extension Controller xID = 0x");
  Serial.println(xID,HEX);
  if(xID == 0xCB){
    Serial.println("WMP Connected But Not Avtivared!");
  }
  if(xID == 0xCE){
    Serial.println("WMP Connected And Avtivared!");
  }
  if(xID == 0x00){
    Serial.println("WMP Not Connected!");
  }
  delay(100);

  IICWrite(0x52,0x8);
  Serial.println("Set Reading Address 0x08 Ok!");
  delay(100);

  IICWrite(0x52,0x00);
}

double Map(double value,double Input_Min,double Input_Max,double Output_Min,double Output_Max)
{
  double rValue = (value - Input_Min) * (Output_Max - Output_Min) / (Input_Max - Input_Min) + Output_Min;

  double rMin,rMax;
  if(Output_Min < Output_Max){
    rMin = Output_Min;
    rMax = Output_Max;
  }
  else{
    rMin = Output_Max;
    rMax = Output_Min;
  }
  if(rValue < rMin){
    return rMin;
  }
  if(rValue > rMax){
    return rMax;
  }

  return rValue;
}

void ComputingNunchuk(uint8_t *buf)
{
  int accel_x_axis = (buf[2] << 2) + ((buf[5] >> 3) & 2);
  int accel_y_axis = (buf[3] << 2) + ((buf[5] >> 4) & 2);
  int accel_z_axis = (buf[4] << 2) + ((buf[5] >> 5) & 6);

  int mapX = Map(accel_x_axis,300.0f,700.0f,-90.0f,90.0f);
  int mapY = Map(accel_y_axis,300.0f,700.0f,-90.0f,90.0f);
  int mapZ = Map(accel_z_axis,360.0f,760.0f,-90.0f,90.0f);

  AccAngleX = atan2(mapX,mapZ) / 3.14159 * 180.0f;
  AccAngleY = atan2(mapY,mapZ) / 3.14159 * 180.0f;
  /*
  Serial.print(accel_x_axis);
  Serial.print(" ");
  Serial.print(accel_y_axis);
  Serial.print(" ");
  Serial.println(accel_z_axis);
  */
}

void ComputingWMP(uint8_t *buf)
{
  int yaw = (((buf[5] & 0xFC) << 6) + buf[0]);
  int pitch = (((buf[4] & 0xFC) << 6) + buf[1]);
  int roll = (((buf[3] & 0xFC) << 6) + buf[2]);

  double GyroDiffYaw = (yaw - GyroLastYaw) / 14.375f;
  double GyroDiffPitch = (pitch - GyroLastPitch) / 14.375f;
  double GyroDiffRoll = (roll - GyroLastRoll) / 14.375f;

  GyroAngleYaw  = GyroDiffYaw *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;
  GyroAnglePitch = GyroDiffPitch *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;
  GyroAngleRoll = GyroDiffRoll *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;

  last_gyro_computing_time = micros();

  GyroLastYaw = yaw;
  GyroLastPitch = pitch;
  GyroLastRoll = roll;
  /*
  Serial.print(yaw);
  Serial.print(" ");
  Serial.print(pitch);
  Serial.print(" ");
  Serial.println(roll);
  */
  /*
  Serial.print(GyroAngleYaw);
  Serial.print(" ");
  Serial.print(GyroAnglePitch);
  Serial.print(" ");
  Serial.println(GyroAngleRoll);
  */
}

void ComplementaryFilter()
{
  AngleX = (HighPass * (AngleX + GyroAngleYaw)) + (LowPass * AccAngleX);
  AngleY = (HighPass * (AngleY + GyroAnglePitch)) + (LowPass * AccAngleY);

  Serial.print(AngleX);
  Serial.print("  ");
  Serial.println(AngleY);

}

void IICWrite(uint8_t address,uint8_t register_address)
{
  Wire.beginTransmission(address);
  Wire.write(register_address);
  Wire.endTransmission();
}

void IICWrite(uint8_t address,uint8_t register_address,uint8_t data)
{
  Wire.beginTransmission(address);
  Wire.write(register_address);
  Wire.write(data);
  Wire.endTransmission();
}

void IICRead(uint8_t *buf,uint8_t address,uint8_t length)
{
  Wire.requestFrom(address,length);
  for(int i = 0;Wire.available();++i){
    buf[i] = Wire.read();
  }
}

2012年8月29日 星期三

Simple Kalman Filter

    本來將這篇放到 Arduino - 使用 Wii Nunchuck後面算是針對單個角度值做一維的卡爾曼濾波的簡單應用,不過感覺放在後面有點亂所以還是另外開一篇,下次在寫多維卡爾曼濾波融合加速度計與陀螺儀的應用

   Kalman FIilter的應用最常見的地方在慣性導航系統中,比如四軸飛行器或者火箭以及SEGWAY平衡的PID值預測,OpenCV中也有個Kalman Filter,用來做物體追蹤預測位置或者速度,Kalman FIilter可以從前一個預測結果計算出現在的最佳結果以及下個結果,而Kalman FIilter光是線性與非線性版本就有很多種,維數的少到多與難度從簡單到複雜成正比,簡單的Kalman FIilter可以用來校正加速度計的角度之類的值,或者將陀螺儀的角速度積分後的值與加速度計的角度做Kalman FIilter後取最佳的角度,而簡化後的Simple Kalman Filter只有幾個計算跑在Arduino上也很容易。


Simple Kalman Filter:

    q是預測誤差、r是感測器誤差、k是卡爾曼增益、x用來紀錄狀態值、p直接取q與r的平方和開根號,接著隨著時間更新時,現在的結果會是上一個結果加上上一個結果和現在量測結果的差再乘以增益的值(x += k * (value - x);),而下一個結果預測的偏差值就可以將p乘上1 - k後的值在加上q預測誤差值得到下一個要修正的結果,如此把Covariance遞迴下去就可以一直計算出現在的最佳結果,而且只需要從上一個保存的結果推算。


#include <math.h>

class KalmanFilter
{
   public:

      KalmanFilter(double q,double r);
      double Update(double);
      double GetK(){return k;}

   private:

      double k; //kalman gain
      double p; //estimation error cvariance
      double q; //process noise cvariance
      double r; //measurement noise covariance
      double x; //value
};

KalmanFilter::KalmanFilter(double q,double r):q(q),r(r),x(0.0)
{
  p = sqrt(q * q + r * r);
}

double KalmanFilter::Update(double value)
{
   p += q;
   k = p / (p + r);
   x += k * (value - x);
   p *= (1 - k);

   return x;
}

Used:
 
    假設預測誤差為2,感測器測量誤差為1.5(這兩個值看情況調整)。

KalmanFilter kalman(2.0,1.5);
    然後將每次測量的值用Update更新取得修正結果即可,會發現得到修正的結果會比沒修正的結果變動幅度較為平滑。



void loop()
{
  double Angle = Accelerometer();
  double k_value = kalman.Update(Angle);
}


直接用兩個OpenGL View Port其中各有一個方塊,並將角度值直接帶入World Matrix直接套用矩陣旋轉公式去作矩陣轉換,而兩方塊放在同個畫面可以方便看非使用卡爾曼濾波(螢幕左)以及使用卡爾曼濾波(螢幕右)兩個方塊振動幅度的差別。