Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent orientation sensor values on Android for azimuth/yaw and roll

I’m having trouble getting good orientation sensor readings. The sensor readings seemed unreliable, so I tested my code against two free sensor test apps (Sensor Tester (Dicotomica) and Sensor Monitoring (R's Software)). I found that while my readings often agreed with the sensor test apps, occasionally the values for azimuth/yaw, and roll differed by up to 40 degrees, although the pitch reading mostly agreed. The two free apps always seemed to agree with each other.

I put my code into a tiny Android activity and got the same inconsistency. The code is as follows:

public class MainActivity extends Activity implements  SensorEventListener {

    private SensorManager mSensorManager;
    private float[] AccelerometerValues;
    private float[] MagneticFieldValues;
    private float[] RotationMatrix;
    private long nextRefreshTime;           // used to ensure dump to LogCat occurs no more than 4 times a second
    private DecimalFormat df;               // used for dumping sensors to LogCat

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSensorManager = (SensorManager)getSystemService(android.content.Context.SENSOR_SERVICE);
        Sensor SensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, SensorAccelerometer, SensorManager.SENSOR_DELAY_UI);  
        Sensor SensorMagField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        mSensorManager.registerListener(this, SensorMagField, SensorManager.SENSOR_DELAY_UI);
        AccelerometerValues = new float[3];
        MagneticFieldValues = new float[3];
        RotationMatrix = new float[9];
        nextRefreshTime = 0;
        df = new DecimalFormat("#.00");
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            System.arraycopy(event.values, 0, AccelerometerValues, 0, AccelerometerValues.length);
        else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
                System.arraycopy(event.values, 0, MagneticFieldValues, 0, MagneticFieldValues.length);

        if (AccelerometerValues != null && MagneticFieldValues != null) {
            if(SensorManager.getRotationMatrix(RotationMatrix, null, AccelerometerValues, MagneticFieldValues)) {
                float[] OrientationValues = new float[3];
                SensorManager.getOrientation(RotationMatrix, OrientationValues);

                // chance conventions to match sample apps
                if (OrientationValues[0] < 0) OrientationValues[0] += 2*(float)Math.PI;
                OrientationValues[2] *= -1;

                // dump to logcat 4 times a second
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis > nextRefreshTime) {
                    nextRefreshTime = currentTimeMillis+250;
                    Log.i("Sensors",    // arrange output so that numbers line up in columns :-)
                            "(" + AngleToStr(OrientationValues[0]) + "," + AngleToStr(OrientationValues[1]) + "," + AngleToStr(OrientationValues[2])
                            + ") ("+FloatToStr(AccelerometerValues[0]) + "," + FloatToStr(AccelerometerValues[1]) + "," + FloatToStr(AccelerometerValues[2])
                            + ") ("+FloatToStr(MagneticFieldValues[0]) + "," + FloatToStr(MagneticFieldValues[1]) + "," + FloatToStr(MagneticFieldValues[2])+")");
                }               
            }
        }               
    }

    private String AngleToStr(double AngleInRadians) {
        String Str = "   "+Integer.toString((int)Math.toDegrees(AngleInRadians));
        return Str.substring(Str.length() - 3);
    }
    private String FloatToStr(float flt) {
        String Str = "      "+df.format(flt);
        return Str.substring(Str.length() - 6);
    }   

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onAccuracyChanged(Sensor arg0, int arg1) { }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}

I’m using a Galaxy Note 2 running Jelly Bean 4.1.1. Can anyone tell me what I’m doing wrong?

Update 24-Mar-2013: More information. (1) I've disabled switches between portrait and landscape in the manifest, so getWindowManager().getDefaultDisplay().getRotation() is always zero. Hence I don't think remapCoordSystem would help here, because that's for switching axes, whereas the errors that I'm seeing aren't big errors, they're much more subtle. (2) I've checked the accuracy sensitivity, and the inconsistencies occur when both sensors claim to have high accuracy.

As an example of the inconsistencies that I'm seeing, when the code above give me (azimuth,pitch,roll) = (235,-52,-11) then the two free apps show similar values. But when I see (278, -58, -52) the apps show (256, -58, -26), so big differences in both Azimuth and roll, although pitch seems OK.

like image 729
gisking Avatar asked Mar 21 '13 00:03

gisking


People also ask

What sensor in mobile device track the orientation whenever the device is Rotated?

The Android platform provides two sensors that let you determine the position of a device: the geomagnetic field sensor and the accelerometer.

Which sensor depends on magnitude of direction and orientation of input parameters?

Gravity. A gravity sensor reports the direction and magnitude of gravity in the device's coordinates.

Which sensor is used to identify the position of the device during a call?

Explanation: A proximity sensor is a sensor able to detect the presence of nearby objects without any physical contact. A proximity sensor often emits an electromagnetic field or a beam of electromagnetic radiation, and looks for a change in the return signal. 2.

Which sensor usually defined as a wake up sensor?

Proximity. A proximity sensor reports the distance from the sensor to the closest visible surface. Up to Android 4.4, the proximity sensors were always wake-up sensors, waking up the SoC when detecting a change in proximity.


2 Answers

I think the best way of defining your orientation angles when the device isn't flat is to use a more appropriate angular co-ordinate system that the standard Euler angles that you get from SensorManager.getOrientation(...). I suggest the one that I describe here on math.stackexchange.com. I've also put some code that does implements it in an answer here. Apart from a good definition of azimuth, it also has a better definition of the pitch angle, which this methodology defines as rotation out of the horizontal plane irrespective of which axis the rotation occurs along.

You can get full details from the two links that I've given in the first paragraph. However, in summary, your rotation matrix R from SensorManager.getRotationMatrix(...) is

Definition of rotation matrix R

where (Ex, Ey, Ez), (Nx, Ny, Nz) and (Gx, Gy, Gz) are vectors pointing due East, North, and in the direction of Gravity. Then the azimuth angle that you want is given by

Definition of azimuth angle phi

like image 125
Stochastically Avatar answered Oct 29 '22 00:10

Stochastically


It all depends on the orientation of the device. If the device is not flat and there is some rotation ie not quite Portrait or Landscape, then the azimuth is wrong. You have to call remapCoordSystem before calling getOrientation in this case. You also should at least filter the accelerometer values.
For more detail see my answer at Convert magnetic field X, Y, Z values from device into global reference frame
And for the meaning of flatness see How to measure the tilt of the phone in XY plane using accelerometer in Android

You should test your app as follow:

  1. Lay your device flat and start your app and compare your value with the other apps. Give each reading about 30 seconds and do not move the device during this step.
    My guess for this step: Your value should differ little from the other apps values but it may be less stable in the beginning than the other apps.

  2. While your app is running, rotate your device to a new position, wait until the value become stable (does not have to be the same value, but variation is about 1 or 2 degrees). In the same position compare your value to the other apps values. Repeat the same thing but with the other apps running when rotate.
    My guess for this step: your stable values should differ little from the other apps values but it takes longer for your value to become stable. Also, when stop at the new position the difference between the first few values and the stable value are larger for your app than the other.

  3. Now put the following code before the comment //dump to logcat and log it as you do for the orientation
    float rotation = (float) Math.atan2(RotationMatrix[6], RotationMatrix[7]);
    Put your device in upright position in a box so that it can not rotate. Make sure that the value of the rotation given above is as near 0 as possible (it will jump a little bit).
    Repeat step 1 and 2 now with the device upright.
    My guess is that your result will be the same as my guess for steps 1 and 2 above.

  4. Put your device in slanted upright position in a box so that it can not further rotate. Make sure that the value of the rotation given above is as near 25 or -25 as possible (it will jump a little bit).
    Repeat step 1 now with the device slanted upright.
    My guess is that your result will be quite different from the other apps.
    You can repeat step 4 with different rotation value.

If my guesses are right come back and I will give you the explanations. In the mean time you can read my answer at Magnetic Fields, Rotation Matrix And global coordinates

like image 23
Hoan Nguyen Avatar answered Oct 29 '22 00:10

Hoan Nguyen