Kiam LB Kiam LB - 5 months ago 8
Android Question

How can I better my results when measuring the roll of a device in portrait mode?

I am trying to take the pitch and roll of a device when in portrait orientation. With the axes looking like this, it would be about the x and z axis respectively. Right now I'm using the SensorManager API to get the pitch, roll, and yaw of the device sitting flat, as is default.

When I try and translate the rotational values from the flat device to the vertical orientation I experience what other SO users have called gimbal lock, which is a problem inherent in the way Euler angles work. Problem is I've tried implementing a rotational matrix, as other users have to solve a similar problem, but even still I'm having the same gimbal lock problem. I've included my onSensorChanged method and hopefully someone out there could help find out what's going wrong.

public void onSensorChanged(SensorEvent se) {

float degPitch = 0;
float degRoll = 0;
float degYaw = 0;
float radPitch = 0;
float radRoll = 0;
float radYaw = 0;

if (se.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mAccelerometerResult = se.values;
Log.d("onSensorChanged", "Accelerometer: " + mAccelerometerResult.length);
}

if (se.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mMagneticFieldResult = se.values;
Log.d("onSensorChanged", "Magnetic Field: " + mMagneticFieldResult.length);
}

if (mAccelerometerResult != null && mMagneticFieldResult != null) {
float[] rotation = new float[9];
float[] inclination = new float[9];

boolean rotationMatrixCheck = mSensorManager.getRotationMatrix(rotation, inclination, mAccelerometerResult, mMagneticFieldResult);

if (rotationMatrixCheck) {

float[] orientation = new float[3];
mSensorManager.getOrientation(rotation, orientation);


radYaw = orientation[0]; //Yaw = Z axis
radPitch = orientation[1]; //Pitch = X axis
radRoll = orientation[2]; //Roll = Y axis

degYaw = round((float) Math.toDegrees(radYaw), 2);
degPitch = round((float)Math.toDegrees(radPitch), 2);
degRoll = round((float)Math.toDegrees(radRoll), 2);


if ((counter % 10) == 0) {
//mYawTextView.setText(degYaw + "°");
mPitchTextView.setText(degPitch + "°");
mRollTextView.setText(degRoll + "°");
counter = 0;
} else {
counter++;
}
}
}


Further, I'm not even sure that I understand what rotational value I'm looking for if I can get good rotation about the portrait axes. If I want the roll of the device in portrait (about z axis from my original image), would that still be the roll of the device laying flat(about y from the flat axes image)?

Any insight that can be shared here would be appreciated.

Answer

I found the solution to the problem. Getting the rotational matrix in a 3x3 grid returns the rotational matrix in Euler's angles, as discussed in the original question. Getting the matrix in a 4x4 grid returns a Quaternion representation of the rotational matrix (According to SO user Stochastically's answer found here).

Further that rotational matrix coordinate system had to be remapped in order to be used in portrait mode. commented changes are shown bellow.

public void onSensorChanged(SensorEvent se) {

float degPitch = 0;
float degRoll = 0;
float degYaw = 0;
float radPitch = 0;
float radRoll = 0;
float radYaw = 0;

if (se.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
    mAccelerometerResult = se.values;
    Log.d("onSensorChanged", "Accelerometer: " + mAccelerometerResult.length);
}

if (se.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
    mMagneticFieldResult = se.values;
    Log.d("onSensorChanged", "Magnetic Field: " + mMagneticFieldResult.length);
}

if (mAccelerometerResult != null && mMagneticFieldResult != null) {

//~~~This is where the 3x3 matrix has changed to a 4x4.
    float[] rotation = new float[16];
    float[] inclination = new float[16];

    boolean rotationMatrixCheck = mSensorManager.getRotationMatrix(rotation, inclination, mAccelerometerResult, mMagneticFieldResult);

    if (rotationMatrixCheck) {

        float[] orientation = new float[3];

//~~~This is where the rotational matrix is remapped to be used in portrait mode.
        float[] remappedRotation = new float[16];
        SensorManager.remapCoordinateSystem(rotation, SensorManager.AXIS_X, SensorManager.AXIS_Z, remappedRotation);

        mSensorManager.getOrientation(rotation, orientation);


        radYaw = orientation[0];      //Yaw = Z axis
        radPitch = orientation[1];      //Pitch = X axis
        radRoll = orientation[2];      //Roll = Y axis

        degYaw = round((float) Math.toDegrees(radYaw), 2);
        degPitch = round((float)Math.toDegrees(radPitch), 2);
        degRoll = round((float)Math.toDegrees(radRoll), 2);


        if ((counter % 10) == 0) {
            //mYawTextView.setText(degYaw + "°");
            mPitchTextView.setText(degPitch + "°");
            mRollTextView.setText(degRoll + "°");
            counter = 0;
        } else {
            counter++;
        }
    }
}