Skip to content

Instantly share code, notes, and snippets.

@phausamann
Created November 19, 2018 09:41
Show Gist options
  • Save phausamann/721fa3df0f8ef6f4f6f24b86fdde53c0 to your computer and use it in GitHub Desktop.
Save phausamann/721fa3df0f8ef6f4f6f24b86fdde53c0 to your computer and use it in GitHub Desktop.
IMU orientation estimation with complementary filter
import numpy as np
from scipy.signal import filtfilt, butter
from quaternion import quaternion, from_rotation_vector, rotate_vectors
def estimate_orientation(a, w, t, alpha=0.9, g_ref=(0., 0., 1.),
theta_min=1e-6, highpass=.01, lowpass=.05):
""" Estimate orientation with a complementary filter.
Fuse linear acceleration and angular velocity measurements to obtain an
estimate of orientation using a complementary filter as described in
`Wetzstein 2017: 3-DOF Orientation Tracking with IMUs`_
.. _Wetzstein 2017: 3-DOF Orientation Tracking with IMUs:
https://pdfs.semanticscholar.org/5568/e2100cab0b573599accd2c77debd05ccf3b1.pdf
Parameters
----------
a : array-like, shape (N, 3)
Acceleration measurements (in arbitrary units).
w : array-like, shape (N, 3)
Angular velocity measurements (in rad/s).
t : array-like, shape (N,)
Timestamps of the measurements (in s).
alpha : float, default 0.9
Weight of the angular velocity measurements in the estimate.
g_ref : tuple, len 3, default (0., 0., 1.)
Unit vector denoting direction of gravity.
theta_min : float, default 1e-6
Minimal angular velocity after filtering. Values smaller than this
will be considered noise and are not used for the estimate.
highpass : float, default .01
Cutoff frequency of the high-pass filter for the angular velocity as
fraction of Nyquist frequency.
lowpass : float, default .05
Cutoff frequency of the low-pass filter for the linear acceleration as
fraction of Nyquist frequency.
Returns
-------
q : array of quaternions, shape (N,)
The estimated orientation for each measurement.
"""
# initialize some things
N = len(t)
dt = np.diff(t)
g_ref = np.array(g_ref)
q = np.ones(N, dtype=quaternion)
# get high-passed angular velocity
w = filtfilt(*butter(5, highpass, btype='high'), w, axis=0)
w[np.linalg.norm(w, axis=1) < theta_min] = 0
q_delta = from_rotation_vector(w[1:] * dt[:, None])
# get low-passed linear acceleration
a = filtfilt(*butter(5, lowpass, btype='low'), a, axis=0)
for i in range(1, N):
# get rotation estimate from gyroscope
q_w = q[i - 1] * q_delta[i - 1]
# get rotation estimate from accelerometer
v_world = rotate_vectors(q_w, a[i])
n = np.cross(v_world, g_ref)
phi = np.arccos(np.dot(v_world / np.linalg.norm(v_world), g_ref))
q_a = from_rotation_vector(
(1 - alpha) * phi * n[None, :] / np.linalg.norm(n))[0]
# fuse both estimates
q[i] = q_a * q_w
return q
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment