Repository: meyiao/ImuFusion
Branch: master
Commit: 23554fb4df3a
Files: 10
Total size: 17.9 KB
Directory structure:
gitextract_3433hldn/
├── README.md
├── SixDofAnimation.m
├── compVq.m
├── data/
│ ├── SpiralStairs.mat
│ ├── StairsAndCorridor.mat
│ ├── StraightLine.mat
│ ├── attitude_data.mat
│ └── readme.txt
├── orien.m
└── zupt.m
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# ImuFusion
## EKF IMU Fusion Algorithms
1. orien.m uses Kalman filter for fusing the gyroscope's and accelerometer's readings to get the IMU's attitude(quaternion).
2. zupt.m implenments the so called 'zero-velocity-update' algorithm for pedestrian tracking(gait tracking), it's also a ekf filter.
* Video: http://v.youku.com/v_show/id_XMTg2NjI4NTI4NA==.html
## Usage
Example data already included.
Simply run the orien.m or zupt.m. For zupt, set 'CreateVideo' as true if you'd like to save the results as a video.
Note that the datasets and the code for visualizing the results were from:
https://github.com/xioTechnologies/Gait-Tracking-With-x-IMU
## References:
[1] S. Madgwick. An efficient orientation filter for inertial and inertial/magnetic sensor arrays.
[2] Fischer C, et. Implementing a Pedestrian Tracker Using inertial Sensors.
[3] Isaac Skog, et. Zero-Velocity Detection — An Algorithm Evaluation.
================================================
FILE: SixDofAnimation.m
================================================
% Note that this animation code was stolen from:
% https://github.com/xioTechnologies/Gait-Tracking-With-x-IMU
function fig = SixDOFanimation(varargin)
%% Create local variables
% Required arguments
p = varargin{1}; % position of body
R = varargin{2}; % rotation matrix of body
[numSamples dummy] = size(p);
% Default values of optional arguments
SamplePlotFreq = 1;
Trail = 'Off';
LimitRatio = 1;
Position = [];
FullScreen = false;
View = [30 20];
AxisLength = 1;
ShowArrowHead = 'on';
Xlabel = 'X';
Ylabel = 'Y';
Zlabel = 'Z';
Title = '6DOF Animation';
ShowLegend = true;
write_video = false;
for i = 3:2:nargin
if strcmp(varargin{i}, 'SamplePlotFreq'), SamplePlotFreq = varargin{i+1};
elseif strcmp(varargin{i}, 'Trail')
Trail = varargin{i+1};
if(~strcmp(Trail, 'Off') && ~strcmp(Trail, 'DotsOnly') && ~strcmp(Trail, 'All'))
error('Invalid argument. Trail must be ''Off'', ''DotsOnly'' or ''All''.');
end
elseif strcmp(varargin{i}, 'LimitRatio'), LimitRatio = varargin{i+1};
elseif strcmp(varargin{i}, 'Position'), Position = varargin{i+1};
elseif strcmp(varargin{i}, 'FullScreen'), FullScreen = varargin{i+1};
elseif strcmp(varargin{i}, 'View'), View = varargin{i+1};
elseif strcmp(varargin{i}, 'AxisLength'), AxisLength = varargin{i+1};
elseif strcmp(varargin{i}, 'ShowArrowHead'), ShowArrowHead = varargin{i+1};
elseif strcmp(varargin{i}, 'Xlabel'), Xlabel = varargin{i+1};
elseif strcmp(varargin{i}, 'Ylabel'), Ylabel = varargin{i+1};
elseif strcmp(varargin{i}, 'Zlabel'), Zlabel = varargin{i+1};
elseif strcmp(varargin{i}, 'Title'), Title = varargin{i+1};
elseif strcmp(varargin{i}, 'ShowLegend'), ShowLegend = varargin{i+1};
elseif strcmp(varargin{i}, 'CreateVideo'), write_video = varargin{i+1};
else error('Invalid argument.');
end
end;
%% Reduce data to samples to plot only
p = p(1:SamplePlotFreq:numSamples, :);
R = R(:, :, 1:SamplePlotFreq:numSamples) * AxisLength;
if(numel(View) > 2)
View = View(1:SamplePlotFreq:numSamples, :);
end
[numPlotSamples dummy] = size(p);
%% Setup AVI file
% % % write video
if write_video
writeObj = VideoWriter('results');
writeObj.FrameRate = 60;
open(writeObj);
end
%% Setup figure and plot
% Create figure
fig = figure();
if(FullScreen)
screenSize = get(0, 'ScreenSize');
set(fig, 'Position', [0 0 screenSize(3) screenSize(4)]);
elseif(~isempty(Position))
set(fig, 'Position', Position);
end
set(gca, 'drawmode', 'fast');
lighting phong;
set(gcf, 'Renderer', 'zbuffer');
hold on;
axis equal;
grid on;
view(View(1, 1), View(1, 2));
title(i);
xlabel(Xlabel);
ylabel(Ylabel);
zlabel(Zlabel);
% Create plot data arrays
if(strcmp(Trail, 'DotsOnly') || strcmp(Trail, 'All'))
x = zeros(numPlotSamples, 1);
y = zeros(numPlotSamples, 1);
z = zeros(numPlotSamples, 1);
end
if(strcmp(Trail, 'All'))
ox = zeros(numPlotSamples, 1);
oy = zeros(numPlotSamples, 1);
oz = zeros(numPlotSamples, 1);
ux = zeros(numPlotSamples, 1);
vx = zeros(numPlotSamples, 1);
wx = zeros(numPlotSamples, 1);
uy = zeros(numPlotSamples, 1);
vy = zeros(numPlotSamples, 1);
wy = zeros(numPlotSamples, 1);
uz = zeros(numPlotSamples, 1);
vz = zeros(numPlotSamples, 1);
wz = zeros(numPlotSamples, 1);
end
x(1) = p(1,1);
y(1) = p(1,2);
z(1) = p(1,3);
ox(1) = x(1);
oy(1) = y(1);
oz(1) = z(1);
ux(1) = R(1,1,1:1);
vx(1) = R(2,1,1:1);
wx(1) = R(3,1,1:1);
uy(1) = R(1,2,1:1);
vy(1) = R(2,2,1:1);
wy(1) = R(3,2,1:1);
uz(1) = R(1,3,1:1);
vz(1) = R(2,3,1:1);
wz(1) = R(3,3,1:1);
% Create graphics handles
orgHandle = plot3(x, y, z, 'k.');
if(ShowArrowHead)
ShowArrowHeadStr = 'on';
else
ShowArrowHeadStr = 'off';
end
quivXhandle = quiver3(ox, oy, oz, ux, vx, wx, 'r', 'ShowArrowHead', ShowArrowHeadStr, 'MaxHeadSize', 0.999999, 'AutoScale', 'off');
quivYhandle = quiver3(ox, oy, oz, uy, vy, wy, 'g', 'ShowArrowHead', ShowArrowHeadStr, 'MaxHeadSize', 0.999999, 'AutoScale', 'off');
quivZhandle = quiver3(ox, ox, oz, uz, vz, wz, 'b', 'ShowArrowHead', ShowArrowHeadStr, 'MaxHeadSize', 0.999999, 'AutoScale', 'off');
% Create legend
if(ShowLegend)
legend('Origin', 'X', 'Y', 'Z');
end
% Set initial limits
Xlim = [x(1)-AxisLength x(1)+AxisLength] * LimitRatio;
Ylim = [y(1)-AxisLength y(1)+AxisLength] * LimitRatio;
Zlim = [z(1)-AxisLength z(1)+AxisLength] * LimitRatio;
set(gca, 'Xlim', Xlim, 'Ylim', Ylim, 'Zlim', Zlim);
% Set initial view
view(View(1, :));
%% Plot one sample at a time
for i = 1:numPlotSamples
% Update graph title
if(strcmp(Title, ''))
titleText = sprintf('Sample %i of %i', 1+((i-1)*SamplePlotFreq), numSamples);
else
titleText = strcat(Title, ' (', sprintf('Sample %i of %i', 1+((i-1)*SamplePlotFreq), numSamples), ')');
end
title(titleText);
% Plot body x y z axes
if(strcmp(Trail, 'DotsOnly') || strcmp(Trail, 'All'))
x(1:i) = p(1:i,1);
y(1:i) = p(1:i,2);
z(1:i) = p(1:i,3);
else
x = p(i,1);
y = p(i,2);
z = p(i,3);
end
if(strcmp(Trail, 'All'))
ox(1:i) = p(1:i,1);
oy(1:i) = p(1:i,2);
oz(1:i) = p(1:i,3);
ux(1:i) = R(1,1,1:i);
vx(1:i) = R(2,1,1:i);
wx(1:i) = R(3,1,1:i);
uy(1:i) = R(1,2,1:i);
vy(1:i) = R(2,2,1:i);
wy(1:i) = R(3,2,1:i);
uz(1:i) = R(1,3,1:i);
vz(1:i) = R(2,3,1:i);
wz(1:i) = R(3,3,1:i);
else
ox = p(i,1);
oy = p(i,2);
oz = p(i,3);
ux = R(1,1,i);
vx = R(2,1,i);
wx = R(3,1,i);
uy = R(1,2,i);
vy = R(2,2,i);
wy = R(3,2,i);
uz = R(1,3,i);
vz = R(2,3,i);
wz = R(3,3,i);
end
set(orgHandle, 'xdata', x, 'ydata', y, 'zdata', z);
set(quivXhandle, 'xdata', ox, 'ydata', oy, 'zdata', oz,'udata', ux, 'vdata', vx, 'wdata', wx);
set(quivYhandle, 'xdata', ox, 'ydata', oy, 'zdata', oz,'udata', uy, 'vdata', vy, 'wdata', wy);
set(quivZhandle, 'xdata', ox, 'ydata', oy, 'zdata', oz,'udata', uz, 'vdata', vz, 'wdata', wz);
% Adjust axes for snug fit and draw
axisLimChanged = false;
if((p(i,1) - AxisLength) < Xlim(1)), Xlim(1) = p(i,1) - LimitRatio*AxisLength; axisLimChanged = true; end
if((p(i,2) - AxisLength) < Ylim(1)), Ylim(1) = p(i,2) - LimitRatio*AxisLength; axisLimChanged = true; end
if((p(i,3) - AxisLength) < Zlim(1)), Zlim(1) = p(i,3) - LimitRatio*AxisLength; axisLimChanged = true; end
if((p(i,1) + AxisLength) > Xlim(2)), Xlim(2) = p(i,1) + LimitRatio*AxisLength; axisLimChanged = true; end
if((p(i,2) + AxisLength) > Ylim(2)), Ylim(2) = p(i,2) + LimitRatio*AxisLength; axisLimChanged = true; end
if((p(i,3) + AxisLength) > Zlim(2)), Zlim(2) = p(i,3) + LimitRatio*AxisLength; axisLimChanged = true; end
if(axisLimChanged), set(gca, 'Xlim', Xlim, 'Ylim', Ylim, 'Zlim', Zlim); end
drawnow;
% Adjust view
if(numel(View) > 2)
view(View(i, :));
end
% Add frame to AVI object
if write_video
frame = getframe(fig);
writeVideo(writeObj, frame);
end
end
hold off;
% Close AVI file
if write_video
close(writeObj);
end
end
================================================
FILE: compVq.m
================================================
function Vq = compVq(q, a)
q1 = q(1);
q2 = q(2);
q3 = q(3);
q4 = q(4);
ax = a(1);
ay = a(2);
az = a(3);
v_dot_q1 = [2*ax*q1 - 2*ay*q4 + 2*az*q3;...
2*ax*q4 + 2*ay*q1 - 2*az*q2;...
2*ay*q2 - 2*ax*q3 + 2*az*q1];
v_dot_q2 = [2*ax*q2 + 2*ay*q3 + 2*az*q4;...
2*ax*q3 - 2*ay*q2 - 2*az*q1;...
2*ax*q4 + 2*ay*q1 - 2*az*q2];
v_dot_q3 = [2*ay*q2 - 2*ax*q3 + 2*az*q1;...
2*ax*q2 + 2*ay*q3 + 2*az*q4;...
2*ay*q4 - 2*ax*q1 - 2*az*q3];
v_dot_q4 = [2*az*q2 - 2*ay*q1 - 2*ax*q4;...
2*ax*q1 - 2*ay*q4 + 2*az*q3;...
2*ax*q2 + 2*ay*q3 + 2*az*q4];
Vq = [v_dot_q1, v_dot_q2, v_dot_q3, v_dot_q4];
================================================
FILE: data/readme.txt
================================================
Note that the StraightLine.mat, SpiralStairs.mat, StairAndCorridor.mat were created from the csv files provided by:
https://github.com/xioTechnologies/Gait-Tracking-With-x-IMU
================================================
FILE: orien.m
================================================
% This is an Extended Kalman Filter which fuses
% accelerometer & gyroscope readings
% to get the orientation.
% Written by @TanTanTan
% Note that we assume the biases of IMU do not change in the whole
% process. This is approximately true when the sensors operate in a steady
% state.
clear; close all; clc;
%--- Dataset parameters
deltat = 1/200; % Sampling period
noise_gyro = 2.4e-3; % Gyroscope noise(discrete), rad/s
noise_accel = 2.83e-2; % Accelerometer noise, m/s^2
gravity = 9.81007; % Gravity magnitude, m/s^2
%--- Load data
% Each row of IMU data :
% (timestamp, wx, wy, wz, ax, ay, az)
% Each row of Ground truth data :
% (time, position, quaternion, velocity, gyroscope bias, accelerometer bias)
data = load('data/attitude_data.mat');
imu_data = data.imu_data; % IMU readings
grt_data = data.grt_data; % Ground truth (GT)
grt_q = grt_data(:, 5:8); % GT quaternions
bias_w = grt_data(1, 12:14); % gyroscope bias
bias_a = grt_data(1, 15:17); % accelerometer bias
%--- Container of the results
N = length(imu_data);
allX = zeros(N, 4);
%--- Initialization
x = grt_q(1,:)'; % Initial state (quaternion)
P = 1e-10 * eye(4); % Initial covariance
allX(1,:) = x';
% ---Here we go !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
for k = 2 : N
%--- 1. Propagation --------------------------
% Gyroscope measurements
w = (imu_data(k-1, 2:4) + imu_data(k, 2:4))/2;
w = w - bias_w;
% Compute the F matrix
Omega =[0 -w(1) -w(2) -w(3);...
w(1) 0 w(3) -w(2); ...
w(2) -w(3) 0 w(1); ...
w(3) w(2) -w(1) 0 ];
F = eye(4) + deltat * Omega / 2;
% Compute the process noise Q
G = [-x(2) -x(3) -x(4); ...
x(1) -x(4) x(3); ...
x(4) x(1) -x(2); ...
-x(3) x(2) x(1)] / 2;
Q = (noise_gyro * deltat)^2 * (G * G');
% Propagate the state and covariance
x = F * x;
x = x / norm(x); % Normalize the quaternion
P = F * P * F' + Q;
%--- 2. Update----------------------------------
% Accelerometer measurements
a = imu_data(k, 5:7);
a = a - bias_a;
% We use the unit vector of acceleration as observation
ea = a' / norm(a);
ea_prediction = [2*(x(2)*x(4)-x(1)*x(3)); ...
2*(x(3)*x(4)+x(1)*x(2)); ...
x(1)^2-x(2)^2-x(3)^2+x(4)^2];
% Residual
y = ea - ea_prediction;
% Compute the measurement matrix H
H = 2*[-x(3) x(4) -x(1) x(2); ...
x(2) x(1) x(4) x(3); ...
x(1) -x(2) -x(3) x(4)];
% Measurement noise R
R_internal = (noise_accel / norm(a))^2 * eye(3);
R_external = (1-gravity/norm(a))^2 * eye(3);
R = R_internal + R_external;
% Update
S = H * P * H' + R;
K = P * H' / S;
x = x + K * y;
P = (eye(4) - K * H) * P;
% 3. Ending
x = x / norm(x); % Normalize the quaternion
P = (P + P') / 2; % Make sure that covariance matrix is symmetric
allX(k,:) = x'; % Save the result
end
%--- Compare the results with ground truth
q_Ws0 = quatinv(grt_q(1,:));
for i=1:N
grt_q(i,:) = quatmultiply(q_Ws0, grt_q(i,:));
allX(i,:) = quatmultiply(q_Ws0, allX(i,:));
end
[psi, theta, phi] = quat2angle(allX);
[grt_psi, grt_theta, grt_phi] = quat2angle(grt_q);
figure, hold on
plot(1:N, psi, 'r-.', 1:N, grt_psi, 'r');
plot(1:N, theta, 'g-.', 1:N, grt_theta, 'g');
plot(1:N, phi, 'b-.', 1:N, grt_phi, 'b');
================================================
FILE: zupt.m
================================================
% This implements a zero velocity update EKF algorithm, which can be used
% for pedestrian tracking.
% By @TanTanTan
% Email: jinaqiaoqiao@gmail.com
clear; close all; clc;
% --- Dataset parameters
dt = 1/256;
g = 9.81;
sigma_acc = 0.1; % accelerometer noise (m/s^2)
sigma_gyro = 0.1*pi/180; % gyroscope noise (rad/s)
sigma_vel = 0.01; % zero-velocity update measurement noise (m/s)
%% zero-velocity detector parameters
cova = 0.01^2;
covw = (0.1*pi/180)^2;
W = 5; % window size
gamma = 0.3e5;
%% read data (Choose one)
% % data = load('data/StaightLine.mat');
% % data = load('data/StairsAndCorridor.mat');
data = load('data/SpiralStairs.mat');
imu_data = data.imu_data';
N = size(imu_data, 2);
%% Since we have got all the data, we can run the zero velocity detection
%% for the whole dataset.
iszv = zeros(1, N);
T = zeros(1, N-W+1);
for k = 1:N-W+1
mean_a = mean(imu_data(1:3,k:k+W-1), 2);
for l = k:k+W-1
temp = imu_data(1:3,l) - g * mean_a / norm(mean_a);
T(k) = T(k) + imu_data(4:6,l)'*imu_data(4:6,l)/covw + temp'*temp/cova;
end
end
T = T./W;
for k = 1:size(T,2)
if T(k) < gamma
iszv(k:k+W-1) = ones(1,W);
end
end
%%
%=========================================================================%
%== Initialization =%
%=========================================================================%
% We require that the IMU to be at static for first one second. So we can
% estimate the initial roll/pitch angle, as well as the biases.
init_a = mean(imu_data(1:3,1:20),2);
init_a = init_a / norm(init_a);
init_psi = 0;
init_theta = -asin(init_a(1));
init_phi = atan2(init_a(2), init_a(3));
init_quat = angle2quat(init_psi, init_theta, init_phi);
% Estimate sensor bias.
Rsw = quat2dcm(init_quat);
as = Rsw * [0;0;g];
bias_a = mean(imu_data(1:3,1:500),2) - as;
bias_w = mean(imu_data(4:6,1:500),2);
% set the initial state vector
x = zeros(10,1);
x(7:10,1) = init_quat';
% set the initial covariance
P = diag([1e-10*ones(1,6), 1e-6*ones(1,4)]);
%
x_r = zeros(10,N);
x_r(:,1) = x;
% measurement matrix
H = [zeros(3), eye(3), zeros(3,4)];
R = sigma_vel^2 * eye(3);
%%
%=========================================================================%
%== Main Loop =%
%=========================================================================%
for k = 2:N
%% compute state transition matrix F and covariance Q
w = imu_data(4:6, k-1); % - bias_w;
quat = x(7:10,1);
a = imu_data(1:3, k-1); % - bias_a;
% continuous state transition matrix
Ow = [0 -w(1) -w(2) -w(3);...
w(1) 0 w(3) -w(2);...
w(2) -w(3) 0 w(1);...
w(3) w(2) -w(1) 0 ];
Vq = compVq(quat, a);
Fc = zeros(10);
Fc(1:3, 4:6) = eye(3);
Fc(4:10,7:10)= [Vq; 0.5*Ow];
% continuous process covariance
Gq = 0.5* [-quat(2) -quat(3) -quat(4); ...
quat(1) -quat(4) quat(3); ...
quat(4) quat(1) -quat(2); ...
-quat(3) quat(2) quat(1)];
Qc = zeros(10);
Qc(4:6, 4:6) = sigma_acc^2*eye(3);
Qc(7:10,7:10) = sigma_gyro^2*(Gq*Gq');
% discretilization
F = eye(10) + Fc* dt;
Q = Qc* dt;
%% state propagation
R_S_n = quat2dcm(quat');
acc = R_S_n' * a - [0; 0; g];
x(1:3) = x(1:3) + x(4:6)* dt + 0.5*acc* dt^2;
x(4:6) = x(4:6) + acc* dt;
quat = (eye(4) + 0.5*Ow* dt)*quat;
quat = quat/norm(quat);
x(7:10) = quat;
%% covariance propagation
P = F*P*F' + Q;
%% zero-velocity update
if iszv(k) == 1
K = (P*H')/(H*P*H'+R);
y = -x(4:6);
x = x + K*y;
x(7:10) = x(7:10)/norm(x(7:10));
P = (eye(10)-K*H)*P;
end
P = (P+P')/2;
x_r(:,k) = x;
end
%% View the results
%% Plot the trajectory
figure(1), hold on
plot(x_r(1,:),x_r(2,:));
plot(x_r(1,1),x_r(2,1),'ro');
legend('Trajectory','Start point')
axis equal
grid on
figure(2),
plot(1:N,x_r(3,:));
title('Height')
grid on
%% Animation
%% The animation code was stolen from xioTechnologies.
%% https://github.com/xioTechnologies/Gait-Tracking-With-x-IMU
L = size(x_r,2);
SamplePlotFreq = 4;
Spin = 120;
SixDofAnimation(x_r(1:3,:)', quat2dcm(quatinv(x_r(7:10,:)')), ...
'SamplePlotFreq', SamplePlotFreq, 'Trail', 'All',...
'Position', [9 39 1280 768],...
'View', [(100:(Spin/(L-1)):(100+Spin))', 10*ones(L, 1)],...
'AxisLength', 0.1, 'ShowArrowHead', false, ...
'Xlabel', 'X (m)', 'Ylabel', 'Y (m)', 'Zlabel', 'Z (m)',...
'ShowLegend', false,...
'CreateVideo', false);