How to measure the tilt of the phone in XY plane using accelerometer in Android
Asked Answered
P

3

38

I tried to use the Z axis data from SensorEvent.values, but it doesn't detect rotation of my phone in the XY plane, ie. around the Z-axis.

I am using this as a reference for the co-ordinate axes. Is it correct?

axes

How do I measure that motion using accelerometer values?

These games do something similar: Extreme Skater, Doodle Jump.

PS: my phone orientation will be landscape.

Priscian answered 24/6, 2012 at 7:4 Comment(0)
R
76

Essentially, there is 2 cases here: the device is laying flat and not flat. Flat here means the angle between the surface of the device screen and the world xy plane (I call it the inclination) is less than 25 degree or larger than 155 degree. Think of the phone lying flat or tilt up just a little bit from a table.

First you need to normalize the accelerometer vector.
That is if g is the vector returns by the accelerometer sensor event values. In code

float[] g = new float[3]; 
g = event.values.clone();

double norm_Of_g = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);

// Normalize the accelerometer vector
g[0] = g[0] / norm_Of_g
g[1] = g[1] / norm_Of_g
g[2] = g[2] / norm_Of_g

Then the inclination can be calculated as

int inclination = (int) Math.round(Math.toDegrees(Math.acos(g[2])));

Thus

if (inclination < 25 || inclination > 155)
{
    // device is flat
}
else
{
    // device is not flat
}

For the case of laying flat, you have to use a compass to see how much the device is rotating from the starting position.

For the case of not flat, the rotation (tilt) is calculated as follow

int rotation = (int) Math.round(Math.toDegrees(Math.atan2(g[0], g[1])));

Now rotation = 0 means the device is in normal position. That is portrait without any tilt for most phone and probably landscape for tablet. So if you hold a phone as in your picture above and start rotating, the rotation will change and when the phone is in landscape the rotation will be 90 or -90 depends on the direction of rotation.

Rhombic answered 1/3, 2013 at 2:23 Comment(17)
Brilliant! Usually people suggest using Sensor.TYPE_MAGNETIC_FIELD as well for this task, however you simplified the solution; and by the way accelerometer is the only sensor guaranteed to be present on Android.Stride
Can i ask you what is the math behind the "Normalization of the acc vector?" and why you do atan(g[1]/g[0]) ( or atan(y/x) ) to get the degrees? ? @Hoan NguyenAplanatic
@AndreaBaccega I forgot why I need to normalize or there is no need at all, just something I need to do for other thing. The atan(y/x) is just simple trigonometric calculation. To find the rotation, first you need to project the gravity to the xy plane. Now if there is no rotation this projection vector will have coordinate (0,1) (assuming normalization). If the device is rotated, this vector is the same but the coordinates change and the angle between this vector and the device y-coordinate is just tan(y/x).Rhombic
@AndreaBaccega As far as I understood normalization is required to bring down the values in the range on -1 to 1, which is the acceptable range for Math.acos(), for numbers lying outside this range it returns NAN. There are some links if someone is interested: in.mathworks.com/help/matlab/ref/… mathopenref.com/arccos.htmlFluorosis
brilliant solution and clean explainingConnected
can anybody tell, how to find angle when the "device is flat position"? Do I want to use "TYPE_MAGNETIC_FIELD"? "TYPE_MAGNETIC_FIELD" is not working in Lollipop(moto e). How to I do this?Tristich
@Karthi For rotation about the device z-axis when the device is flat you need to calculate the azimuth and hence the need for TYPE_MAGNETIC_FIELD, without it you are out of luck.Rhombic
Great solution, the only mention that I have to make is regarding some false values when moving fast the phone along one axis. But this can be easily filtered.Sonnier
i know this is already past 3 years, but, can anyone pointing me out to some reference about this 'inclination' and/or math calculation about flat surface. i have a final project in college about this subject, for citation purpose. because stackverflow is not counted as reference..Decamp
HI, This solution works like charm for calculating tilt, but how to calculate the phone rotation keeping phone upright and Y axis steady? Can you please help? @HoanNguyenUndersurface
Why do you use Math.acos(g[2]) not Math.acos(g[8]) ?Sharmainesharman
@FatihOzcan There is no rotation matrix here. If you look at any linear algebra book you will see the angle between the plane containing the screen and the plane parallel to the ground is cos(g[2])Rhombic
This works like charm but there is something i wasn't able to overcome and all of the 3D compass i've downloaded so far has the same issue. If you move your device from laying flat position to standing position there occurs a difference sometimes about 10 degrees of azimuth. Wht does this occur and is there a way to fix it?Sharmainesharman
This works in Android 10 if I use Sensor.TYPE_ROTATION_VECTOR rather than accelerometer.Salpinx
Is there a way to also tell if the phone is facing up or down while laying flat? I mean if the rear camera is facing up or facing the floor.Prepositor
@Prepositor yes the sign of the z accelerometer component will tell the facing of the phone. Positive means face up and negative face down.Rhombic
Which should be used for the web: developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/… or developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/… ?Prepositor
P
5

The accelerometer is sufficient for checking if the phone is flat as Hoan very nicely demonstrated.

For anyone who arrives here looking to not only check if the phone flat, but what the rotation of the phone is, it can be achieved through the Rotation Vector Motion Sensor.

private double pitch, tilt, azimuth;

@Override
public void onSensorChanged(SensorEvent event) {
    //Get Rotation Vector Sensor Values
    double[] g = convertFloatsToDoubles(event.values.clone());

    //Normalise
    double norm = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2] + g[3] * g[3]);
    g[0] /= norm;
    g[1] /= norm;
    g[2] /= norm;
    g[3] /= norm;

    //Set values to commonly known quaternion letter representatives
    double x = g[0];
    double y = g[1];
    double z = g[2];
    double w = g[3];

    //Calculate Pitch in degrees (-180 to 180)
    double sinP = 2.0 * (w * x + y * z);
    double cosP = 1.0 - 2.0 * (x * x + y * y);
    pitch = Math.atan2(sinP, cosP) * (180 / Math.PI);

    //Calculate Tilt in degrees (-90 to 90)
    double sinT = 2.0 * (w * y - z * x);
    if (Math.abs(sinT) >= 1)
        tilt = Math.copySign(Math.PI / 2, sinT) * (180 / Math.PI);
    else
        tilt = Math.asin(sinT) * (180 / Math.PI);

    //Calculate Azimuth in degrees (0 to 360; 0 = North, 90 = East, 180 = South, 270 = West)
    double sinA = 2.0 * (w * z + x * y);
    double cosA = 1.0 - 2.0 * (y * y + z * z);
    azimuth = Math.atan2(sinA, cosA) * (180 / Math.PI);
}

private double[] convertFloatsToDoubles(float[] input)
{
    if (input == null)
        return null;

    double[] output = new double[input.length];

    for (int i = 0; i < input.length; i++)
        output[i] = input[i];

    return output;
}

Then to check if the phone is flat you can simply compare the tilt and pitch values with a tolerance values. For example

public boolean flatEnough(double degreeTolerance) {
    return tilt <= degreeTolerance && tilt >= -degreeTolerance && pitch <= degreeTolerance && pitch >= -degreeTolerance;
}

The advantage to doing it this way is you can check if the phone is being held in any specific rotation.

It is worth noting that the app's orientation will not affect the values of pitch, tilt, and azimuth.

Pergola answered 25/2, 2018 at 20:10 Comment(3)
Why is the tilt between -90 to 90? What if i need to find / tilt vs \ tilt?Approximal
when i tried to compile this it failed with an ArrayIndexOutOfBounds on g[3] as length was 3... the solution i came up with for producing a value from 90 (vertical pointing upwards) to -90 (vertical pointing downwards) with 0 meaning the phone was in a horizontal position was replacing the sinT equation for sinT = (g[1] - g[2] * g[0]).toDouble() Springwood
probably late to the party, but still... I have an issue with tilt left/right in this solution (and with @Borovez as well). When device is in portrait, I can look at variation of tilt angle and detect if device is being right or left tilted. When device is in landscape, whether the tilt is left or right, the tilt angle always goes towards 0° (whether from 90° or -90°). How can I detect left/right tilt motion in landscape? ThanksOresund
S
3

Working off of the perfect response from @Dan

He missed a very slight bit of information that @davy307 pointed out.

When initializing the mAccelerometer, you must define it as Sensor.TYPE_ROTATION_VECTOR otherwise, it will not have the 3rd rotation vector and throw an ArrayIndexOutOfBounds exception.

mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);

Otherwise, this is a perfect solution... Appreciated!

Steere answered 25/4, 2019 at 3:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.