13

What is the correct way to use CMAttitude:multiplyByInverseOfAttitude?

Assuming an iOS5 device laying flat on a table, after starting CMMotionManager with:

CMMotionManager *motionManager = [[CMMotionManager alloc]init];
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
    CMAttitudeReferenceFrameXTrueNorthZVertical];

Later, CMDeviceMotion objects are obtained:

CMDeviceMotion *deviceMotion = [motionManager deviceMotion];

I expect that [deviceMotion attitude] reflects the rotation of the device from True North.

By observation, [deviceMotion userAcceleration] reports acceleration in the device reference frame. That is, moving the device side to side (keeping it flat on the table) registers acceleration in the x-axis. Turning the device 90° (still flat) and moving the device side to side still reports x acceleration.

What is the correct way to transform [deviceMotion userAcceleration] to obtain North-South/East-West acceleration rather than left-right/forward-backward?

CMAttitude multiplyByInverseOfAttitude seems unnecessary since a reference frame has already been specified and it is unclear from the documentation how to apply the attitude to CMAcceleration.

Zaq
  • 1,071
  • 2
  • 10
  • 19

4 Answers4

24

The question would not have arisen if CMDeviceMotion had an accessor for the userAcceleration in coordinates of the reference frame. So, I used a category to add the required method:

In CMDeviceMotion+TransformToReferenceFrame.h:

#import <CoreMotion/CoreMotion.h>

@interface CMDeviceMotion (TransformToReferenceFrame)
-(CMAcceleration)userAccelerationInReferenceFrame;
@end

and in CMDeviceMotion+TransformToReferenceFrame.m:

#import "CMDeviceMotion+TransformToReferenceFrame.h"

@implementation CMDeviceMotion (TransformToReferenceFrame)

-(CMAcceleration)userAccelerationInReferenceFrame
{
    CMAcceleration acc = [self userAcceleration];
    CMRotationMatrix rot = [self attitude].rotationMatrix;

    CMAcceleration accRef;
    accRef.x = acc.x*rot.m11 + acc.y*rot.m12 + acc.z*rot.m13;
    accRef.y = acc.x*rot.m21 + acc.y*rot.m22 + acc.z*rot.m23;
    accRef.z = acc.x*rot.m31 + acc.y*rot.m32 + acc.z*rot.m33;

    return accRef;
}

@end

and in Swift 3

extension CMDeviceMotion {

    var userAccelerationInReferenceFrame: CMAcceleration {
        let acc = self.userAcceleration
        let rot = self.attitude.rotationMatrix

        var accRef = CMAcceleration()
        accRef.x = acc.x*rot.m11 + acc.y*rot.m12 + acc.z*rot.m13;
        accRef.y = acc.x*rot.m21 + acc.y*rot.m22 + acc.z*rot.m23;
        accRef.z = acc.x*rot.m31 + acc.y*rot.m32 + acc.z*rot.m33;

        return accRef;
    }
}

Now, code that previously used [deviceMotion userAcceleration] can use [deviceMotion userAccelerationInReferenceFrame] instead.

zirinisp
  • 9,971
  • 5
  • 32
  • 38
Zaq
  • 1,071
  • 2
  • 10
  • 19
  • This is interesting, nice work. I assume you are using it in conjunction with the compass (CLHeading). What are you doing with the numbers you get from the acceleration? – Kevin_TA Jun 14 '12 at 17:55
  • In theory, initialising with CMAttitudeReferenceFrameXTrueNorthZVertical means that the compass heading is already considered. In general, there are many interesting uses for the world acceleration rather than device acceleration. – Zaq Jun 18 '12 at 07:36
  • @Zaq Can i use this concept to rotational rate data(rotate.x,rotate.y,rotate.z) and for the reading of yaw of the iphone? Thanks in advance! – sam Jul 16 '12 at 17:20
  • 1
    Your answer is incorrect. In order to move a vector from reference frame A to reference frame B, when rotation matrix from B to A is given, the vector should be multiplied by inverse rotation matrix. Here is how you calculate the inverse matrix http://stackoverflow.com/a/984054/204533 – Vitaly Stakhov Mar 10 '13 at 10:27
  • 4
    @Vitaly, thanks for the feedback. The code is operational and working fine; have you tried it? Your link gives a method for inverting an arbitrary matrix but "the inverse of a rotation matrix is simply its transpose" (http://mathpages.com/home/kmath593/kmath593.htm). It's subtle, but the code transposes the rotation matrix in the calculation. – Zaq Mar 11 '13 at 08:03
  • 2
    @Zaq, I'm taking my words back, you're right. Took a look at my rotation matrix inversion code, which is an arbitrary matrix inversion code, and it produces the transposed matrix. Apparently rotation matrix inverse is its transpose because of the two facts: a) determinant is always 1 as vectors don't scale when rotate and b) rotation matrix is an orthogonal matrix. – Vitaly Stakhov Mar 18 '13 at 18:56
  • Thank you. Finally found it. – zirinisp Feb 14 '14 at 18:39
  • Now CMDeviceMotion has UserAcceleration property.. am i right.. so above code will do the same as the one provided by apple ? – nr5 Mar 10 '15 at 11:32
  • ca I do this for pitch and roll also ? – nr5 Jul 21 '15 at 11:01
  • I agree with @VitalyStakhov, the code posted here is incorrect. According to Apple's documentation. If you multiply the rotation matrix obtained from a CMAttitude object by the gravity reference, you get gravity in the device's frame. So in order to get the reference frame value, we should inverse rotation matrix and multiply by the acceleration value from device motion – xmkevinchen Mar 29 '16 at 19:18
  • 1
    @xmkevinchen, Thanks for your interest. The code does exactly what you suggest. As VitalyStakhov came to realise, the code transposes the rotation matrix in the calculation. – Zaq Apr 08 '16 at 07:17
  • @Zaq, the key point here is if we want to project the x,y,z to global frame, instead of using the matrix directly, we should use its inverse matrix. Because Current X = MX', but what we want is X' – xmkevinchen Apr 08 '16 at 17:27
  • @xmkevinchen, inverting an arbitrary matrix can be complicated, but for a rotation matrix, all you need to do is transpose it. It's subtle, but the code transposes the rotation matrix in the calculation. – Zaq Apr 11 '16 at 01:54
  • @Zaq Great code! But I don't understand how to use this data. Is the acceleration from this still in m/s^2? And how does it change relative to North/South/East/West or the device's orientation? – Armin May 23 '16 at 21:43
  • Armin, been a year so you probably learned, but let's pretend your phone is accelerating north at 1m/s^2. If you rotate your phone, but keep accelerating in that direction, it *should* still say North and 1m/s^2. – Stephen J Aug 09 '17 at 19:26
4

According to Apple's Documentation, CMAttitude refers to the orientation of a body relative to a given frame of reference. And either userAcceleration or gravity is the value of the device's frame. So in order to get the value of reference frame. We should do as @Batti said

  1. take the attitude rotation matrix every update time.
  2. comput the inverse matrix.
  3. multiplying the inverse matrix for the UserAcceleration vector.

Here's the Swift version

import CoreMotion
import GLKit

extension CMDeviceMotion {

    func userAccelerationInReferenceFrame() -> CMAcceleration {

        let origin = userAcceleration
        let rotation = attitude.rotationMatrix
        let matrix = rotation.inverse()

        var result = CMAcceleration()
        result.x = origin.x * matrix.m11 + origin.y * matrix.m12 + origin.z * matrix.m13;
        result.y = origin.x * matrix.m21 + origin.y * matrix.m22 + origin.z * matrix.m23;
        result.z = origin.x * matrix.m31 + origin.y * matrix.m32 + origin.z * matrix.m33;

        return result
    }

    func gravityInReferenceFrame() -> CMAcceleration {

        let origin = self.gravity
        let rotation = attitude.rotationMatrix
        let matrix = rotation.inverse()

        var result = CMAcceleration()
        result.x = origin.x * matrix.m11 + origin.y * matrix.m12 + origin.z * matrix.m13;
        result.y = origin.x * matrix.m21 + origin.y * matrix.m22 + origin.z * matrix.m23;
        result.z = origin.x * matrix.m31 + origin.y * matrix.m32 + origin.z * matrix.m33;

        return result
    }
}

extension CMRotationMatrix {

    func inverse() -> CMRotationMatrix {

        let matrix = GLKMatrix3Make(Float(m11), Float(m12), Float(m13), Float(m21), Float(m22), Float(m23), Float(m31), Float(m32), Float(m33))
        let invert = GLKMatrix3Invert(matrix, nil)

        return CMRotationMatrix(m11: Double(invert.m00), m12: Double(invert.m01), m13: Double(invert.m02),
                            m21: Double(invert.m10), m22: Double(invert.m11), m23: Double(invert.m12),
                            m31: Double(invert.m20), m32: Double(invert.m21), m33: Double(invert.m22))

    }

}

Hope it helps a little bit

xmkevinchen
  • 1,506
  • 1
  • 14
  • 17
3

i tried to implement a solution after reading the paper linked above.

Steps are the follows:

  • take the attitude rotation matrix every update time.
  • comput the inverse matrix.
  • multiplying the inverse matrix for the UserAcceleration vector.

the resultant vector will be the projection of the vector.

-x north, +x south

-y east, +y weast

my code it's not perfect yet, i'm working on it.

Batti
  • 425
  • 4
  • 15
  • Can i do this for pitch and roll also >? – nr5 Jul 21 '15 at 11:01
  • can you share the code? or explain the above steps.. All I got is Store the attitude which you want to create as a reference, compute the inverse of the current attitude – nr5 Jul 27 '15 at 09:45
0

The reference frame is related to the attitude value, look the value of attitude of yaw angle; If you don't use a reference frame, when you start your app, this value is always zero, instead if you use the reference frame CMAttitudeReferenceFrameXTrueNorthZVertical the yaw value indicates the angle between the x-axis and true north. with this information you can identify the attitude of phone in the coordinates of the earth and therefore the position of axes of the accelerometer with respect to the cardinal points.

Batti
  • 425
  • 4
  • 15
  • 1
    Thanks @batti, I understand the geometry, the question is about coding. The API doesn't seem to support a simple approach to applying the transform. In CMAttitude, the euler angles, the quaternion representation and the rotation matrix are all easily accessed, but there's no obvious way to apply these to the CMAcceleration. Worse still, CMAcceleration is read-only, so I can't transform it myself and update the object before sending it on. – Zaq Nov 01 '11 at 03:11
  • 1
    the problem is that le axis of accelerometer are fixed with the device, so the accelerometer can measure the acceleration in the device frame. If the sensors were not so noisy, you should just find the horizontal plane of acceleration, the plane perpendicular to the vector of gravity, look the vector on this plane and calculate the angle between the vector and the north to know the direction of the acceleration in the cardinals cordinates. What does your app do? – Batti Nov 02 '11 at 10:01
  • this should be helpful, section 3.A [link](http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=5767590) – Batti Nov 02 '11 at 11:03