Ejemplo n.º 1
0
    def run(self, s1_a, s2_a, s1_w, s2_w, s1_m, s2_m, dt):
        """
        Run the SSRO algorithm on the data from two body-segment adjacent sensors. Rotation quaternion
        is from the second sensor to the first sensor.

        Parameters
        ----------
        s1_w : numpy.ndarray
            Sensor 1 angular velocity in rad/s.
        s2_w : numpy.ndarray
            Sensor 2 angular velocity in rad/s.
        s1_a : numpy.ndarray
            Sensor 1 acceleration in m/s^2.
        s2_a : numpy.ndarray
            Sensor 2 acceleration in m/s^2.
        s1_m : numpy.ndarray
            Sensor 1 magnetometer readings.
        s2_m : numpy.ndarray
            Sensor 2 magnetometer readings.
        dt : float, optional
            Sample rate in seconds.

        Returns
        -------
        q : numpy.ndarray
            Rotation quaternion containing the rotation from sensor 2's frame to that of sensor 1's
        """
        # initialize values
        self.x = zeros(10)  # state vector
        g1_init = mean(s1_a[:self.init_window, :], axis=0)
        g2_init = mean(s2_a[:self.init_window, :], axis=0)
        self.x[:3] = g1_init / norm(g1_init)
        self.x[3:6] = g2_init / norm(g2_init)

        m1_init = mean(s1_m[:self.init_window, :], axis=0)
        m2_init = mean(s2_m[:self.init_window, :], axis=0)
        mg1_init = m1_init - sum(m1_init * self.x[0:3]) * self.x[0:3]
        mg2_init = m2_init - sum(m2_init * self.x[3:6]) * self.x[3:6]

        q_g = utility.vec2quat(self.x[3:6], self.x[:3])
        q_m = utility.vec2quat(utility.quat2matrix(q_g) @ mg2_init, mg1_init)
        self.x[6:] = utility.quat_mult(q_m, q_g)

        self.P = identity(10) * 0.01
        self.a1, self.a2 = zeros(3), zeros(3)
        self.a1_, self.a2_ = zeros(s1_w.shape), zeros(s1_w.shape)
        self.eps1, self.eps2 = zeros((3, 3)), zeros((3, 3))

        # storage for the states
        self.x_ = zeros((s1_w.shape[0], 10))

        # run the update step over all the data
        for i in range(s1_w.shape[0]):
            self.update(s1_a[i, :], s2_a[i, :], s1_w[i, :], s2_w[i, :],
                        s1_m[i, :], s2_m[i, :], dt, i)
            self.x_[i] = self.x
            self.a1_[i] = self.a1
            self.a2_[i] = self.a2

        return self.x_
Ejemplo n.º 2
0
    def run(self, s1_a, s2_a, s1_w, s2_w, s1_h, s2_h, dt):
        """
        Run the filter on the data from the joint. Rotate from sensor 2 to sensor 1

        Parameters
        ----------
        s1_a : numpy.ndarray
            Nx3 array of accelerations from the first sensor.
        s2_a : numpy.ndarray
            Nx3 array of accelerations from the second sensor
        s1_w : numpy.ndarray
            Nx3 array of angular velocities from the first sensor.
        s2_w : numpy.ndarray
            Nx3 array of angular velocities from the second sensor.
        s1_h : numpy.ndarray
            Nx3 array of magnetometer readings from the first sensor.
        s2_h : numpy.ndarray
            Nx3 array of magnetometer readings from the second sensor.
        dt : float
            Sampling period in seconds.

        Returns
        -------
        q : numpy.ndarray
            Nx4 array of quaternions representing the rotation from sensor 2 to sensor 1.
        """
        # number of elements
        n = s1_a.shape[0]

        # get the mean of the first few samples of acceleration which will be used to compute an initial guess for the
        # gravity vector esimation.
        s1_a0 = mean(s1_a[:self.init_window], axis=0)
        s2_a0 = mean(s2_a[:self.init_window], axis=0)

        # get the rotation from the initial accelerations to the global gravity vector
        s1_q_init = utility.vec2quat(s1_a0, array([0, 0, 1]))
        s2_q_init = utility.vec2quat(s2_a0, array([0, 0, 1]))

        # setup the AHRS algorithms
        s1_ahrs = MadgwickAHRS(dt, q_init=s1_q_init, beta=self.s1_ahrs_beta)
        s2_ahrs = MadgwickAHRS(dt, q_init=s2_q_init, beta=self.s2_ahrs_beta)

        # run the algorithms
        s1_q = zeros((n, 4))
        s2_q = zeros((n, 4))
        for i in range(n):
            s1_q[i] = s1_ahrs.updateIMU(s1_w[i], s1_a[i] / self.g)
            s2_q[i] = s2_ahrs.updateIMU(s2_w[i], s2_a[i] / self.g)

        # get the gravity axis from the rotation matrices
        s1_z = utility.quat2matrix(s1_q)[:, 2, :]
        s2_z = utility.quat2matrix(s2_q)[:, 2, :]

        # get the magnetometer readings that are not in the direction of gravity
        s1_m = s1_h - sum(s1_h * s1_z, axis=1, keepdims=True) * s1_z
        s2_m = s2_h - sum(s2_h * s2_z, axis=1, keepdims=True) * s2_z

        # get the initial guess for the orientation.  Used to initialize the UKF
        # Initial guess from aligning gravity axes
        q_z_init = utility.vec2quat(s2_z[0], s1_z[0])
        # Initial guess from aligning non-gravity vector magnetometer readings
        q_m_init = utility.vec2quat(
            utility.quat2matrix(q_z_init) @ s2_m[0], s1_m[0])

        q_init = utility.quat_mult(q_m_init, q_z_init)

        # Initial Kalman Filter parameters
        P_init = identity(4) * 0.1  # assume our initial guess is fairly good
        R = identity(4)  # Measurement will have some error

        ukf = UnscentedKalmanFilter(q_init, P_init, OldSROFilter._F,
                                    OldSROFilter._H, self.Q, R)

        self.q_ = zeros((n, 4))  # storage for ukf output
        self.q_[0] = q_init  # store the initial guess
        self.err = zeros(n)
        for i in range(1, n):
            # get the measurement guess for the orientation
            q_z = utility.vec2quat(s2_z[i], s1_z[i])
            q_m = utility.vec2quat(utility.quat2matrix(q_z) @ s2_m[i], s1_m[i])

            q = utility.quat_mult(q_m, q_z).reshape((4, 1))

            # get an estimate of the measurement error, based on how dynamic the motion is
            err = abs(norm(s1_a[i]) - self.g) + abs(norm(s2_a[i]) - self.g)
            if self.error_mode == 'linear':
                err *= self.error_factor
            elif self.error_mode == 'exp' or self.error_mode == 'exponential':
                err = err**self.error_factor
            else:
                raise ValueError(
                    'error_mode must either be linear or exp (exponential)')
            self.err[i] = err
            # update the measurement noise matrix based upon this error estimate
            ukf.R = identity(4) * err

            self.q_[i] = ukf.run(q,
                                 f_kwargs=dict(s1_w=s1_w[i],
                                               s2_w=s2_w[i],
                                               dt=dt)).flatten()
            self.q_[i] /= norm(
                self.q_[i]
            )  # output isn't necessarily normalized as rotation quaternions need to be

        if self.smooth:
            q = self.q_.copy()
            pad = 16
            for i in range(pad, self.q_.shape[0] - pad):
                self.q_[i] = utility.quat_mean(q[i - pad:i + pad, :])

        return self.q_
Ejemplo n.º 3
0
    def update(self, y_a1, y_a2, y_w1, y_w2, y_m1, y_m2, dt, j):
        """
        Update the state vector and covariance for the next time point.
        """
        # short hand names for parts of the state vector
        g1 = self.x[:3]
        g2 = self.x[3:6]
        q_21 = self.x[6:]
        # compute the difference in angular velocities in the second sensor's frame
        R_21 = utility.quat2matrix(q_21)  # rotation from sensor 2 to sensor 1
        y_w1_2 = R_21.T @ y_w1  # sensor 1 angular velocity in sensor 2 frame
        diff_y_w = y_w2 - y_w1_2

        # create the state transition matrix
        A = zeros((10, 10))
        A[0:3, 0:3] = identity(3) - dt * SSRO._skew(y_w1)
        A[3:6, 3:6] = identity(3) - dt * SSRO._skew(y_w2)
        A[6:, 6:] = identity(4) - 0.5 * dt * SSRO._skew_quat(diff_y_w)

        # state transition covariance
        Q = zeros((10, 10))
        Q[0:3, 0:3] = -dt**2 * SSRO._skew(g1) @ (self.sigma_g**2 *
                                                 identity(3)) @ SSRO._skew(g1)
        Q[3:6, 3:6] = -dt**2 * SSRO._skew(g2) @ (self.sigma_g**2 *
                                                 identity(3)) @ SSRO._skew(g2)
        Q[6:, 6:] = -dt**2 * SSRO._skew_quat(q_21) @ (
            self.sigma_g**2 * identity(4)) @ SSRO._skew_quat(q_21)

        # predicted state and covariance
        xhat = A @ self.x
        Phat = A @ self.P @ A.T + Q

        # create the measurements
        zt = zeros(10)
        zt[0:3] = y_a1 - self.c * self.a1
        zt[3:6] = y_a2 - self.c * self.a2
        # quaternion measurement
        q_g = utility.vec2quat(g2, g1)
        mg1 = y_m1 - sum(y_m1 * g1) * g1
        mg2 = y_m2 - sum(y_m2 * g2) * g2
        q_m = utility.vec2quat(utility.quat2matrix(q_g) @ mg2, mg1)

        zt[6:] = utility.quat_mult(q_m, q_g)

        # measurement estimation matrix
        H = zeros((10, 10))
        H[:6, :6] = identity(6) * self.grav
        H[6:, 6:] = identity(4)

        # update estimates of the acceleration variance
        self.eps1 += self.c**2 / self.N * outer(self.a1, self.a1)
        self.eps1 -= self.c**2 / self.N * outer(self.a1_[j - self.N],
                                                self.a1_[j - self.N])
        self.eps2 += self.c**2 / self.N * outer(self.a2, self.a2)
        self.eps2 -= self.c**2 / self.N * outer(self.a2_[j - self.N],
                                                self.a2_[j - self.N])

        # measurement covariance - essentially how much we trust the measurement vector
        M = zeros((10, 10))
        M[0:3, 0:3] = self.sigma_a**2 * identity(3) + self.eps1
        M[3:6, 3:6] = self.sigma_a**2 * identity(3) + self.eps2
        M[6:,
          6:] = identity(4) * self.err_factor * (norm(self.a1) + norm(self.a2))

        # kalman gain and state estimation
        K = Phat @ H.T @ np_inv(H @ Phat @ H.T + M)
        xbar = xhat + K @ (zt - H @ xhat)  # not yet normalized
        self.P = (identity(10) - K @ H) @ Phat

        # normalize the state vector
        self.x[0:3] = xbar[0:3] / norm(xbar[0:3])
        self.x[3:6] = xbar[3:6] / norm(xbar[3:6])
        self.x[6:] = xbar[6:] / norm(xbar[6:])

        # acceleration values
        self.a1 = y_a1 - self.x[0:3] * self.grav
        self.a2 = y_a2 - self.x[3:6] * self.grav