28

From my Android device I can read an array of linear acceleration values (in the device's coordinate system) and an array of absolute orientation values (in Earth's coordinate system). What I need is to obtain the linear acceleration values in the latter coord. system.

How can I convert them?

EDIT after Ali's reply in comment:

All right, so if I understand correctly, when I measure the linear acceleration, the position of the phone completely does not matter, because the readings are given in Earth's coordinate system. right?

But I just did a test where I put the phone in different positions and got acceleration in different axes. There are 3 pairs of pictures - the first ones show how I put the device (sorry for my Paint "master skill") and the second ones show readings from data provided by the linear acc. sensor:

  1. device put on left side

first position first readings

  1. device lying on back

second position second readings

  1. device standing

enter image description here enter image description here

And now - why in the third case the acceleration occurs along the Z axis (not Y) since the device position doesn't matter?

alex
  • 9,184
  • 12
  • 64
  • 86
  • Was the phone stationary (completely still) when you made the measurements? What is on the vertical axis? What does, for example -14 mean? – Ali Jul 20 '12 at 13:54
  • during the measurements I was moving the phone very fast along the West-East axis (just as shown on those Paint drawings) to create the acceleration readings (-14 is the acceleration in m/s*s). And as shown here [link](http://stackoverflow.com/questions/4174389/different-values-between-sensors-type-accelerometer-type-magnetic-field-and-type) the measurements coordinate system IS the phone's one. – alex Jul 20 '12 at 14:15
  • OK, now I understand your experiment. I will fix my answer. – Ali Jul 20 '12 at 14:53
  • OK, it was my mistake, I am terribly sorry. I have fixed my answer. Also, I have upvoted your question. – Ali Jul 20 '12 at 15:02
  • what is the graph tool you are using? – Muhammad Babar Nov 05 '14 at 11:26
  • @MuhammadBabar that's just MS Excel – alex Nov 05 '14 at 11:33
  • how did you managed to create it? are you using any software that pickups the sensor reading and creating that charts? – Muhammad Babar Nov 05 '14 at 11:36
  • 1
    @MuhammadBabar programmed to save the readings to file on the device, and just manually imported into excel. nothing fancy – alex Nov 05 '14 at 15:08
  • @alex Oh right! thanks anyway :) – Muhammad Babar Nov 06 '14 at 05:32
  • Would it be actually better to use Sensor.TYPE_GAME_ROTATION if only an absolute coordinate system is desired, neglecting the true geomagnetic north? – SOFe Mar 27 '17 at 06:01

4 Answers4

44

I finally managed to solve it! So to get acceleration vector in Earth's coordinate system you need to:

  1. get rotation matrix (float[16] so it could be used later by android.opengl.Matrix class) from SensorManager.getRotationMatrix() (using SENSOR.TYPE_GRAVITY and SENSOR.TYPE_MAGNETIC_FIELD sensors values as parameters),
  2. use android.opengl.Matrix.invertM() on the rotation matrix to invert it (not transpose!),
  3. use Sensor.TYPE_LINEAR_ACCELERATION sensor to get linear acceleration vector (in device's coord. sys.),
  4. use android.opengl.Matrix.multiplyMV() to multiply the rotation matrix by linear acceleration vector.

And there you have it! I hope I will save some precious time for others.

Thanks for Edward Falk and Ali for hints!!

alex
  • 9,184
  • 12
  • 64
  • 86
  • Hmm, that's odd. I thought that the inverse of a matrix comprised entirely of rotations *is* the transpose. I guess I need to play with this a little. – Edward Falk Jul 24 '12 at 18:18
  • 2
    In step 4 you cannot use sensor data directly since it's length is 3 and you need length 4. Do you add a 0 or a 1 at the end, or at the start? – Nino van Hooff Sep 14 '12 at 09:19
  • 2
    @NinovanHooff at the 4th position (array[3]) there should be 0 value – alex Sep 14 '12 at 10:57
  • What exactly is your earth coordinate system? Are you referring to this one in the docs of the `Sensor.TYPE_ROTATION_VECTOR` here http://developer.android.com/reference/android/hardware/SensorEvent.html#values? – htz Feb 20 '13 at 12:10
  • @htz I obviously mean the most common system, the one everybody uses when reading a map, for example. One unit vector pointing north, second one pointing east and the last one headed towards Earth center. The order and direction (e.g. west/east) of vectors may vary, but the point is still the same - to use this **fixed** system as a reference for position changes of other systems, e.g. mobile phone. – alex Feb 20 '13 at 16:27
  • Your solution seems complicated. See my simple solution at http://stackoverflow.com/questions/14963190/calculate-acceleration-in-reference-to-true-north/14988559#14988559 – Hoan Nguyen Feb 20 '13 at 19:46
  • Except from the inversion of the RotationMatrix, the solutions are the same. Why this inversion is made in your solution @alex? I am not familiar enought with this arithmetics... – htz Feb 22 '13 at 11:54
  • 2
    The inversion is made probably because the rotation matrix is computed according to the world axis, instead of the phone axis, as was mentioned here(http://gentlenav.googlecode.com/files/DCMDraft2.pdf). Here's a scientific paper describing the same approach in section III.A (http://www.ece.nus.edu.sg/stfpage/elesohws/PerCom_Pedestrian_Tracking.pdf) – ravemir Apr 16 '13 at 18:25
  • Is a step missing? The Pedestrian article says you need to do more than just invert. It mentions something about "reversed order": "In order to obtain the acceleration values in the world coordinate system, we multiply the inverse of the corresponding rotation matrices in the reversed order (inverse pitch, inverse roll, and inverse azimuth) to the acceleration vector reported in the local coordinate system." – gregm Sep 13 '13 at 19:56
  • 2
    @alex could you post the code where you correct the linear_acceleration to world coordinates? My sensor listener is creating a rotation matrix using the Sensor.TYPE_ROTATION_VECTOR and it is working great, but i can't seem to correct the linear_acceleration the way you describe.. code sample would be awesome – erik Jun 24 '14 at 23:23
  • @alex any idea how to do this on IPHONE? – sau Oct 21 '14 at 09:28
  • 1
    @sau it should be somewhat similar but iPhone API is totally different so... I guess you have to crack it yourself – alex Oct 21 '14 at 09:54
  • Could you add some example-code so one sees the actual calculations? Including declaration of all variables. Thanks! – helmesjo Apr 22 '15 at 12:22
  • @Alex As far as I understand, this change the reference to be aligned on magnetic north and gravity. To get it align with true north, you should rotate around the Z axis by the declination. To get the declination use `GeomagneticField.getDeclination()` – Kristian Benoit May 04 '15 at 19:49
  • Hi Alex, Can you add the code for this calculation? thanks, Eyal. – eyal May 10 '15 at 14:51
  • 1
    why did you use the `SENSOR.TYPE_GRAVITY` in `SensorManager.getRotationMatrix()` ? The documentation (http://developer.android.com/reference/android/hardware/SensorManager.html#getRotationMatrix(float[], float[], float[], float[])) says to use the values of the `TYPE_ACCELEROMETER` – orak Apr 07 '16 at 11:13
  • is it possible to calculate it when device has no linear acceleration nor gravity sensor? – Androider Jul 27 '16 at 08:23
  • @alex I tried to follow the steps here but I don't seem to get earth co-ordinates. Can you take a look? https://stackoverflow.com/questions/46146602/normalizing-mobile-accelerometer-x-y-z-to-earth-co-ordinates-in-javascript-not-w – user1361529 Sep 11 '17 at 13:20
17

Based on @alex's answer, here is the code snippet:

private float[] gravityValues = null;
private float[] magneticValues = null;

 @Override
    public void onSensorChanged(SensorEvent event) {
        if ((gravityValues != null) && (magneticValues != null) 
            && (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)) {

            float[] deviceRelativeAcceleration = new float[4];
            deviceRelativeAcceleration[0] = event.values[0];
            deviceRelativeAcceleration[1] = event.values[1];
            deviceRelativeAcceleration[2] = event.values[2];
            deviceRelativeAcceleration[3] = 0;

            // Change the device relative acceleration values to earth relative values
            // X axis -> East
            // Y axis -> North Pole
            // Z axis -> Sky

            float[] R = new float[16], I = new float[16], earthAcc = new float[16];

            SensorManager.getRotationMatrix(R, I, gravityValues, magneticValues);

            float[] inv = new float[16];

            android.opengl.Matrix.invertM(inv, 0, R, 0);
            android.opengl.Matrix.multiplyMV(earthAcc, 0, inv, 0, deviceRelativeAcceleration, 0);
           Log.d("Acceleration", "Values: (" + earthAcc[0] + ", " + earthAcc[1] + ", " + earthAcc[2] + ")");                

        } else if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
            gravityValues = event.values;
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            magneticValues = event.values;
        } 
   }
orak
  • 2,159
  • 5
  • 27
  • 51
  • 1
    It worked! Thanks @orak ! I have no gravitysensor type so I just used low-pass filter to get it. – Kanat Aug 26 '16 at 07:19
  • I'm sorry @orak, but what is "started" variable is all about ? To control the flow? i'm not sure about it. – Saeger Dec 06 '16 at 21:04
  • 1
    @Saeger you are right, it isn't needed, I probably wrote it for flow control, which I didn't need later on – orak Jan 01 '17 at 03:01
  • @orak could you please explain what if I should do if I want to have X axis pointing towards true north and Z vertical? – pixie Jan 24 '17 at 22:28
  • @pixie just swap the x and y values ? – orak Oct 09 '17 at 06:49
4

According to the documentation you get the linear acceleration in the phone's coordinate system.

You can transform any vector from the phone's coordinate system to the Earth's coordinate system by multiplying it with the rotation matrix. You can get the rotation matrix from getRotationMatrix().

(Perhaps there already is a function doing this multiplication for you but I don't do Android programming and I am not familiar with its API.)

A nice tutorial on the rotation matrix is the Direction Cosine Matrix IMU: Theory manuscript. Good luck!

Ali
  • 51,545
  • 25
  • 157
  • 246
  • Thanks for your answer! It seems like it should work properly, but I have no idea why it does not.. Please, have a look at my comment to Edward Falk's answer, I explained there what's still wrong. – alex Jul 23 '12 at 09:19
1

OK, first of all, if you're trying to do actual inertial navigation on Android, you've got your work cut out for you. The cheap little sensor used in smart phones are just not precise enough. Although, there has been some interesting work done on intertial navigation over small distances, such as inside a building. There are probably papers on the subject you can dig up. Google "Motion Interface Developers Conference" and you might find something useful -- that's a conference that Invensense put on a couple months ago.

Second, no, linear acceleration is in device coordinates, not world coordinates. You'll have to convert yourself, which means knowing the device's 3-d orientation.

What you want to do is use a version of Android that supports the virtual sensors TYPE_GRAVITY and TYPE_LINEAR_ACCELERATION. You'll need a device with gyros to get reasonably accurate and precise readings.

Internally, the system combines gyros, accelerometers, and magnetometers in order to come up with true values for the device orientation. This effectively splits the accelerometer device into its gravity and acceleration components.

So what you want to do is to set up sensor listeners for TYPE_GRAVITY, TYPE_LINEAR_ACCELERATION, and TYPE_MAGNETOMETER. Use the gravity and magnetometer data as inputs to SensorManager. getRotationMatrix() in order to get the rotation matrix that will transform world coordinates into device coordinates or vice versa. In this case, you'll want the "versa" part. That is, convert the linear acceleration input to world coordinates by multiplying them by the transpose of the orientation matrix.

Edward Falk
  • 9,578
  • 8
  • 66
  • 105
  • I did like you said - I obtained the acceleration vector A (in Earth's coord. syst.) by multiplying rotation matrix R (from `getRotationMatrix()` provided with gravity and magnetic field data) and linear acceleration vector V (from lin. acc. sensor in device's coord. sys.) like so: **A=RV**. But I didn't get good results. I put the phone in standing position (it's Y axis up) and moved it up and down. Lin. acc. sensor should indicate movement in Y axis while the transformed vector - in Z axis. But I got [vector V](http://i.imgur.com/73FoN.jpg) and [vector A](http://i.imgur.com/1C0CE.jpg). So..? – alex Jul 23 '12 at 09:14