14

This is my first question on Stackoverflow, so I apologize if I word it poorly. I am writing code to take raw acceleration data from an IMU and then integrate it to update the position of an object. Currently this code takes a new accelerometer reading every milisecond, and uses that to update the position. My system has a lot of noise, which results in crazy readings due to compounding error, even with the ZUPT scheme I implemented. I know that a Kalman filter is theoretically ideal for this scenario, and I would like to use the pykalman module instead of building one myself.

My first question is, can pykalman be used in real time like this? From the documentation it looks to me like you have to have a record of all measurements and then perform the smooth operation, which would not be practical as I want to filter recursively every milisecond.

My second question is, for the transition matrix can I only apply pykalman to the acceleration data by itself, or can I somehow include the double integration to position? What would that matrix look like?

If pykalman is not practical for this situation, is there another way I can implement a Kalman Filter? Thank you in advance!

Alex
  • 155
  • 1
  • 6

1 Answers1

17

You can use a Kalman Filter in this case, but your position estimation will strongly depend on the precision of your acceleration signal. The Kalman Filter is actually useful for a fusion of several signals. So error of one signal can be compensated by another signal. Ideally you need to use sensors based on different physical effects (for example an IMU for acceleration, GPS for position, odometry for velocity).

In this answer I'm going to use readings from two acceleration sensors (both in X direction). One of these sensors is an expansive and precise. The second one is much cheaper. So you will see the sensor precision influence on the position and velocity estimations.

You already mentioned the ZUPT scheme. I just want to add some notes: it is very important to have a good estimation of the pitch angle, to get rid of the gravitation component in your X-acceleration. If you use Y- and Z-acceleration you need both pitch and roll angles.

Let's start with modelling. Assume you have only acceleration readings in X-direction. So your observation will look like

formula

Now you need to define the smallest data set, which completely describes your system in each point of time. It will be the system state.

formula

The mapping between the measurement and state domains is defined by the observation matrix:

formula

formula

Now you need to describe the system dynamics. According to this information the Filter will predict a new state based on the previous one.

formula

In my case dt=0.01s. Using this matrix the Filter will integrate the acceleration signal to estimate the velocity and position.

The observation covariance R can be described by the variance of your sensor readings. In my case I have only one signal in my observation, so the observation covariance is equal to the variance of the X-acceleration (the value can be calculated based on your sensors datasheet).

Through the transition covariance Q you describe the system noise. The smaller the matrix values, the smaller the system noise. The Filter will become stiffer and the estimation will be delayed. The weight of the system's past will be higher compared to new measurement. Otherwise the filter will be more flexible and will react strongly on each new measurement.

Now everything is ready to configure the Pykalman. In order to use it in real time, you have to use the filter_update function.

from pykalman import KalmanFilter
import numpy as np
import matplotlib.pyplot as plt

load_data()

# Data description
#  Time
#  AccX_HP - high precision acceleration signal
#  AccX_LP - low precision acceleration signal
#  RefPosX - real position (ground truth)
#  RefVelX - real velocity (ground truth)

# switch between two acceleration signals
use_HP_signal = 1

if use_HP_signal:
    AccX_Value = AccX_HP
    AccX_Variance = 0.0007
else:    
    AccX_Value = AccX_LP
    AccX_Variance = 0.0020


# time step
dt = 0.01

# transition_matrix  
F = [[1, dt, 0.5*dt**2], 
     [0,  1,       dt],
     [0,  0,        1]]

# observation_matrix   
H = [0, 0, 1]

# transition_covariance 
Q = [[0.2,    0,      0], 
     [  0,  0.1,      0],
     [  0,    0,  10e-4]]

# observation_covariance 
R = AccX_Variance

# initial_state_mean
X0 = [0,
      0,
      AccX_Value[0, 0]]

# initial_state_covariance
P0 = [[  0,    0,               0], 
      [  0,    0,               0],
      [  0,    0,   AccX_Variance]]

n_timesteps = AccX_Value.shape[0]
n_dim_state = 3
filtered_state_means = np.zeros((n_timesteps, n_dim_state))
filtered_state_covariances = np.zeros((n_timesteps, n_dim_state, n_dim_state))

kf = KalmanFilter(transition_matrices = F, 
                  observation_matrices = H, 
                  transition_covariance = Q, 
                  observation_covariance = R, 
                  initial_state_mean = X0, 
                  initial_state_covariance = P0)

# iterative estimation for each new measurement
for t in range(n_timesteps):
    if t == 0:
        filtered_state_means[t] = X0
        filtered_state_covariances[t] = P0
    else:
        filtered_state_means[t], filtered_state_covariances[t] = (
        kf.filter_update(
            filtered_state_means[t-1],
            filtered_state_covariances[t-1],
            AccX_Value[t, 0]
        )
    )


f, axarr = plt.subplots(3, sharex=True)

axarr[0].plot(Time, AccX_Value, label="Input AccX")
axarr[0].plot(Time, filtered_state_means[:, 2], "r-", label="Estimated AccX")
axarr[0].set_title('Acceleration X')
axarr[0].grid()
axarr[0].legend()
axarr[0].set_ylim([-4, 4])

axarr[1].plot(Time, RefVelX, label="Reference VelX")
axarr[1].plot(Time, filtered_state_means[:, 1], "r-", label="Estimated VelX")
axarr[1].set_title('Velocity X')
axarr[1].grid()
axarr[1].legend()
axarr[1].set_ylim([-1, 20])

axarr[2].plot(Time, RefPosX, label="Reference PosX")
axarr[2].plot(Time, filtered_state_means[:, 0], "r-", label="Estimated PosX")
axarr[2].set_title('Position X')
axarr[2].grid()
axarr[2].legend()
axarr[2].set_ylim([-10, 1000])

plt.show()

When using the better IMU-sensor, the estimated position is exactly the same as the ground truth:

position estimation based on a good IMU-sensor

The cheaper sensor gives significantly worse results:

position estimation based on a cheap IMU-sensor

I hope I could help you. If you have some questions, I will try to answer them.

UPDATE

If you want to experiment with different data you can generate them easily (unfortunately I don't have the original data any more).

Here is a simple matlab script to generate reference, good and poor sensor set.

clear;

dt = 0.01;
t=0:dt:70;

accX_var_best = 0.0005; % (m/s^2)^2
accX_var_good = 0.0007; % (m/s^2)^2
accX_var_worst = 0.001; % (m/s^2)^2

accX_ref_noise = randn(size(t))*sqrt(accX_var_best);
accX_good_noise = randn(size(t))*sqrt(accX_var_good);
accX_worst_noise = randn(size(t))*sqrt(accX_var_worst);

accX_basesignal = sin(0.3*t) + 0.5*sin(0.04*t);

accX_ref = accX_basesignal + accX_ref_noise;
velX_ref = cumsum(accX_ref)*dt;
distX_ref = cumsum(velX_ref)*dt;


accX_good_offset = 0.001 + 0.0004*sin(0.05*t);

accX_good = accX_basesignal + accX_good_noise + accX_good_offset;
velX_good = cumsum(accX_good)*dt;
distX_good = cumsum(velX_good)*dt;


accX_worst_offset = -0.08 + 0.004*sin(0.07*t);

accX_worst = accX_basesignal + accX_worst_noise + accX_worst_offset;
velX_worst = cumsum(accX_worst)*dt;
distX_worst = cumsum(velX_worst)*dt;

subplot(3,1,1);
plot(t, accX_ref);
hold on;
plot(t, accX_good);
plot(t, accX_worst);
hold off;
grid minor;
legend('ref', 'good', 'worst');
title('AccX');

subplot(3,1,2);
plot(t, velX_ref);
hold on;
plot(t, velX_good);
plot(t, velX_worst);
hold off;
grid minor;
legend('ref', 'good', 'worst');
title('VelX');

subplot(3,1,3);
plot(t, distX_ref);
hold on;
plot(t, distX_good);
plot(t, distX_worst);
hold off;
grid minor;
legend('ref', 'good', 'worst');
title('DistX');

The simulated data looks pretty the same like the data above.

simulated data for different sensor variances

Anton
  • 4,334
  • 2
  • 20
  • 29
  • This is awesome. @Anton Can you please, please also provide the dataset and the implementation for load_data()? I have been struggling with this now since one day, and all the datasets I"ve tried either do not provide ground truth or need to be reformatted.. Many thanks! – Mihai Galos Jan 04 '19 at 09:30
  • 1
    Hallo Mihai, give me some time to find the data I used. – Anton Jan 05 '19 at 23:06
  • Hi Anton, any luck with that ? – Mihai Galos Jan 09 '19 at 19:10
  • 1
    Hallo Mihai, unfortunately I can't find the original data. You can use the simple matlab script I added to my answer to generate sensor data for a given sensor variance. I hope you can use it for your research. – Anton Jan 14 '19 at 07:56
  • @Anton could you tell us both the expensive and cheap accelerometer name? I want to look up their datasheets. – rosmianto Feb 21 '20 at 09:51
  • @rosmianto sorry, I'm not allowed to. The cheap one is comparable to $2 items, everyone can buy on ebay :) – Anton Feb 21 '20 at 11:07
  • @Anton Is it possible to do with pykalman library an estimation of velocity and position given the signals of three-axis acceletometer and gyroscope? I mean, fusing these two sensors and estimating the position and velocity. I would appreciate your help!! Best Oscar – Oscar Gabriel Reyes Pupo Apr 06 '20 at 16:11
  • @Oscar pykalman is just a library and it will work fine for this problem if you have a suitable system model and some trace data. As far as I know it's not an easy problem. It depends strongly on your application domain (is it a vehicle, a jet or a person) and which assumptions you can do to simplify the modelling. If I find something interesting, I'll post a link. – Anton Apr 07 '20 at 12:18
  • @Anton Yes, I have a lot of trace data collected from three-axis accelerometer and gyroscope sensors of Iphone(s) while several persons perform different daily activities (walking, sitting, climbing stairs, etc). The data were sample at 50Hz. I hab used the function imufilter of Matlab that returns quaternions, but I have not found any solution in Python. – Oscar Gabriel Reyes Pupo Apr 09 '20 at 14:40
  • @Oscar, yes I have some experience with imufilter function. There is a nice description of the internal math, which gives you all the matrices you need for the Kalman filter. So actually if you were satisfied with the imufilter output, you can reimplement it for the pykalman library. I tried to do it in matlab some time ago. – Anton Apr 09 '20 at 19:34
  • @Oscar, here is the link https://github.com/memsindustrygroup/Open-Source-Sensor-Fusion/tree/master/docs – Anton Apr 09 '20 at 20:09