سؤال

I am working on a tilt app for Android. I am having an issue with Portrait & landscape mode. When the pitch = 90 degrees (phone on end) and even slightly before the roll value goes crazy when there has been no physical change in roll. I have not been able to find a solution to this problem. If anyone can point me in the right direction, it would be appreciated.

Here’s a short code dump, so you know it is not an accelerometer error.

final SensorEventListener mEventListener = new SensorEventListener(){
     public void onAccuracyChanged(Sensor sensor, int accuracy) {}  
 public void onSensorChanged(SensorEvent event) {
     setListners(sensorManager, mEventListener);

      SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
     SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);


        synchronized (this) {

            switch (event.sensor.getType()){
                case Sensor.TYPE_ACCELEROMETER:

                    System.arraycopy(event.values, 0, mValuesAccel, 0, 3);

                    long actualTime = System.currentTimeMillis();

                    //Sensitivity delay
                    if (actualTime - lastUpdate < 250) {
                        return;
                        }
                    else {
                        sysAzimuth = (int)Math.toDegrees(mValuesOrientation[0]);
                        sysPitch = (int)Math.toDegrees(mValuesOrientation[1]);
                        sysRoll = (int)Math.toDegrees(mValuesOrientation[2]);

                        //invert direction with -1
                      pitch = (sysPitch - pitchCal)*-1;
                      roll = (sysRoll - rollCal);
                      azimuth = sysAzimuth;

                    lastUpdate = actualTime;
                    }
هل كانت مفيدة؟

المحلول

I found what I was looking for, Rotational Matrices.

I was using Euler angles (roll, pitch, yaw) for the pitch and roll. When the phone is on end 90 degrees, the x and z plain are the same and the phone goes crazy, a fundamental flaw with Euler angles.

I need to get the pitch and roll degrees using Rotational Matrices via getRotationMatrix

Here it is for all ;)

XML:

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout/main.xml -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Button android:id="@+id/update" android:text="Update Values"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doUpdate" />
<Button android:id="@+id/show" android:text="Show Me!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doShow" android:layout_toRightOf="@id/update" />
<TextView android:id="@+id/preferred" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/update" />
<TextView android:id="@+id/orientation" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/preferred" />
</RelativeLayout>

Code:

package YOURPACKAGE;



import android.app.Activity;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;


public class YOURCLASS extends Activity implements SensorEventListener {
private static final String TAG = "VirtualJax";
private SensorManager mgr;
private Sensor accel;
private Sensor compass;
private Sensor orient;
private TextView preferred;
private TextView orientation;
private boolean ready = false;
private float[] accelValues = new float[3];
private float[] compassValues = new float[3];
private float[] inR = new float[9];
private float[] inclineMatrix = new float[9];
private float[] orientationValues = new float[3];
private float[] prefValues = new float[3];
private float mAzimuth;
private double mInclination;
private int counter;
private int mRotation;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    preferred = (TextView)findViewById(R.id.preferred);
    orientation = (TextView)findViewById(R.id.orientation);
    mgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
    accel = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    compass = mgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    orient = mgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    WindowManager window = (WindowManager) this.getSystemService(WINDOW_SERVICE);
    int apiLevel = Integer.parseInt(Build.VERSION.SDK);
    if(apiLevel <8) {
        mRotation = window.getDefaultDisplay().getOrientation();
    }
    else {
        mRotation = window.getDefaultDisplay().getRotation();
    }
}

@Override
protected void onResume() {
    mgr.registerListener(this, accel, SensorManager.SENSOR_DELAY_GAME);
    mgr.registerListener(this, compass, SensorManager.SENSOR_DELAY_GAME);
    mgr.registerListener(this, orient, SensorManager.SENSOR_DELAY_GAME);
    super.onResume();
}

@Override
protected void onPause() {
    mgr.unregisterListener(this, accel);
    mgr.unregisterListener(this, compass);
    mgr.unregisterListener(this, orient);
    super.onPause();
}

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

public void onSensorChanged(SensorEvent event) {
    // Need to get both accelerometer and compass
    // before we can determine our orientationValues
    switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            for(int i=0; i<3; i++) {
                accelValues[i] = event.values[i];
            }
            if(compassValues[0] != 0)
                ready = true;
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            for(int i=0; i<3; i++) {
                compassValues[i] = event.values[i];
            }
            if(accelValues[2] != 0)
                ready = true;
            break;
        case Sensor.TYPE_ORIENTATION:
            for(int i=0; i<3; i++) {
                orientationValues[i] = event.values[i];
            }
            break;
    }

    if(!ready)
        return;
    if(SensorManager.getRotationMatrix(inR, inclineMatrix, accelValues, compassValues)) {
        // got a good rotation matrix
        SensorManager.getOrientation(inR, prefValues);
        mInclination = SensorManager.getInclination(inclineMatrix);
        // Display every 10th value
        if(counter++ % 10 == 0) {
            doUpdate(null);
            counter = 1;
        }

    }
}

public void doUpdate(View view) {
    if(!ready)
        return;
    mAzimuth = (float) Math.toDegrees(prefValues[0]);
    if(mAzimuth < 0) {
        mAzimuth += 360.0f;
    }
    String msg = String.format(
            "Preferred:\nazimuth (Z): %7.3f \npitch (X): %7.3f\nroll (Y): %7.3f",
            mAzimuth, Math.toDegrees(prefValues[1]),
            Math.toDegrees(prefValues[2]));
    preferred.setText(msg);
    msg = String.format(
            "Orientation Sensor:\nazimuth (Z): %7.3f\npitch (X): %7.3f\nroll (Y): %7.3f",
            orientationValues[0],
            orientationValues[1],
            orientationValues[2]);
    orientation.setText(msg);
    preferred.invalidate();
    orientation.invalidate();
}

public void doShow(View view) {
    // google.streetview:cbll=30.32454,-81.6584&cbp=1,yaw,,pitch,1.0
    // yaw = degrees clockwise from North
    // For yaw we can use either mAzimuth or orientationValues[0].
    //
    // pitch = degrees up or down. -90 is looking straight up,
    // +90 is looking straight down
    // except that pitch doesn't work properly
    Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse(
            "google.streetview:cbll=30.32454,-81.6584&cbp=1," +
                    Math.round(orientationValues[0]) + ",,0,1.0"
    ));
    startActivity(intent);
    return;
}

نصائح أخرى

I wouldn't use Euler angles (roll, pitch, yaw). It pretty much screws up the stability of your app as you already noticed.

See here why, and what to do instead: Strange behavior with android orientation sensor.

Through experimentation I found that when you switch from Portrait to Landscape mode your rotation matrix doesn't change but you have to change it manually in order to use with OpenGL correctly

copyMat(mRotationMatrixP, mRotationMatrix);

// permute and negate columns 0, 1
mRotationMatrixP[0] = -mRotationMatrix[1];
mRotationMatrixP[4] = -mRotationMatrix[5];
mRotationMatrixP[8] = -mRotationMatrix[9];

// permute 1, 0
mRotationMatrixP[1] = mRotationMatrix[0];
mRotationMatrixP[5] = mRotationMatrix[4];
mRotationMatrixP[9] = mRotationMatrix[8];

Also I hope you acquire the Rotation Matrix correctly on the first place:

public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
        SensorManager.getRotationMatrixFromVector(
                mRotationMatrix , event.values);
        SensorManager.getOrientation (mRotationMatrix, values);

What you describe is called gimbal lock. At pitch +/-90, yaw -(+) roll is completely undefined. Near pitch +/-90, small nois/error in attitude can cause large fluctuations in yaw and roll individually even though there is no large change in actual orientation. Here is a great write-up on yaw, pitch roll (and how thay are not implemented well on many platforms):

http://www.sensorplatforms.com/understanding-orientation-conventions-mobile-platforms/

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top