Kalman Filter

2 
months ago
  by Valeriy Novytskyy  at ^H Hackerspace/ 14 min
to read

Learn about this widely used signal processing algorithm capable of overcoming measurement noise in real time.

overview

Kalman filter is a “smart” signal processing algorithm that takes advantage of the way the Universe works and even predicts the future to overcome measurement noise and deliver timely, accurate readings.

This algorithm is ubiquitous: it’s used in autonomous navigation systems like Robot Localization in ROS, for Object Tracking in OpenCV, and inside actuators like Dynamixel where measurements from the absolute encoder are fused with measurements from the quadrature encoder to enable precise positioning.

This filter was first used in the Apollo space program and various defense projects in the early 1960s. It was introduced by Rudolf E. Kálmán in 1960 in his seminal paper, A New Approach to Linear Filtering and Prediction Problems, published in the Journal of Basic Engineering.

background

In this article I introduce the Kalman Filter in the shortest way possible with practical examples. When you’re ready to go deeper check out Kalman Filter from the Ground Up by Alex Becker at kalmanfilter.net:

kalman filter from the ground up

why not average?

A basic moving average filter can be implemented as follows:

function output = movingAverageFilter(input, bufferSize)
buffer = ones(bufferSize, 1) * input(1);
output = zeros(length(input), 1);
for sampleIndex = 1:length(input)
bufferIndex = mod(sampleIndex - 1, bufferSize) + 1;
buffer(bufferIndex) = input(sampleIndex);
% Output is average of last bufferSize inputs
output(sampleIndex) = ...
sum(buffer) / bufferSize;
end
end

Compare the filter result to the original data with buffer size 8, 32, and 64:

moving average filter

Filtered data has been shifted forward on the time axis proportionately to the smoothing amount, introducing delay:

  • In the case of DC motor control for robotics, a moving average filter would cause the PID algorithm to oscillate, as it would see this delay as a signal that it’s not working hard enough, and over-compensate.
  • In the case of a vehicle, a moving average would cause the estimated position to lag too far behind, causing the navigation system to suggest changes to the route too late for the driver to respond.

why not low pass?

A basic low-pass filter can be implemented as follows:

function output = lowPassFilter(input, coefficient)
estimate = input(1);
output = zeros(length(input), 1);
for sampleIndex = 1:length(input)
sample = input(sampleIndex);
% Output is input blended with previous estimate
estimate = ...
(1.0 - coefficient) * estimate + ...
coefficient * sample;
output(sampleIndex) = estimate;
end
end

Compare the filter result with 0.1 and 0.001 coefficients to the original data:

low pass filter

This filter missed short temporal spikes in the data. This may be fine if the system is cyclical in nature, but if the subsequent state depends on previous system state, missing events could cause it to diverge far from reality.

In the following video we test both filters on real data:

why predict?

If you know what a moving vehicle or a motor shaft are supposed to do (since you are controlling them in the first place), why should you let measurements have so much influence on where they are estimated to be next?

In the case of a DC motor, commanded velocity can be used to determine the current shaft position:

motor model

% System Identification (5202 Series Yellow Jacket Motor)
MAX_PWM = 65536;
MAX_VELOCITY_RPM = 30;
MAX_VELOCITY = MAX_VELOCITY_RPM * PI * 2 / 60;
PWM_NONLINEARITY = [1.0, 1.023, 1.03, 1.0, 0.98, 1.0];
% System Model
function position = systemModel(...
input, ...
position, ...
timeStep ...
)
% Map PWM to velocity
norm = input / MAX_PWM;
index = round(...
length(PWM_NONLINEARITY) * norm...
);
velocity = ...
norm * PWM_NONLINEARITY[index] * MAX_VELOCITY;
% Predict position
position = position + velocity * timeStep;
end

In the case of a moving vehicle, the pressure on the accelerator pedal can determine the velocity and position:

vehicle model

% System Identification (Chevrolet Trax 2017 4WD)
MAX_ACCELERATION = 2.82;
DRAG_COEFFICIENT = 0.35;
FRONTAL_AREA = 2.5;
AIR_DENSITY = 1.225;
% System Model
function [position, velocity] = systemModel(...
input, ...
position, ...
velocity, ...
timeStep ...
)
% Input pressure on accelerator pedal
acceleration = ...
input * MAX_ACCELERATION
% Subtract drag force
drag = ...
DRAG_COEFFICIENT / 2.0 * ...
AIR_DENSITY * ...
FRONTAL_AREA * ...
velocity^2;
acceleration = acceleration - drag;
% Predict position
position = position + velocity * timeStep;
% Predict velocity
velocity = velocity + acceleration * timeStep;
end

Prediction can be a valuable tool provided the model is detailed enough. The Kalman filter’s key strength lies in its ability to combine measurements with predictions, depending on which offers the most accurate estimate.

A basic Kalman filter algorithm looks something like this:

  1. Start with an initial estimate
  2. Take a measurement
  3. Correct the estimate by the measurement
  4. Predict the next estimate

We will add more detail to these steps in the filter algorithm section later.

variance

Another special thing about the Kalman filter is that its output isn’t simply a value but a likelihood of it being a certain value. This concept is borrowed from statistics where it’s called a random variable.

  • A random variable is defined by a mean and a spread from that mean.
  • The mean is a sum of a set of samples divided by the size of the set. This represents the center of the random variable.
  • The spread from the mean is called variance and it’s denoted by the greek letter σ (sigma).
  • The shape of this spread is a “bell curve” called a Gaussian distribution.

Measuring physical quantities results in readings that follow this Gaussian distribution due to the Holographic Principle of the Universe:

/*

The one-dimensional array of data that makes up the Universe is stretched over a 3D surface with 1 bit of information every 0.724 x 10-64cm2.

*/

When the Universe is “sampled” by taking a measurement, the sample is interpolated by using a strategy discovered by Carl Gauss: surrounding bits are blended in depending on distance of the sample from the “center” of each bit.

variance

Variance can be calculated from a set of samples as follows:

  1. Calculate the mean
  2. For each number in the set, calculate distance from the mean (subtract mean from the number)
  3. Square each distance from the mean, which will give you variance for this item in the set
  4. Calculate the mean of all variances in the set (variances summed over number of items). This will give you the variance of the set.

In Matlab or Octave, mean and variance can be calculated as follows:

set = [1, 2, 3, 4, 5]
>> mean(set)
ans =
3
>> var(set)
ans =
2.5000

The following video demonstrates how to calculate variance:

filter algorithm

Since the filter estimate is a random variable, the algorithm has to keep track of its uncertainty, making the same changes to the estimate uncertainty as the ones made to the estimate mean value.

The measurement is also a random variable and its uncertainty could change each iteration or remain constant.

If we include the additional steps for tracking estimate and measurement uncertainties, the algorithm would look like the following:

  1. Start with initial estimate and its uncertainty
  2. Take a measurement and determine its uncertainty
  3. Correct the estimate by the measurement
  4. Correct the estimate uncertainty by the measurement uncertainty
  5. Predict the next estimate
  6. Predict the next estimate uncertainty

Kalman filter starts with an initial estimate, the uncertainty of this estimate, and an initial state of the system being measured.

Each iteration, the system state is corrected by the last measurement according to the ratio of estimate uncertainty to measurement uncertainty.

The next estimate is then predicted by evaluating the system model with the corrected state:

% Initial estimate
estimate = y0;
% Initial estimate uncertainty
covariance = P0;
% Initial state
state = x0;
% Identity matrix (state x state)
I = eye(length(state));
% Filter
for i = 1:length(inputs)
% Measure
measurement = measurements(i);
% Determine correction amount
gain = ...
(covariance * C') / ...
(C * covariance * C' + measurementVariance);
% Correct state with measurement
state = state + gain * (measurement - estimate);
% Correct estimate uncertainty
covariance = ...
(I - gain * C) * ...
covariance * (I - gain * C)' + ...
gain * measurementVariance * gain';
% Read input
input = inputs(i);
% Update state
state = A * state + B * input;
% Update estimate uncertainty
covariance = A * covariance * A' + noiseCovariance;
% Predict the next estimate
estimate = C * state + D * input;
end

The next few sections will explain the algorithm parameters in more detail.

initial estimate

The initial estimate y0 can come from initial measurement, output of the system model with initial state, or an educated guess.

A more accurate guess will let the algorithm converge on the optimal estimate faster and avoid a “jump” when the filter performs a large correction.

measurement

Each iteration begins by taking a measurement (labeled z in equations) and determining its uncertainty or variance, typically denoted by R.

Measurement variance can be constant or vary based on conditions:

  • Hall effect encoders like AKSIM report the distance of the rotating magnet from the center of the sensor because this affects the accuracy of the readings. This could be used to derive a measurement variance.
  • Complex measurement devices like radar let you estimate the dynamic effects of wind and rain on their signal-to-noise ratio to derive a measurement variance.
  • A basic potentiometer can be sampled in a range of expected conditions to calculate a constant variance (see variance).

kalman gain

To produce optimal estimates, the Kalman filter has to know if the estimates are more or less reliable than measurements.

The ratio of estimate uncertainty to measurement uncertainty is calculated at each iteration. This ratio is called the Kalman gain (typically denoted by K) and used as a weight to correct predictions with measurements.

% K - Kalman gain
% P - estimate uncertainty
% C - measurement matrix
% R - measurement uncertainty
K = (P * C') / (C * P * C' + R);

Similar to other iterative optimization algorithms like Gradient Descent, the Kalman filter has to calculate an error at each iteration, defined as the difference between the prediction and the measurement.

This error is then used to correct the model, with the correction amount determined by the Kalman gain:

  • If the measurements are trusted more, the Kalman gain will be closer to 1 which will correct the model prediction by a greater amount.
  • If the model is trusted more, the Kalman gain will be closer to 0 which will correct the model prediction by a lesser amount.

Since estimates are a function of the model internal state, we can’t correct them directly. Instead we change the model state to get the desired output:

% Correct state with measurement
% x - system state
% K - Kalman gain
% z - measurement
% y - current estimate
x = x + K * (z - y);

After correcting the model state, the estimate uncertainty is updated by blending measurement uncertainty with model uncertainty:

% Correct estimate uncertainty
% P - current estimate uncertainty
% I - identity matrix (state x state)
% K - Kalman gain
% C - measurement matrix
% R - measurement uncertainty
P = ...
(I - K * C) * ...
P * (I - K * C)' + ...
K * R * K';

If the estimate was based more on the measurement, then the estimate uncertainty reflects more of the measurement uncertainty.

Conversely, if the estimate was based more on the model prediction, then the estimate uncertainty reflects more of the model uncertainty.

prediction

The Kalman filter borrows from System Identification theory which states that most systems (heaters, motors, hydraulic pumps, you name it!) can be approximated by a linear model of the following form:

prediction equation y = Cx + Du + e

The system output y depends on system state x and input u.

The relationship between system output and system state is described by C measurement matrix and the relationship between input and immediate output is described by D input contribution.

Differences between the real system and its model are represented by e as disturbance or noise. This is a theoretical term that’s there in the equation to represent “unknowns” and does not appear in the code.

/*

The measurement matrix is often called H in literature, but in this article I use C for consistency with Matlab System Identification Toolbox.

*/

When modeling systems, you’ll find that the input contribution to system output D is almost always 0 because real-world systems are not instantly affected by inputs. Instead, the inputs modify system state which eventually changes the output.

state transition

The following equation is used to update (or transition) the state of a linear system model at each iteration:

transition equation x = Ax + Bu + Ke

This equation involves the following terms:

  • x is the system state being updated. The new state is based on the previous state so this term appears on both sides of the equation. The initial system state is usually denoted by x0.
  • u is the input provided at this iteration.
  • A is the state transition matrix. Multiplying the system state by this matrix will “simulate” the system, advancing it forward by one time step.
  • B is the input matrix or control matrix that represents the weight of input on each system state variable. Multiplying the input by this matrix will change each system state variable by a different amount depending on how it’s affected by the input.
  • e represents the noise or disturbance that affects the system state transition. This is a theoretical term used to model unknowns so it does not appear in the code.
  • K represents the influence of noise on each state variable. Since e is not used in a practical implementation, we don’t use this term either.

covariance

For a system model with a single state variable, the initial estimate and disturbance uncertainties are represented by scalar variances.

For a system with multiple state variables, the initial estimate and disturbance uncertainties are represented by square covariance matrices with the same number of rows and columns as there are state variables.

In a covariance matrix, the diagonal entries encode variances, and the off-diagonal entries encode covariances.

estimate covariance

Estimate uncertainty is typically denoted by P, and the uncertainty of initial estimate is denoted by P0.

For systems with only one state variable both terms are scalar variances, otherwise they are covariance matrices.

A covariance matrix that encodes the estimate uncertainty of a system with two state variables (position and velocity) would look like this:

estimate covariance of 2nd order system

This matrix encodes the following information:

  • The variance of position (how far the estimated position could be from the true position, squared)
  • The variance of velocity (how far the estimated velocity could be from the true velocity, squared)
  • The covariance of position and velocity (the mathematical relationship between position and velocity random variables), with 0 indicating the two variables are not related.

A covariance matrix that encodes the estimate uncertainty of a system with three or more state variables follows the same pattern:

estimate covariance of 3rd order system

There are a few ways to initialize the estimate uncertainty:

  • If the approximate variances of state variables are known, they can be projected on the diagonal of the covariance matrix:

    covariance = diag([ ...
    100, ...
    200, ...
    300 ...
    ]);
    covariance =
    100 0 0
    0 200 0
    0 0 300
  • If the system was identified with Control System Toolbox, the standard deviations of state variables are available in dx0 property. Squaring elements of dx0 to convert standard deviation to variance and projecting them on the diagonal will give you the estimate covariance:

    % ss - identified system
    covariance = diag(ss.dx0.^2);
    5.5288e+13 0 0
    0 1.5450e+19 0
    0 0 2.6517e+21
  • If you have a data set that records system state over time you could use Matlab’s cov function to initialize the estimate covariance:

    % CSV with position, velocity, acceleration
    dataset = readmatrix("states.csv");
    covariance = cov(dataset);
  • If you can express the variance of the system model in terms of tolerance by using it in a sentence like “this estimate is within x or +/- x”, then square half the tolerance amount to convert it to variance.

    The model variance can then be back-projected through the measurement matrix to determine the variance of the initial estimate:

    modelTolerance = 50
    modelVariance = (modelTolerance / 2)^2;
    covariance = diag( ...
    inv(C) * ...
    eye(length(state)) * modelVariance ...
    * inv(C)' ...
    );

    Multiplying variance by another quantity requires squaring that quantity. To make this work with matrices, the variance is pre-multiplied by the matrix and post-multiplied by the transpose of the same matrix. This is why you’ll often see the pattern X * y * X'.

  • Lastly, you could assume the same variance for all initial state variables, leaving covariances blank:

    variance = 200;
    covariance = eye(length(state)) * variance
    covariance =
    200 0 0
    0 200 0
    0 0 200

These initialization strategies work because the Kalman filter will eventually converge on a more accurate estimate covariance.

However, significantly over-estimating initial estimate uncertainty will cause the algorithm to ignore model predictions while significantly over-estimating the measurement variance will cause the algorithm to ignore measurements.

/*

Recall that Kalman filter works by calculating a ratio between the two uncertainties, so if either one is many orders of magnitude greater than the other, it will break the filter.

*/

noise covariance

A process noise or disturbance covariance matrix usually denoted by Q looks exactly like estimate covariance, but it encodes the variance of Gaussian noise affecting the change made to each state variable within a single iteration.

/*

Noise or disturbance is the difference between the system model and the real system. Its covariance represents the likelihood of state variables being affected by random occurrences like external forces, noise, or the system state being updated using imperfect (noisy) inputs.

*/

There are a few ways to initialize the Q covariance matrix:

  • Similarly to estimate covariance, the approximate variance of noise or disturbance affecting all state variables in the same way could be projected by spreading the same variance across the diagonal:

    noiseVariance = 200
    noiseCovariance = eye(length(state)) * noiseVariance
  • If the approximate variance of noise introduced by updating the system state is known, it could be projected by using the state transition matrix:

    noiseVariance = 200;
    noiseCovariance = A * noiseVariance * A'

    For systems identified with System Identification Toolbox, the noise variance is available in NoiseVariance property of the model:

    % ss1 - linear system model
    noiseCovariance = eye(length(state)) * ss1.NoiseVariance
  • If the approximate variance of noise introduced by the system input is known, it could be projected by using the control matrix:

    inputNoiseVariance = 200;
    noiseCovariance = B * inputNoiseVariance * B'
  • If the approximate variances of noise or disturbances affecting each state variable are known, they could be used to fill in diagonal entries of the covariance matrix, leaving off-diagonal entries blank:

    noiseCovariance = diag([ ...
    positionNoiseVariance, ...
    velocityNoiseVariance, ...
    accelerationNoiseVariance ...
    ])
  • If the system was identified with Control System Toolbox, the covar function will output its disturbance covariance:

    noiseVariance = 200;
    [ ...
    % Initial estimate variance
    P, ...
    % Disturbance covariance
    Q ...
    ] = covar(ss, noiseVariance)
  • Finally, you could simulate your system model with the same data used to identify it, and calculate the difference between the original measurements and the system model at each iteration.

    Since the system model includes a measurement matrix which maps system state to system output, you could multiply the difference between the original measurement and the model prediction by the inverse of this matrix to calculate the noise affecting each state variable at each iteration.

    With these results recorded in a separate vector for each state variable, you could then use cov to generate a disturbance matrix.

covariance transition

Since Kalman filter estimates are random variables represented by mean and variance, anything that we do to the mean of the estimate must also be reflected in its uncertainty.

Earlier we corrected the estimate uncertainty by the same amount (K) used to correct the estimate:

% P - estimate uncertainty
% R - measurement uncertainty
% I - identity matrix (state x state)
% K - Kalman gain
% C - measurement matrix
P = ...
(I - K * C) * P * (I - K * C)' + ...
K * R * K';

In this step, we transition the estimate uncertainty by the same amount (A) used to transition the estimate:

% P - estimate uncertainty
% A - state transition matrix
% Q - state transition noise/disturbance matrix
P = A * P * A' + Q;

Recall that A is the state transition matrix that represents the change made to the system state at each iteration.

The noise covariance matrix Q represents how much uncertainty is added at each iteration by simulating the system based on an imperfect model and using noisy inputs to change the system state.

system identification

The system model can be derived by analyzing the system (as we did in the overly simplified examples of a vehicle and PWM-controlled motor in why predict? section earlier), or by using system identification.

/*

A system identification algorithm tries to guess the system model given system input and output tracked over time.

*/

In the following video we’ll identify a system in Matlab Control System Toolbox using a discrete linear state-space model:

Identifying a system refers to finding A, B, C, D coefficients that make the output of the linear model most closely resemble the original measurements. These coefficients can then be used as parameters to the Kalman filter.

The best way to compare the identified systems to original measurements is by viewing the Model Output in System Identification app:

system identification

This will simulate any identified models and display their outputs alongside the original measurements:

system output

The properties of a system identified by Control System Toolbox can be extracted by using an object property notation, for example:

% Get initial state of system "ss1"
ss1.x0

We will use the following properties in this article:

  • A, B, C, D are the coefficients described earlier.
  • x0 is the initial system state.
  • dx0 is the uncertainty of initial state.
  • NoiseVariance is the variance of Gaussian noise affecting the system.

For more background on system identification, try this series of tutorials assembled by two professors at Carnegie Mellon university.

simulating systems

The quickest way to simulate a linear system is by using lsim in Matlab:

% Linear discrete system ss
startTime = 0;
endTime = 8;
timeStep = ss.Ts;
initialState = ss.x0;
% Generate equally spaced time samples
time = startTime:timeStep:endTime;
% Simulate
lsim(ss, inputs, time, initialState);

Calling lsim without storing results in a variable will display a plot while assigning to a variable will store the simulated outputs in that variable.

simulation plot

It’s useful to understand how linear system equations presented earlier are used by lsim under the hood to simulate the system.

See the complete Matlab and C++ examples demonstrating how to simulate a linear system in systemid companion repository.

demonstration

Live Kalman filter demonstration that lets you load a file and tweak parameters:

measurement
estimate
estimate uncertainty
gain
Dataset
timereadingPWM
0.0199952430
0.04001682430
0.06002182510
0.07999672440
0.100011228-111
0.120012228-111
0.139993241-111
0.160019232-111
0.180016220-111
0.20001213-111
0.220012214-159
0.240088233-159
0.260076234-159
0.280015234-159
0.300022234-159
0.32002258-207
0.340016273-207
0.360013249-207
0.380027255-207
0.400015268-207
0.420026255-255
0.440058242-255
0.459996235-255
0.480028237-255
0.500021245-255
0.520013233-255
0.540016217-207
0.559992220-207
0.58001230-207
0.60008230-207
0.620022222-207
0.640024217-159
0.659993236-159
0.680031218-159
0.700009211-159
0.720011227-159
0.740012207-159
0.760001186-111
0.779996213-111
0.8226-111
0.820014221-111
0.84001209-111
0.860022186-87
0.880019186-87
0.900019194-87
0.920004182-87
0.940023177-87
0.960017191-87
0.9800831710
1.000021650
1.020011880
1.040011930
1.060021830
1.080011720
1.11660
1.120011560
1.140021500
1.160021500
1.180011550
1.200011660
1.220011740
1.240021660
1.260081570
1.280011540
1.300011620
1.320021690
1.340011680
1.361550
1.380031580
1.399991820
1.419991990
1.439991990
1.460031940
1.481900
1.5193-255
1.52002198-255
1.54002212-255
1.56002189-255
1.58168-255
1.6180-255
1.62001173-255
1.63999168-255
1.66001169-255
1.68001153-255
1.7161-255
1.72001161255
1.74002180255
1.76001164255
1.78002164255
1.80004168255
1.81999160255
1.84002166255
1.86002162255
1.88002157255
1.90001144255
1.92007161255
1.94162255
1.95999136255
1.97999136255
2.00001136255
2.02002136255
2.04001140255
2.06001155255
2.08001161255
2.10002150255
2.12002144255
2.14001144255
2.16009141255
2.18001165255
2.20002175255
2.22159255
2.24002154159
2.26147159
2.28003147159
2.3146159
2.32002146159
2.34002146159
2.36001146159
2.37999153159
2.40001152159
2.42002190159
2.44001204159
2.46209159
2.48002225159
2.5202159
2.52002194159
2.54001204159
2.56001212159
2.58002212159
2.60002195159
2.62207159
2.64003229159
2.66215159
2.68002237159
2.70002271159
2.72002258159
2.74002254-159
2.76259-159
2.78320-159
2.80001316-159
2.82002273-159
2.84315-159
2.86001315-159
2.88002306-159
2.90002270-159
2.92002262-159
2.94001262-159
2.95999253-159
2.98002247-159
3.000022510
3.020022560
3.040022620
3.060022880
3.080013000
3.099992630
3.120012460
3.140032580
3.159992580
3.180012510
3.22270
3.222210
3.240042260
3.26222255
3.28002214255
3.3230255
3.32002236255
3.3401234255
3.36249255
3.38253255
3.40001258255
3.42258255
3.44003268255
3.46002262255
3.48001280255
3.50002272255
3.52002251-255
3.54001317-255
3.56002321-255
3.58258-255
3.60002269-255
3.62002274-255
3.64250-255
3.66256-255
3.68002262-255
3.70002259-255
3.72002259-255
3.74001246-255
3.76236-255
3.78005248-255
3.80001282-255
3.82275-255
3.84002263-255
3.86001273-255
3.88261-255
3.90001266-255
3.92001257-255
3.94003268-255
3.96001296-255
3.98261-255
4230-255
4.02230-255
4.04240159
4.06001267159
4.08279159
4.10002251159
4.12001233159
4.14001233159
4.16227159
4.18001241159
4.20002230159
4.22266159
4.24002282159
4.26241159
4.28002238159
4.30001238111
4.32001226111
4.34002228111
4.36002243111
4.38001243111
4.4241111
4.42002241111
4.44001223111
4.46001219111
4.48008210111
4.50001201111
4.52008207111
4.54001226111
4.56002209111
4.58209111
4.60007199111
4.62002199111
4.64002215111
4.66002243111
4.68002232111
4.70002212111
4.72001216111
4.74001231111
4.76008218111
4.78002209111
4.80001220111
4.82001231111
4.84001232111
4.86002252111
4.88011252111
4.90001257111
4.92004260111
4.94003251111
4.96001224111
4.98002233111
5.00002257111
5.02003282111
5.04001270111
5.06002233111
5.07999217111
5.10001237-111
5.12225-111
5.14002238-111
5.16001238-111
5.18002305-111
5.20002281-111
5.22009253-111
5.24002269-111
5.26264-111
5.28267-111
5.3276-111
5.32002288111
5.34002272111
5.36002262111
5.38004278111
5.40005259111
5.42002259111
5.44295111
5.46001300111
5.48003294111
5.50002308111
5.52002292111
5.54001320111
5.56001321111
5.58313111
5.60002309111
5.62002311111
5.64002321111
5.66001312111
5.68001338111
5.7001329111
5.72002329111
5.74001320111
5.76347111
5.78003333111
5.8325111
5.82001317111
5.840013480
5.860013640
5.880013480
5.900013640
5.920023570
5.940023410
5.960013410
5.980033410
6.000023410
6.020013280
6.040023120
6.060013100
6.080013100
6.100033100
6.123100
6.140023420
6.160033690
6.180013600
6.200013350
6.223360
6.240014040
6.263970
6.283600
6.300023600
6.320024000
6.340033770
6.360013330
6.380023620
6.43620
6.420023330
6.440023670
6.460033610
6.480093210
6.53210
6.519993460
6.540013500
6.560023180
6.580013180
6.600023190
6.620013230
6.640033070
6.660013230
6.680013490
6.700023380
6.720023150
6.739993070
6.763120
6.780023130
6.800013200
6.819993300
6.840013130
6.862830
6.880022830
6.900082870
6.920032870
6.940033060
6.959993200
6.980023010
7.000023140
7.020023210
7.040023060
7.060023140
7.080123290
7.100043170
7.120013000
7.140012780
7.160092810
7.180022810
7.200023300
7.220013210
7.239992990
7.260013050
7.280022890
7.300012850
7.320022760
7.342780
7.362970
7.380022810
7.42580
7.420012920
7.443120
7.460023120
7.480012930
7.52820
7.520082760
7.540012750
7.560032740
7.580012740
7.600022950
7.623170
7.640022900
7.659992610
7.680012750
7.700022870
7.720012950
7.740022950
7.762860
7.780012840
7.800022900
7.820022900
7.840022940
7.863070
7.880023130
7.900012890
7.920012920
7.940013090
7.960013050
7.980033000
8.000023120
8.020023120
8.040023170
8.060023110
8.079993100
8.100023100
8.123090
8.140023080
8.16013080
8.292713140
8.312773140
8.332792950
8.352812800
8.372792830
8.39282890
8.412793130
8.432793340
8.45283280
8.472783010
8.492862830
8.512772830
8.532892830
8.55292830
8.572892830
Parameters

Enter filter parameters:

A:
State transition matrix
B:
Control/input matrix
C:
Measurement matrix
D:
Input contribution to immediate output
P0:
Initial estimate uncertainty matrix
Q:
State transition noise/disturbance matrix
R:
Measurement uncertainty
x0:
Initial state vector

kalman in matlab

The kalman companion repository includes a complete filter implementation in addition to two live notebooks that explain the filter algorithm parameters.

The constantAcceleration.mlx notebook implements a Kalman filter that works with a simpler Newtonian motion model:

constant acceleration notebook

The linearSystemModel.mlx notebook implements a Kalman filter that works with a general-form linear discrete system model:

constant acceleration notebook

Finally, kalman.m demonstrates a Kalman filter with the same linear system model in the form of a .m script:

% Constants
global A;
global B;
global C;
global D;
global Q;
global R;
global I;
% A weights (3x3 matrix)
A = [ ...
1.0005, -0.0050, 0.0001;
0.0061, 0.9881, -0.0684;
-0.0009, 0.0768, 0.9224;
];
% B weights (3x1 vector)
B = [ ...
8.7913e-10;
1.0489e-07;
-2.4853e-05;
];
% C weights (1x3 vector)
C = [ ...
-5.2908e+03, ...
13.0803, ...
-0.6389 ...
];
% D weight (scalar)
D = 0;
% Initial state (3x1 vector)
x0 = [ ...
-0.0461;
-0.0198;
0.0098;
];
% Initial state standard deviation (3x1 vector)
dx0 = [ ...
7.4356e+06;
3.9306e+09;
5.1495e+10;
];
% State identity matrix
I = eye(length(x0));
% Noise variance (scalar)
NoiseVariance = 1.539e-7;
% Noise covariance (3x3 matrix)
Q = I * NoiseVariance;
% Measurement variance
R = 3.4556e+03;
% Initial covariance
P0 = diag(dx0.^2);
% Read input
csv = readmatrix('https://raw.githubusercontent.com/01binary/kalman/main/input.csv');
time = csv(:,1);
measurements = csv(:,2);
inputs = csv(:,3);
% Initialize
state = x0;
covariance = P0;
gain = I * 0;
prediction = 100;
outputs = zeros(length(inputs), 1);
gains = zeros(length(inputs), 1);
% Filter
for i = 1:length(inputs)
% Read input
input = inputs(i);
% Take measurement
measurement = measurements(i);
% Correct state
[state, covariance, gain] = kalmanFilter( ...
prediction, ...
measurement, ...
state, ...
covariance, ...
gain ...
);
% Predict and update state
[state, prediction] = systemModel( ...
state, ...
input ...
);
% Output
gains(i) = sum(gain);
outputs(i) = prediction;
end
function [x, y] = systemModel(x, u)
global A;
global B;
global C;
global D;
global Q;
% Predict
% y = Cx + Du
y = ...
% Contribution of state
C * x + ...
% Contribution of input
D * u;
% Update state
% x = Ax + Bu
x = ...
% Contribution of previous state
A * x + ...
% Contribution of input
B * u;
end
function [x, P, K] = kalmanFilter(y, z, x, P, K)
global A;
global C;
global Q;
global R;
global I;
% Update covariance
P = A * P * A' + Q;
% Optimize gain
K = (P * C') / (C * P * C' + R);
% Correct state with measurement
x = x + K * (z - y);
% Correct covariance
P = (I - K * C) * P * (I - K * C)' + ...
K * R * K';
end

kalman in c++

The kalman companion repository includes the following example. Steps that can be used to compile, run, and debug the code are in the readme file:

//
// Includes
//
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <random>
#include <limits>
#include <Eigen/Dense>
//
// Namespaces
//
using namespace std;
using namespace Eigen;
//
// Constants
//
// A weights (3x3 matrix)
const MatrixXd A
{
{ 0.998800, 0.05193, -0.02261 },
{ 0.0222200, -0.01976, 0.7353 },
{ 0.0009856, -0.20930, -0.5957 }
};
// B weights (3x1 vector)
const VectorXd B {{
-0.00000266,
0.0000572747,
-0.0001872152
}};
// C weights (1x3 vector)
const RowVectorXd C {{
-5316.903919,
24.867656,
105.92416
}};
// D weight (scalar)
const double D = 0;
// Initial state (3x1 vector)
const VectorXd x0 {{
-0.0458,
0.0099,
-0.0139
}};
// State standard deviation (3x1 vector)
const VectorXd dx0 {{
7.4356e+06,
3.9306e+09,
5.1495e+10
}};
// State identity (3x3 matrix)
const Matrix3d I = Matrix3d::Identity();
// Noise variance (scalar)
const double NoiseVariance = 1.539e-7;
// Noise covariance (3x3 matrix)
const MatrixXd Q = I * NoiseVariance;
// Measurement variance
const double R = 3.4556e+03;
// Initial covariance
const MatrixXd P0 {{
{ pow(dx0(0, 0), 2), 0.0, 0.0 },
{ 0.0, pow(dx0(1, 0), 2), 0.0 },
{ 0.0, 0.0, pow(dx0(2, 0), 2) }
}};
//
// Functions
//
double systemModel(
VectorXd& x, // state
double u) // input
{
// Predict
// y = Cx + Du
double y =
// Contribution of state
C.dot(x) +
// Contribution of input
D * u;
// Update state
// x = Ax + Bu
x =
// Contribution of previous state
A * x +
// Contribution of input
B * u;
return y;
}
double kalmanFilter(
double y, // prediction
double z, // measurement
VectorXd& x, // state
MatrixXd& P, // covariance
VectorXd& K) // gain
{
// Update covariance
P = A * P * A.transpose() + Q;
// Optimize gain
K = (
(P * C.transpose()) /
(C * P * C.transpose() + R)
);
// Correct state with measurement
x = x + K * (z - y);
// Correct covariance
P = (I - K * C) * P *
(I - K * C).transpose() +
K * R * K.transpose();
return y;
}
//
// Forward Declarations
//
bool openInput(const string& path, ifstream& file);
bool openOutput(const string& path, ofstream& file);
bool read(
ifstream& file,
double& time,
double& measurement,
double& input);
//
// Entry point
//
int main(int argc, char** argv)
{
ifstream inputFile;
if (!openInput("input.csv", inputFile))
{
cerr << "Failed to open input file" << endl;
return 1;
}
ofstream outputFile;
if (!openOutput("output.csv", outputFile))
{
cerr << "Failed to open output file" << endl;
return 1;
}
// Initialize
VectorXd state = x0;
VectorXd gain(3);
MatrixXd covariance = P0;
double time, measurement, input;
double prediction = 0.0;
// Filter
while(read(inputFile, time, measurement, input))
{
// Correct state with measurement
double estimate = kalmanFilter(
prediction,
measurement,
state,
covariance,
gain
);
// Predict and update state
prediction = systemModel(
state,
input
);
// Output
outputFile
<< time << ","
<< estimate << ","
<< measurement
<< endl;
}
return 0;
}
//
// Utilities
//
bool openOutput(
const string& path, ofstream& file)
{
// Delete if exists
remove(path.c_str());
// Open file for writing
file.open(path);
if (!file.is_open()) return false;
// Write headers
file << "time,estimate,measurement" << endl;
return true;
}
bool openInput(
const string& path, ifstream& file)
{
// Open file for reading
file.open(path);
if (!file.is_open()) return false;
// Skip headers
string headers;
getline(file, headers);
return true;
}
bool read(
ifstream& file,
double& time,
double& measurement,
double& input)
{
static string line;
getline(file, line);
sscanf(
line.c_str(),
"%lf, %lf, %lf",
&time,
&measurement,
&input);
return !file.eof();
}

sensor fusion

When the same measurement is available from multiple sources, readings can be combined by using a technique called sensor fusion.

/*

For example, you might have readings from an absolute and a relative encoder, or an odometer and an accelerometer.

*/

The measurements and their uncertainties are combined into a single measurement and its uncertainty with the following equations.

  • Combined measurement uncertainty σ is the blend of all uncertainties, each weighted by its inverse:

    σ = 1 / (1 / σ1 + 1 / σn)

    Since “one divided by uncertainty” converts it to “certainty” (its opposite), the inner equation sums up all the weighted certainties, and the outer equation converts the result back to “uncertainty”.

  • Combined measurement mean μ is the blend of all measurements, each weighted by inverse of its uncertainty:

    μ = σ × (μ1 / σ1 + μn / σn)

    This results in the combined measurement that blends in more certain measurement sources in greater amounts.

Combining more measurements results in the combined measurement uncertainty less than the uncertainty of any one measurement:

sensor-fusion

These equations are simple to implement as two loops that accumulate values:

% Same measurement from multiple sources
measurements = [ 100, 120, 89 ];
% Each source may have a different uncertainty
measurementVariances = [ 1000, 800, 90000 ];
% Combine measurement uncertainties
sumVariances = 0;
for n = 1:length(measurementVariances)
% Each uncertainty weighted by its inverse
sumVariances = sumVariances + ...
1 / measurementVariances(n);
end
measurementVariance = 1 / sumVariances
% Combine measurements
sumMeasurements = 0;
for n = 1:length(measurements)
% Each measurement weighted by inverse of its uncertainty
sumMeasurements = sumMeasurements + ...
measurements(n) / measurementVariances(n);
end
measurement = sumMeasurements * measurementVariance
>> measurementVariance =
442.2604
>> measurement =
111.0025

The resulting measurement (z) and its variance (R) are simply plugged into the Kalman filter, with no changes required to the filter implementation.

For sensor fusion in systems with multiple outputs and other advanced scenarios, see the Kalman Filter from the Ground Up book.

rate mismatch

To fuse measurements taken at different rates, simply combine any measurements available during a single Kalman filter iteration:

rate mismatch

In the example above, the 3 measurements could be combined in 7 ways:

  • Only the first measurement is available
  • Only the second measurement is available
  • Only the third measurement is available
  • Only the first and second measurements are available
  • Only the first and third measurements are available
  • Only the second and third measurements are available
  • All three measurements are available

Since combining measurements is done in a loop, simply filling one vector with whichever measurements are available and another vector with their uncertainties will let you combine the available measurements.

resources

See my presentation of these concepts at Ctrl^H Hackerspace:

kalman filter presentation

I take a slightly different path to build up to the Kalman filter in the presentation, which might help you see this topic from another perspective.

Additional resources covering Kalman filter design and linear system identification are listed below. Some are sold as published books and others are available for free as digital eBooks.

Loading comments...