Skip to content

Instantly share code, notes, and snippets.

@davidfoster
Last active October 21, 2023 07:58
Show Gist options
  • Star 50 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save davidfoster/48acce6c13e5f7f247dc5d5909dce349 to your computer and use it in GitHub Desktop.
Save davidfoster/48acce6c13e5f7f247dc5d5909dce349 to your computer and use it in GitHub Desktop.
Simple Kalman filtering in Unity.
using System.Collections.Generic;
/// <summary>A Kalman filter implementation for <c>float</c> values.</summary>
public class KalmanFilterFloat {
//-----------------------------------------------------------------------------------------
// Constants:
//-----------------------------------------------------------------------------------------
public const float DEFAULT_Q = 0.000001f;
public const float DEFAULT_R = 0.01f;
public const float DEFAULT_P = 1;
//-----------------------------------------------------------------------------------------
// Private Fields:
//-----------------------------------------------------------------------------------------
private float q;
private float r;
private float p = DEFAULT_P;
private float x;
private float k;
//-----------------------------------------------------------------------------------------
// Constructors:
//-----------------------------------------------------------------------------------------
// N.B. passing in DEFAULT_Q is necessary, even though we have the same value (as an optional parameter), because this
// defines a parameterless constructor, allowing us to be new()'d in generics contexts.
public KalmanFilterFloat() : this(DEFAULT_Q) { }
public KalmanFilterFloat(float aQ = DEFAULT_Q, float aR = DEFAULT_R) {
q = aQ;
r = aR;
}
//-----------------------------------------------------------------------------------------
// Public Methods:
//-----------------------------------------------------------------------------------------
public float Update(float measurement, float? newQ = null, float? newR = null) {
// update values if supplied.
if (newQ != null && q != newQ) {
q = (float)newQ;
}
if (newR != null && r != newR) {
r = (float)newR;
}
// update measurement.
{
k = (p + q) / (p + q + r);
p = r * (p + q) / (r + p + q);
}
// filter result back into calculation.
float result = x + (measurement - x) * k;
x = result;
return result;
}
public float Update(List<float> measurements, bool areMeasurementsNewestFirst = false, float? newQ = null, float? newR = null) {
float result = 0;
int i = (areMeasurementsNewestFirst) ? measurements.Count - 1 : 0;
while (i < measurements.Count && i >= 0) {
// decrement or increment the counter.
if (areMeasurementsNewestFirst) {
--i;
}
else {
++i;
}
result = Update(measurements[i], newQ, newR);
}
return result;
}
public void Reset() {
p = 1;
x = 0;
k = 0;
}
}
using System.Collections.Generic;
/// <summary>A Kalman filter implementation for any type of value which can added and multiplied.</summary>
/// <remarks>
/// Determining whether the type can be added and multiplied occurs at runtime via the <c>dynamic</c> keyword.
/// Note that if you use this with the incorrect data type (such as a <c>Quaternion</c>, which cannot be added),
/// the error will occur at runtime.
///
/// <c>dynamic</c> also incurs a runtime cost, so if performance is crucial, it is suggested a concrete Kalman
/// filter implementation be used such as <c>KalmanFilterFloat</c> or <c>KalmanFilterVector3</c>.
/// </remarks>
public class KalmanFilter<T> {
//-----------------------------------------------------------------------------------------
// Constants:
//-----------------------------------------------------------------------------------------
public const float DEFAULT_Q = 0.000001f;
public const float DEFAULT_R = 0.01f;
public const float DEFAULT_P = 1;
//-----------------------------------------------------------------------------------------
// Private Fields:
//-----------------------------------------------------------------------------------------
private float q;
private float r;
private float p = DEFAULT_P;
private T x;
private float k;
//-----------------------------------------------------------------------------------------
// Constructors:
//-----------------------------------------------------------------------------------------
// N.B. passing in DEFAULT_Q is necessary, even though we have the same value (as an optional parameter), because this
// defines a parameterless constructor, allowing us to be new()'d in generics contexts.
public KalmanFilter() : this(DEFAULT_Q) { }
public KalmanFilter(float aQ = DEFAULT_Q, float aR = DEFAULT_R) {
q = aQ;
r = aR;
}
//-----------------------------------------------------------------------------------------
// Public Methods:
//-----------------------------------------------------------------------------------------
public T Update(T measurement, float? newQ = null, float? newR = null) {
// update values if supplied.
if (newQ != null && q != newQ) {
q = (float)newQ;
}
if (newR != null && r != newR) {
r = (float)newR;
}
// update measurement.
{
k = (p + q) / (p + q + r);
p = r * (p + q) / (r + p + q);
}
// filter result back into calculation.
dynamic dynamicMeasurement = measurement;
dynamic result = x + (dynamicMeasurement - x) * k;
x = result;
return result;
}
public T Update(List<T> measurements, bool areMeasurementsNewestFirst = false, float? newQ = null, float? newR = null) {
T result = default(T);
int i = (areMeasurementsNewestFirst) ? measurements.Count - 1 : 0;
while (i < measurements.Count && i >= 0) {
// decrement or increment the counter.
if (areMeasurementsNewestFirst) {
--i;
}
else {
++i;
}
result = Update(measurements[i], newQ, newR);
}
return result;
}
public void Reset() {
p = 1;
x = default(T);
k = 0;
}
}
using System.Collections.Generic;
using UnityEngine;
/// <summary>A Kalman filter implementation for <c>Vector3</c> values.</summary>
public class KalmanFilterVector3 {
//-----------------------------------------------------------------------------------------
// Constants:
//-----------------------------------------------------------------------------------------
public const float DEFAULT_Q = 0.000001f;
public const float DEFAULT_R = 0.01f;
public const float DEFAULT_P = 1;
//-----------------------------------------------------------------------------------------
// Private Fields:
//-----------------------------------------------------------------------------------------
private float q;
private float r;
private float p = DEFAULT_P;
private Vector3 x;
private float k;
//-----------------------------------------------------------------------------------------
// Constructors:
//-----------------------------------------------------------------------------------------
// N.B. passing in DEFAULT_Q is necessary, even though we have the same value (as an optional parameter), because this
// defines a parameterless constructor, allowing us to be new()'d in generics contexts.
public KalmanFilterVector3() : this(DEFAULT_Q) { }
public KalmanFilterVector3(float aQ = DEFAULT_Q, float aR = DEFAULT_R) {
q = aQ;
r = aR;
}
//-----------------------------------------------------------------------------------------
// Public Methods:
//-----------------------------------------------------------------------------------------
public Vector3 Update(Vector3 measurement, float? newQ = null, float? newR = null) {
// update values if supplied.
if (newQ != null && q != newQ) {
q = (float)newQ;
}
if (newR != null && r != newR) {
r = (float)newR;
}
// update measurement.
{
k = (p + q) / (p + q + r);
p = r * (p + q) / (r + p + q);
}
// filter result back into calculation.
Vector3 result = x + (measurement - x) * k;
x = result;
return result;
}
public Vector3 Update(List<Vector3> measurements, bool areMeasurementsNewestFirst = false, float? newQ = null, float? newR = null) {
Vector3 result = Vector3.zero;
int i = (areMeasurementsNewestFirst) ? measurements.Count - 1 : 0;
while (i < measurements.Count && i >= 0) {
// decrement or increment the counter.
if (areMeasurementsNewestFirst) {
--i;
}
else {
++i;
}
result = Update(measurements[i], newQ, newR);
}
return result;
}
public void Reset() {
p = 1;
x = Vector3.zero;
k = 0;
}
}
using System.Collections.Generic;
using UnityEngine;
/// <summary>A Kalman filter implementation for <c>Vector4</c> values.</summary>
public class KalmanFilterVector4 {
//-----------------------------------------------------------------------------------------
// Constants:
//-----------------------------------------------------------------------------------------
public const float DEFAULT_Q = 0.000001f;
public const float DEFAULT_R = 0.01f;
public const float DEFAULT_P = 1;
//-----------------------------------------------------------------------------------------
// Private Fields:
//-----------------------------------------------------------------------------------------
private float q;
private float r;
private float p = DEFAULT_P;
private Vector4 x;
private float k;
//-----------------------------------------------------------------------------------------
// Constructors:
//-----------------------------------------------------------------------------------------
// N.B. passing in DEFAULT_Q is necessary, even though we have the same value (as an optional parameter), because this
// defines a parameterless constructor, allowing us to be new()'d in generics contexts.
public KalmanFilterVector4() : this(DEFAULT_Q) { }
public KalmanFilterVector4(float aQ = DEFAULT_Q, float aR = DEFAULT_R) {
q = aQ;
r = aR;
}
//-----------------------------------------------------------------------------------------
// Public Methods:
//-----------------------------------------------------------------------------------------
public Vector4 Update(Vector4 measurement, float? newQ = null, float? newR = null) {
// update values if supplied.
if (newQ != null && q != newQ) {
q = (float)newQ;
}
if (newR != null && r != newR) {
r = (float)newR;
}
// update measurement.
{
k = (p + q) / (p + q + r);
p = r * (p + q) / (r + p + q);
}
// filter result back into calculation.
Vector4 result = x + (measurement - x) * k;
x = result;
return result;
}
public Vector4 Update(List<Vector4> measurements, bool areMeasurementsNewestFirst = false, float? newQ = null, float? newR = null) {
Vector4 result = Vector4.zero;
int i = (areMeasurementsNewestFirst) ? measurements.Count - 1 : 0;
while (i < measurements.Count && i >= 0) {
// decrement or increment the counter.
if (areMeasurementsNewestFirst) {
--i;
}
else {
++i;
}
result = Update(measurements[i], newQ, newR);
}
return result;
}
public void Reset() {
p = 1;
x = Vector4.zero;
k = 0;
}
}
@Kujoen
Copy link

Kujoen commented May 7, 2023

What is the license of this code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment