安卓getOrientation()方法返回坏的结果(Android getOrientation(

2019-06-23 17:35发布

I'm creating 3D Compass application.

I'm using getOrientation method to get orientation (almost same implementation like here). If I place phone on the table it works well, but when top of the phone points to the sky (minus Z axis on the picture; sphere is the Earth) getOrientation starts giving really bad results. It gives values for Z axis between 0 to 180 degrees in a few real degrees. Is there any way how to suppress this behavior? I created a little video what describes problem (sorry for bad quality). Thanks in advance.

Solution: When you rotating model, there is difference between:

gl.glRotatef(_angleY, 0f, 1f, 0f); //ROLL
gl.glRotatef(_angleX, 1f, 0f, 0f); //ELEVATION
gl.glRotatef(_angleZ, 0f, 0f, 1f); //AZIMUTH


gl.glRotatef(_angleX, 1f, 0f, 0f); //ELEVATION
gl.glRotatef(_angleY, 0f, 1f, 0f); //ROLL
gl.glRotatef(_angleZ, 0f, 0f, 1f); //AZIMUTH

Answer 1:

好吧,我可以看到你的这种做法至少1问题。

我认为你把对应的磁力与均值低通滤波器,以平滑的数据的三维矢量。 虽然这种方法是有效的伟大的传感器值而没有不连续,如加速度计原始数据变化时,它不会从你的磁强计取角变量的工作如此之大一字不差。 为什么,有人可能会问?

由于这些角变量(方位角,俯仰,滚动)具有上结合的和结合的低,这意味着高于180度的任意值,即181度,将环绕到181-360 = -179度,并且任何变量下面-180度将在另一个方向上环绕。 因此,当这些角的变量之一得到接近于阈值(180或-180),该变量将趋向于振荡以接近于2个极端值。 当你一味的低通滤波器,适用于那些价值观,你要么顺利从180度向-180度或从平稳走向-180 180度的增大而减小。 无论哪种方式,结果看起来就会很喜欢你上面的视频......只要一个直接应用的平均缓冲到原始角度数据从getOrientation(...)这个问题会出现(而且应该不仅存在于其中手机是正直,而且在那里有方位角的wraparounds太...也许你可以测试这些错误,以及...)案件的情况。

你说你这个测试用的1缓冲区大小从理论上说,这个问题不应该存在,如果存在根本不进行平均,虽然我已经在过去见过循环缓冲区的一些实现,这可能意味着有仍然场均至少有1过去值进行,而不是有没有平均的。 如果您遇到这种情况,我们发现你的错误的根本原因。

不幸的是,没有太多的,可以同时用标准平均滤波器坚持实现一个优雅的解决方案。 我通常在这种情况下是切换到另一种类型的低通滤波器的,其不需要任何深缓冲器来操作:一个简单的IIR滤波器(顺序1):

DIFF = X [n]的- Y [N-1]

Y [N] - Y [N-1] =阿尔法 *(X [n]的- Y [N-1])=阿尔法 * DIFF

...其中y是过滤角度,x是原始角,和α<1类似于一个时间常数,如α= 1对应于无过滤器的情况下,和所述低通滤波器的截止频率变降低如α趋近于零。 急性眼大概会到现在,这相当于一个简单的比例控制器的注意。

这样的过滤器允许角度值的回绕的补偿,因为我们可以加上或减去360 diff的 ,以便确保ABS(差异)<= 180,这又确保了过滤角度值将始终增加/减少最佳的方向以达到其“设定值”。

一个示例函数调用,将被周期性地调度时,其计算经滤波的角度值y对于给定的原始角度值x,可能是这样的:

private float restrictAngle(float tmpAngle){
    while(tmpAngle>=180) tmpAngle-=360;
    while(tmpAngle<-180) tmpAngle+=360;
    return tmpAngle;
}

//x is a raw angle value from getOrientation(...)
//y is the current filtered angle value
private float calculateFilteredAngle(float x, float y){ 
    final float alpha = 0.1f;
    float diff = x-y;

    //here, we ensure that abs(diff)<=180
    diff = restrictAngle(diff);

    y += alpha*diff;
    //ensure that y stays within [-180, 180[ bounds
    y = restrictAngle(y);

    return y;
}

功能calculateFilteredAngle(float x, float y)然后可以使用这样的事情(例如用于从方位角周期性地调用getOrientation(...)函数:

filteredAzimuth = calculateFilteredAngle(azimuth, filteredAzimuth);

使用这种方法,过滤器不会胡作非为像平均滤波器由OP提及。

正如我无法加载由OP上传.apk文件,我决定实现自己的测试项目,以查看是否更正工作。 这里是整个代码(它不使用.XML为主要布局,所以我不包括它)。 只需将它复制到一个测试项目,看看它是否工作的特定设备(在HTC Desire的W /安卓2.1版测试功能。)上:

文件1:Compass3DActivity.java:

package com.epichorns.compass3D;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

public class Compass3DActivity extends Activity {
    //Textviews for showing angle data
    TextView mTextView_azimuth;
    TextView mTextView_pitch;
    TextView mTextView_roll;

    TextView mTextView_filtered_azimuth;
    TextView mTextView_filtered_pitch;
    TextView mTextView_filtered_roll;


    float mAngle0_azimuth=0;
    float mAngle1_pitch=0;
    float mAngle2_roll=0;

    float mAngle0_filtered_azimuth=0;
    float mAngle1_filtered_pitch=0;
    float mAngle2_filtered_roll=0;

    private Compass3DView mCompassView;

    private SensorManager sensorManager;
    //sensor calculation values
    float[] mGravity = null;
    float[] mGeomagnetic = null;
    float Rmat[] = new float[9];
    float Imat[] = new float[9];
    float orientation[] = new float[3];
    SensorEventListener mAccelerometerListener = new SensorEventListener(){
        public void onAccuracyChanged(Sensor sensor, int accuracy) {}

        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
                mGravity = event.values.clone();
                processSensorData();
            }
        }   
    };
    SensorEventListener mMagnetometerListener = new SensorEventListener(){
        public void onAccuracyChanged(Sensor sensor, int accuracy) {}

        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
                mGeomagnetic = event.values.clone();
                processSensorData();                
                update();
            }
        }   
    };

    private float restrictAngle(float tmpAngle){
        while(tmpAngle>=180) tmpAngle-=360;
        while(tmpAngle<-180) tmpAngle+=360;
        return tmpAngle;
    }

    //x is a raw angle value from getOrientation(...)
    //y is the current filtered angle value
    private float calculateFilteredAngle(float x, float y){ 
        final float alpha = 0.3f;
        float diff = x-y;

        //here, we ensure that abs(diff)<=180
        diff = restrictAngle(diff);

        y += alpha*diff;
        //ensure that y stays within [-180, 180[ bounds
        y = restrictAngle(y);

        return y;
    }



    public void processSensorData(){
        if (mGravity != null && mGeomagnetic != null) { 
            boolean success = SensorManager.getRotationMatrix(Rmat, Imat, mGravity, mGeomagnetic);
            if (success) {              
                SensorManager.getOrientation(Rmat, orientation);
                mAngle0_azimuth = (float)Math.toDegrees((double)orientation[0]); // orientation contains: azimut, pitch and roll
                mAngle1_pitch = (float)Math.toDegrees((double)orientation[1]); //pitch
                mAngle2_roll = -(float)Math.toDegrees((double)orientation[2]); //roll               
                mAngle0_filtered_azimuth = calculateFilteredAngle(mAngle0_azimuth, mAngle0_filtered_azimuth);
                mAngle1_filtered_pitch = calculateFilteredAngle(mAngle1_pitch, mAngle1_filtered_pitch);
                mAngle2_filtered_roll = calculateFilteredAngle(mAngle2_roll, mAngle2_filtered_roll);    
            }           
            mGravity=null; //oblige full new refresh
            mGeomagnetic=null; //oblige full new refresh
        }
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);     
        LinearLayout ll = new LinearLayout(this);       
        LinearLayout.LayoutParams llParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT);      
        ll.setLayoutParams(llParams);      
        ll.setOrientation(LinearLayout.VERTICAL);      
        ViewGroup.LayoutParams txtParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);        
        mTextView_azimuth = new TextView(this);
        mTextView_azimuth.setLayoutParams(txtParams);
        mTextView_pitch = new TextView(this);
        mTextView_pitch.setLayoutParams(txtParams);
        mTextView_roll = new TextView(this);
        mTextView_roll.setLayoutParams(txtParams);      
        mTextView_filtered_azimuth = new TextView(this);
        mTextView_filtered_azimuth.setLayoutParams(txtParams);
        mTextView_filtered_pitch = new TextView(this);
        mTextView_filtered_pitch.setLayoutParams(txtParams);
        mTextView_filtered_roll = new TextView(this);
        mTextView_filtered_roll.setLayoutParams(txtParams);

        mCompassView = new Compass3DView(this);        
        ViewGroup.LayoutParams compassParams = new ViewGroup.LayoutParams(200,200);
        mCompassView.setLayoutParams(compassParams);

        ll.addView(mCompassView);
        ll.addView(mTextView_azimuth);
        ll.addView(mTextView_pitch);
        ll.addView(mTextView_roll);
        ll.addView(mTextView_filtered_azimuth);
        ll.addView(mTextView_filtered_pitch);
        ll.addView(mTextView_filtered_roll);

        setContentView(ll);

        sensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
        sensorManager.registerListener(mAccelerometerListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI); 
        sensorManager.registerListener(mMagnetometerListener, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
        update();       
    }


    @Override
    public void onDestroy(){
        super.onDestroy();
        sensorManager.unregisterListener(mAccelerometerListener);
        sensorManager.unregisterListener(mMagnetometerListener);
    }


    private void update(){
        mCompassView.changeAngles(mAngle1_filtered_pitch,  mAngle2_filtered_roll, mAngle0_filtered_azimuth);

        mTextView_azimuth.setText("Azimuth: "+String.valueOf(mAngle0_azimuth));
        mTextView_pitch.setText("Pitch: "+String.valueOf(mAngle1_pitch));
        mTextView_roll.setText("Roll: "+String.valueOf(mAngle2_roll));

        mTextView_filtered_azimuth.setText("Azimuth: "+String.valueOf(mAngle0_filtered_azimuth));
        mTextView_filtered_pitch.setText("Pitch: "+String.valueOf(mAngle1_filtered_pitch));
        mTextView_filtered_roll.setText("Roll: "+String.valueOf(mAngle2_filtered_roll));

    }
}

文件2:Compass3DView.java:

package com.epichorns.compass3D;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class Compass3DView extends GLSurfaceView {
    private Compass3DRenderer mRenderer;

    public Compass3DView(Context context) {
        super(context);
        mRenderer = new Compass3DRenderer(context);
        setRenderer(mRenderer);
    }

    public void changeAngles(float angle0, float angle1, float angle2){
        mRenderer.setAngleX(angle0);
        mRenderer.setAngleY(angle1);
        mRenderer.setAngleZ(angle2);
    }

}

文件3:Compass3DRenderer.java:

package com.epichorns.compass3D;


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;


public class Compass3DRenderer implements GLSurfaceView.Renderer {
    Context mContext;

    // a raw buffer to hold indices
    ShortBuffer _indexBuffer;    
    // raw buffers to hold the vertices
    FloatBuffer _vertexBuffer0;
    FloatBuffer _vertexBuffer1;
    FloatBuffer _vertexBuffer2;
    FloatBuffer _vertexBuffer3;
    FloatBuffer _vertexBuffer4;
    FloatBuffer _vertexBuffer5;
    int _numVertices = 3; //standard triangle vertices = 3

    FloatBuffer _textureBuffer0123;



    //private FloatBuffer _light0Position;
    //private FloatBuffer _light0Ambient;
    float _light0Position[] = new float[]{10.0f, 10.0f, 10.0f, 0.0f};
    float _light0Ambient[] = new float[]{0.05f, 0.05f, 0.05f, 1.0f};
    float _light0Diffuse[] = new float[]{0.5f, 0.5f, 0.5f, 1.0f};
    float _light0Specular[] = new float[]{0.7f, 0.7f, 0.7f, 1.0f};
    float _matAmbient[] = new float[] { 0.6f, 0.6f, 0.6f, 1.0f };
    float _matDiffuse[] = new float[] { 0.6f, 0.6f, 0.6f, 1.0f };




    private float _angleX=0f;
    private float _angleY=0f;
    private float _angleZ=0f;


    Compass3DRenderer(Context context){
        super();
        mContext = context;
    }

    public void setAngleX(float angle) {
        _angleX = angle;
    }

    public void setAngleY(float angle) {
        _angleY = angle;
    }

    public void setAngleZ(float angle) {
        _angleZ = angle;
    }

    FloatBuffer InitFloatBuffer(float[] src){
        ByteBuffer bb = ByteBuffer.allocateDirect(4*src.length);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer inBuf = bb.asFloatBuffer();
        inBuf.put(src);
        return inBuf;
    }

    ShortBuffer InitShortBuffer(short[] src){
        ByteBuffer bb = ByteBuffer.allocateDirect(2*src.length);
        bb.order(ByteOrder.nativeOrder());
        ShortBuffer inBuf = bb.asShortBuffer();
        inBuf.put(src);
        return inBuf;
    }

    //Init data for our rendered pyramid
    private void initTriangles() {

        //Side faces triangles
        float[] coords = {
            -0.25f, -0.5f, 0.25f,
            0.25f, -0.5f, 0.25f,
            0f, 0.5f, 0f
        };

        float[] coords1 = {
            0.25f, -0.5f, 0.25f,
            0.25f, -0.5f, -0.25f,
            0f, 0.5f, 0f
        };

        float[] coords2 = {
            0.25f, -0.5f, -0.25f,
            -0.25f, -0.5f, -0.25f,
            0f, 0.5f, 0f
        };

        float[] coords3 = {
            -0.25f, -0.5f, -0.25f,
            -0.25f, -0.5f, 0.25f,
            0f, 0.5f, 0f
        };

        //Base triangles
        float[] coords4 = {
            -0.25f, -0.5f, 0.25f,
            0.25f, -0.5f, -0.25f,
            0.25f, -0.5f, 0.25f
        };

        float[] coords5 = {
            -0.25f, -0.5f, 0.25f,
            -0.25f, -0.5f, -0.25f, 
            0.25f, -0.5f, -0.25f
        };


        float[] textures0123 = {
                // Mapping coordinates for the vertices (UV mapping CW)
                0.0f, 0.0f,     // bottom left                    
                1.0f, 0.0f,     // bottom right
                0.5f, 1.0f,     // top ctr              
        };


        _vertexBuffer0 = InitFloatBuffer(coords);
        _vertexBuffer0.position(0);

        _vertexBuffer1 = InitFloatBuffer(coords1);
        _vertexBuffer1.position(0);    

        _vertexBuffer2 = InitFloatBuffer(coords2);
        _vertexBuffer2.position(0);

        _vertexBuffer3 = InitFloatBuffer(coords3);
        _vertexBuffer3.position(0);

        _vertexBuffer4 = InitFloatBuffer(coords4);
        _vertexBuffer4.position(0);

        _vertexBuffer5 = InitFloatBuffer(coords5);
        _vertexBuffer5.position(0);

        _textureBuffer0123 = InitFloatBuffer(textures0123);
        _textureBuffer0123.position(0);

        short[] indices = {0, 1, 2};
        _indexBuffer = InitShortBuffer(indices);        
        _indexBuffer.position(0);

    }


    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        gl.glEnable(GL10.GL_CULL_FACE); // enable the differentiation of which side may be visible 
        gl.glShadeModel(GL10.GL_SMOOTH);

        gl.glFrontFace(GL10.GL_CCW); // which is the front? the one which is drawn counter clockwise
        gl.glCullFace(GL10.GL_BACK); // which one should NOT be drawn

        initTriangles();

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    }

    public void onDrawFrame(GL10 gl) {


        gl.glPushMatrix();

        gl.glClearColor(0, 0, 0, 1.0f); //clipping backdrop color
        // clear the color buffer to show the ClearColor we called above...
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        // set rotation       
        gl.glRotatef(_angleY, 0f, 1f, 0f); //ROLL
        gl.glRotatef(_angleX, 1f, 0f, 0f); //ELEVATION
        gl.glRotatef(_angleZ, 0f, 0f, 1f); //AZIMUTH

        //Draw our pyramid

        //4 side faces
        gl.glColor4f(0.5f, 0f, 0f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer0);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

        gl.glColor4f(0.5f, 0.5f, 0f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer1);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

        gl.glColor4f(0f, 0.5f, 0f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer2);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

        gl.glColor4f(0f, 0.5f, 0.5f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer3);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

        //Base face
        gl.glColor4f(0f, 0f, 0.5f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer4);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer5);
        gl.glDrawElements(GL10.GL_TRIANGLES, _numVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

        gl.glPopMatrix();
    }

    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
        gl.glViewport(0, 0, w, h);

    }



}

请注意,此代码不能弥补平板电脑的默认横向,所以它预计只在手机上正常工作(我没有平板附近测试任何纠正码)。



Answer 2:

你应该尝试一个较长的延迟像游戏和/或保持/增加循环缓冲区的大小。 在移动设备上的传感器(加速度计,指南针等)本质上是嘈杂的,所以当我问到“低通滤波器”,我的意思是你使用更多的数据来降低你的应用程序可用更新的频率。 您的视频做里面,我也建议用更少的电磁干扰要去一个地方,如公园只是为了检查该行为是一致的,以及标准的指南针复位动作(图8旋转装置)。 最后,你可能需要应用一些启发式抛出了“坏”数据,以便为用户提供流畅的体验。



Answer 3:

嗯,我有完全相同的问题,因为我是检索方向。 事情是,我没有得到解决(我必须设置一个约束,当谈到检索它时,设备的位置),我不知道如果你是会能。

选择一个磁力罗盘,并试图让北方向时,指南针是在你所描述的情况 - 你会得到相同的非意识的结果。 所以你不能真正期望的设备的罗盘任何做的更好!



Answer 4:

关于过滤,用您的权限几句话。

  1. 我建议做把它变成角度之前场均磁场矢量本身。
  2. 这是错误的做平均化处理/不使用某种幅度的对角只平滑。 角本身不提供足够的数据,以检测方向/标题/轴承。 例如:当你想在一整天就知道平均风向,你必须使用风的力量,而不是仅仅只角。 如果将平均只有角度你会得到完全错误的风向。 至于轴承的方向我会用速度大小。


文章来源: Android getOrientation() method returns bad results