How to Calculate Azimuth, Pitch, Orientation When My Android Device Isn't Flat

How should I calculate azimuth, pitch, orientation when my Android device isn't flat?

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 definition of the pitch angle which is exactly the angle given by Math.acos(rotationMatrix[8]) that is mentioned in another answer here.

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

equation with definition of R matrix

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

equation defining the azimuth angle

Finding Azimuth Value when Device is not FLAT to ground - Android?

Found its answer:

Calculate azimuth angle like this:

mAzimuthAngleNotFlat = (int) Math.toDegrees(Math
.atan2((rotationMatrix[1] - rotationMatrix[3]), (rotationMatrix[0] + rotationMatrix[4])));

To convert it to 0 to 359 simply apply this:

    if (mAzimuthAngleNotFlat < 0) {
mAzimuthAngleNotFlat += 360;
}

Then you are done. And this angle will be accurate when device is not flat. For more, you can see discussion on accepted answer.

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

Android: get device orientation from Azimuth, roll & pitch

I've found it & here is the calculating function that will be called inside the listener after reading the pitch & roll:

public static final int ORIENTATION_PORTRAIT = 0;
public static final int ORIENTATION_LANDSCAPE_REVERSE = 1;
public static final int ORIENTATION_LANDSCAPE = 2;
public static final int ORIENTATION_PORTRAIT_REVERSE = 3;
public int orientation = ORIENTATION_PORTRAIT;

private int calculateOrientation(int roll, int pitch) {
if (((orientation == ORIENTATION_PORTRAIT || orientation == ORIENTATION_PORTRAIT_REVERSE)
&& (roll > -30 && roll < 30))) {
if (averagePitch > 0)
return ORIENTATION_PORTRAIT_REVERSE;
else
return ORIENTATION_PORTRAIT;
} else {
// divides between all orientations
if (Math.abs(pitch) >= 30) {
if (pitch > 0)
return ORIENTATION_PORTRAIT_REVERSE;
else
return ORIENTATION_PORTRAIT;
} else {
if (averageRoll > 0) {
return ORIENTATION_LANDSCAPE_REVERSE;
} else {
return ORIENTATION_LANDSCAPE;
}
}
}
}

-- Update --

& here is my full utility class implementation

-- Update --

Adding this image to help visualizing the azimuth, pitch & roll:
Visualizing azimuth, pitch and roll

Android getOrientation Azimuth gets polluted when phone is tilted

For complete code see https://github.com/hoananguyen/dsensor

Keep a history and average out, I do not know the correct interpretation of pitch and roll so the following code is for azimuth only.

Class members

private List<float[]> mRotHist = new ArrayList<float[]>();
private int mRotHistIndex;
// Change the value so that the azimuth is stable and fit your requirement
private int mHistoryMaxLength = 40;
float[] mGravity;
float[] mMagnetic;
float[] mRotationMatrix = new float[9];
// the direction of the back camera, only valid if the device is tilted up by
// at least 25 degrees.
private float mFacing = Float.NAN;

public static final float TWENTY_FIVE_DEGREE_IN_RADIAN = 0.436332313f;
public static final float ONE_FIFTY_FIVE_DEGREE_IN_RADIAN = 2.7052603f;

onSensorChanged

@Override
public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_GRAVITY)
{
mGravity = event.values.clone();
}
else
{
mMagnetic = event.values.clone();
}

if (mGravity != null && mMagnetic != null)
{
if (SensorManager.getRotationMatrix(mRotationMatrix, null, mGravity, mMagnetic))
{
// inclination is the degree of tilt by the device independent of orientation (portrait or landscape)
// if less than 25 or more than 155 degrees the device is considered lying flat
float inclination = (float) Math.acos(mRotationMatrix[8]);
if (inclination < TWENTY_FIVE_DEGREE_IN_RADIAN
|| inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
{
// mFacing is undefined, so we need to clear the history
clearRotHist();
mFacing = Float.NaN;
}
else
{
setRotHist();
// mFacing = azimuth is in radian
mFacing = findFacing();
}
}
}
}

private void clearRotHist()
{
if (DEBUG) {Log.d(TAG, "clearRotHist()");}
mRotHist.clear();
mRotHistIndex = 0;
}

private void setRotHist()
{
if (DEBUG) {Log.d(TAG, "setRotHist()");}
float[] hist = mRotationMatrix.clone();
if (mRotHist.size() == mHistoryMaxLength)
{
mRotHist.remove(mRotHistIndex);
}
mRotHist.add(mRotHistIndex++, hist);
mRotHistIndex %= mHistoryMaxLength;
}

private float findFacing()
{
if (DEBUG) {Log.d(TAG, "findFacing()");}
float[] averageRotHist = average(mRotHist);
return (float) Math.atan2(-averageRotHist[2], -averageRotHist[5]);
}

public float[] average(List<float[]> values)
{
float[] result = new float[9];
for (float[] value : values)
{
for (int i = 0; i < 9; i++)
{
result[i] += value[i];
}
}

for (int i = 0; i < 9; i++)
{
result[i] = result[i] / values.size();
}

return result;
}

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

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

Pitch problems in landscape mode

Sensor.TYPE_ORIENTATION is deprecated and should not be used.

Reading the device orientation gave me some headache as well. Here is a base class that I am using for activities that need the device's orientation:

public abstract class SensorActivity extends Activity implements SensorEventListener {

private SensorManager sensorManager;

private final float[] accelerometerValues = new float[3];

private final float[] R = new float[9];

private final float[] I = new float[9];

private final float[] orientation = new float[3];

private final float[] remappedR = new float[9];

private final List<HasOrientation> observers = new ArrayList<HasOrientation>();

private int x;

private int y;

protected SensorActivity() {
this(SensorManager.AXIS_X, SensorManager.AXIS_Y);
}

/**
* Initializes a new instance.
*
*/
protected SensorActivity(int x, int y) {
setAxisMapping(x, y);
}

/**
* The parameters specify how to map the axes of the device to the axes of
* the sensor coordinate system.
*
* The device coordinate system has its x-axis pointing from left to right along the
* display, the y-axis is pointing up along the display and the z-axis is pointing
* upward.
*
* The <code>x</code> parameter defines the direction of the sensor coordinate system's
* x-axis in device coordinates. The <code>y</code> parameter defines the direction of
* the sensor coordinate system's y-axis in device coordinates.
*
* For example, if the device is laying on a flat table with the display pointing up,
* specify <code>SensorManager.AXIS_X</code> as the <code>x</code> parameter and
* <code>SensorManager.AXIS_Y</code> as the <code>y</code> parameter.
* If the device is mounted in a car in landscape mode,
* specify <code>SensorManager.AXIS_Z</code> as the <code>x</code> parameter and
* <code>SensorManager.AXIS_MINUS_X</code> as the <code>y</code> parameter.
*
* @param x specifies how to map the x-axis of the device.
* @param y specifies how to map the y-axis of the device.
*/
public void setAxisMapping(int x, int y) {
this.x = x;
this.y = y;
}

/**
* Registers an orientation observer.
*
* @param hasOrientation is the observer to register.
*/
protected void register(HasOrientation hasOrientation) {
observers.add(hasOrientation);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
}

@Override
protected void onResume() {
super.onResume();

sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
}

@Override
protected void onPause() {
sensorManager.unregisterListener(this);

super.onPause();
}

public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

public void onSensorChanged(SensorEvent event) {
switch(event.sensor.getType())
{
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, accelerometerValues, 0, accelerometerValues.length);
break;

case Sensor.TYPE_MAGNETIC_FIELD:
if (SensorManager.getRotationMatrix(R, I, accelerometerValues, event.values)) {
if (SensorManager.remapCoordinateSystem(R, x, y, remappedR)) {
SensorManager.getOrientation(remappedR, orientation);

for (HasOrientation observer : observers) {
observer.onOrientation(Orientation.fromRadians(orientation));
}
}
}
break;

default:
throw new IllegalArgumentException("unknown sensor type");
}
}
}

Orientation goes like this:

/**
* An angular direction vector.
*
* The vector consists of three angles {azimuth, pitch, roll}. Within a body-fixed
* cartesian system, the values of these angles define rotations of the three body axes.
*
* All angles are in degrees.
*
* @author michael@mictale.com
*
*/
public class Orientation {

/**
* Represents the angle to rotate the up axis.
*/
public float azimuth;

/**
* Represents the angle to rotate the axis pointing right.
*/
public float pitch;

/**
* Represents the angle to rotate the forward axis.
*/
public float roll;

/**
* Initializes an instance that is empty.
*/
public Orientation() {
}

/**
* Initializes an instance from the specified rotation values in degrees.
*
* @param azimuth is the azimuth angle.
* @param pitch is the pitch angle.
* @param roll is the roll angle.
*/
public Orientation(float azimuth, float pitch, float roll) {
this.azimuth = azimuth;
this.pitch = pitch;
this.roll = roll;
}

/**
* Sets the current values to match the specified orientation.
*
* @param o is the orientation to copy.
*/
public void setTo(Orientation o) {
this.azimuth = o.azimuth;
this.pitch = o.pitch;
this.roll = o.roll;
}

/**
* Normalizes the current instance.
*
* Limits the azimuth to [0...360] and pitch and roll to [-180...180].
*/
public void normalize() {
azimuth = Angle.normalize(azimuth);
pitch = Angle.tilt(pitch);
roll = Angle.tilt(roll);
}

/**
* Creates a new vector from an array of radian values in the form
* [azimuth, pitch, roll].
*
* This method is useful to fill sensor data into a vector.
*
* @param vec is the array of radians.
* @return the vector.
*/
public static Orientation fromRadians(float[] vec) {
return new Orientation((float)Math.toDegrees(vec[0]), (float)Math.toDegrees(vec[1]),
(float)Math.toDegrees(vec[2]));
}

@Override
public String toString() {
return "{a=" + azimuth + ", p=" + pitch + ", r=" + roll + "}";
}
}

You need to call setAxisMapping() to receive the orientation aligned to either portrait or landscape mode. I only called it from within the constructor so I cannot tell you what happens when you call it while the activity is running. You might have to reset the matrixes.



Related Topics



Leave a reply



Submit