7

My augmented reality app needs the compass bearing of the camera view, and there's plenty of examples of getting the direction from the sensormanager.

However I'm finding the resulting value different depending on the phone orientation - landscape rotated to right is about 10 degrees different to landscape rotated to left (difference between ROTATION_0 and ROTATION_180 is less, but still different). This difference is enough to ruin any AR effect.

Is it something to do with calibration? (I'm not convinced I'm doing the figure of 8 thing properly - I've tried various ways I've found on youtube).

Any ideas why there's a difference? Have I messed up on the rotation matrix stuff? I have the option of restricting the app to a single orientation, but it still concerns me that the compass reading still isn't very accurate (even though after filtering it's fairly stable)

public void onSensorChanged(SensorEvent event) {
        if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
            return;
        }

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)  mGravity = event.values;
        if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) mGeomagnetic = event.values;

        if (mGravity != null && mGeomagnetic != null) {

            float[] rotationMatrixA = mRotationMatrixA;
            if (SensorManager.getRotationMatrix(rotationMatrixA, null, mGravity, mGeomagnetic)) {

                float[] rotationMatrixB = mRotationMatrixB;

                Display display = getWindowManager().getDefaultDisplay(); 
                int deviceRot = display.getRotation();

                switch (deviceRot)
                {
                // portrait - normal
                case Surface.ROTATION_0: SensorManager.remapCoordinateSystem(rotationMatrixA,
                        SensorManager.AXIS_X, SensorManager.AXIS_Z,
                        rotationMatrixB);
                break;
                // rotated left (landscape - keys to bottom)
                case Surface.ROTATION_90: SensorManager.remapCoordinateSystem(rotationMatrixA,
                        SensorManager.AXIS_Z, SensorManager.AXIS_MINUS_X,
                        rotationMatrixB); 
                break;
                // upside down
                case Surface.ROTATION_180: SensorManager.remapCoordinateSystem(rotationMatrixA,
                        SensorManager.AXIS_X, SensorManager.AXIS_Z,
                        rotationMatrixB); 
                break;
                // rotated right
                case Surface.ROTATION_270: SensorManager.remapCoordinateSystem(rotationMatrixA,
                        SensorManager.AXIS_MINUS_Z, SensorManager.AXIS_X,
                        rotationMatrixB); 
                break;

                default:  break;
                }

                float[] dv = new float[3]; 
                SensorManager.getOrientation(rotationMatrixB, dv);
                // add to smoothing filter
                fd.AddLatest((double)dv[0]); 
            }
            mDraw.invalidate();     
        }
    }
Mush
  • 133
  • 1
  • 6

2 Answers2

10

Try this

public void onSensorChanged(SensorEvent event) {
    if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
        return;
    }

    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)  mGravity = event.values.clone ();
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) mGeomagnetic =  event.values.clone ();

    if (mGravity != null && mGeomagnetic != null) {

        float[] rotationMatrixA = mRotationMatrixA;
        if (SensorManager.getRotationMatrix(rotationMatrixA, null, mGravity, mGeomagnetic)) {

            float[] rotationMatrixB = mRotationMatrixB;
            SensorManager.remapCoordinateSystem(rotationMatrixA,
                    SensorManager.AXIS_X, SensorManager.AXIS_Z,
                    rotationMatrixB);
            float[] dv = new float[3]; 
            SensorManager.getOrientation(rotationMatrixB, dv);
            // add to smoothing filter
            fd.AddLatest((double)dv[0]); 
        }
        mDraw.invalidate();     
    }
}

You do not need the switch statement, there seems to be a lot of confusion concerning getRotationMatrix, remapCoordinateSystem and getOrientation from stackoverflow questions.
I probably will write a detail explanation of these in the near future.

Hoan Nguyen
  • 18,033
  • 3
  • 50
  • 54
  • Man! I've been searching SO for more than 2 hours, and there are lots of WRONG answers. Yours is the first one that actually works correctly for me. Thanks for this. – BoD Aug 26 '13 at 22:57
  • I copied and edited the post. I didn't realize that Mush had mGravity = event.values, it should be event.values.clone(). Same for mGeomagnetic – Hoan Nguyen Aug 27 '13 at 14:21
  • `SensorManager.remapCoordinateSystem` is the solution. Thanks. – Douglas Nassif Roma Junior Jun 18 '15 at 14:40
  • The answer by @Mark Kranzler is wrong. The question ask for the direction of the camera view, which is the direction of the device -z axis. getOrientation return the azimuth as the direction of the y axis. Thus you need remapCoordinateSystem. The device z axis point in the same direction independent of the orientation of the device. That is the z axis point in the same direction whether the device is in portrait, landscape or anything in between. Thus there is no need to take orientation of the device into account. – Hoan Nguyen Jun 18 '15 at 18:55
  • This answer is correct if your phone is on landscape and device screen is pointing at you(AR for landscape orientation works fine). If your phone is lying on table on landscape it does not work. Hoan Nyugen, i also read your almost every post about the compass, and checked out the "dsensor" code, it's really educative. Can you share how to implement for portrait and landscape orientation when device lying flat or screen is perpendecular to the ground? – Thracian May 08 '17 at 12:23
  • If the phone is flat then you do not remap coordinate. What happens here is that the value[0] of getOrientation is the angle between the device y-axis and the magnetic north. When you remap coordinate as above the value[0] in getOrientation is the angle between the negative device z-axis and the magnetic north. If the phone is lying flat then the negative of the device z-axis is pointing to the sky or the earth and thus it does not make sense to talk about angle between this and the magnetic north. – Hoan Nguyen May 10 '17 at 00:13
1

Hoan's answer is actually incorrect because it doesn't account for the display rotation. This is the correct answer.

Community
  • 1
  • 1
Matt Kranzler
  • 565
  • 6
  • 14