安卓getOrientation()方法返回坏的结果 [英] Android getOrientation() method returns bad results

查看:975
本文介绍了安卓getOrientation()方法返回坏的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要创建3D指南针应用程序。

我用<一个href="http://developer.android.com/reference/android/hardware/SensorManager.html#getOrientation%28float%5B%5D,%20float%5B%5D%29"相对=nofollow> getOrientation 方法来获得方向(几乎相同的实现像<一个href="http://stackoverflow.com/questions/2963705/using-getrotationmatrix-and-getorientation-in-android-2-1">here).如果我把手机放在桌子上效果很好,但是当手机指向天空顶部(减去图片Z轴;球体,地球)。getOrientation开始给予非常糟糕的结果,它提供了值0 Z轴来180度在几个真实度。有什么办法如何晚饭preSS这种行为?我创建了一个小视频什么描述问题(对不起,品质差)。在此先感谢。

解决方案: 当你旋转模型,有区别:

  gl.glRotatef(_angleY​​,0F,1F,0F); //滚
gl.glRotatef(_angleX,1F,0F,0F); //海拔
gl.glRotatef(_angleZ,0F,0F,1F); //方位角


gl.glRotatef(_angleX,1F,0F,0F); //海拔
gl.glRotatef(_angleY​​,0F,1F,0F); //滚
gl.glRotatef(_angleZ,0F,0F,1F); //方位角
 

解决方案

嗯,我可以看到至少有1的问题,你的这个方法。

我认为你把相应的磁力计与均值低通滤波器,平滑的数据的三维矢量。虽然这种做法将工作非常适合传感器值变化没有间断,如加速度传感器的原始数据,它不具有角变量从你的磁强计取工作如此之大一字不差。为什么,有人可能会问?

由于这些角变量(方位角,俯仰,滚动)有一个上界和下结合的,这意味着,上述180度的任意值,假设181度,将绕回到181-360 = -179度,和下方-180度的任何变量将环绕在其他方向。因此,当这些角变量之一亲近这些阈值(180或-180),这个变量将趋于振荡,以接近那些2极端值。当盲目地应用一个低通滤波器为这些值,将得到无论是平滑从180度减小朝-180度或光滑-180增加朝180度。无论哪种方式,其结果看起来很喜欢上面的视频......只要有一个直接从适用的平均缓冲到原始角度数据getOrientation(...),这个问题将是present(应该为present不仅限于在手机直立,而且在那里有方位角的wraparounds过的案件的情况下......也许你可以测试这些错误的嗯...)。

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

不幸的是,没有太多的同时与您的标准平均滤波器粘着可以实施一个优雅溶液。我最常做在这种情况下切换到另一种类型的低通滤波器,它不需要任何深刻的缓冲区操作:一个简单的IIR滤波器(顺序1):

差异= X [N] - Y [N-1]

Y [N] - Y [N-1] =阿尔法的*的(X [N] - Y [N-1)=阿尔法的*的差异

...其中的的是滤波角,的 X 的是原始的角度,和的阿尔法的&小于1类似于一个时间常数,如α= 1对应于没有过滤器的情况下,与低通滤波器的截止频率被降低如α趋近于零。急性眼大概会到现在,这相当于一个简单的比例控制器

注意

这种滤波器允许角度值的环绕的补偿,因为我们可以增加或减去360的差异的,以便确保的无水(差异)&其中; = 180 ,这又确保了过滤角度值将始终增加在最佳方向/减少,达到它的设定值。

这是例如函数调用,这是定期计划,其计算过滤的角度值y对于给定的原始角度值x,可能是这样的:

 专用浮restrictAngle(浮动tmpAngle){
    而(tmpAngle&GT; = 180)tmpAngle- = 360;
    而(tmpAngle&LT; -180)tmpAngle + = 360;
    返回tmpAngle;
}

// x是getOrientation原始角度值(...)
// y是电流滤波角度值
私人浮动calculateFilteredAngle(浮X,浮动Y){
    最终浮动的α= 0.1F;
    浮动差异= X-Y;

    //这里,我们确保ABS(差异)&LT; = 180
    差异= restrictAngle(差异);

    Y + =阿尔法*差异;
    //确保Ÿ停留[-180,180范围内
    Y = restrictAngle(Y);

    返回是;
}
 

功能 calculateFilteredAngle(浮X,浮动Y)定期使用这样的事情(例如,对于方位角 getOrientation(可以被调用。 ..)功能:

  filteredAzimuth = calculateFilteredAngle(方位角,filteredAzimuth);
 

采用这种方法,过滤器将不会表现不好等作为输入的由OP平均滤波器。

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

文件1:Compass3DActivity.java:

 包com.epichorns.compass3D;

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

公共类Compass3DActivity延伸活动{
    // Textviews为显示角度数据
    TextView的mTextView_azimuth;
    TextView的mTextView_pitch;
    TextView的mTextView_roll;

    TextView的mTextView_filtered_azimuth;
    TextView的mTextView_filtered_pitch;
    TextView的mTextView_filtered_roll;


    浮mAngle0_azimuth = 0;
    浮mAngle1_pitch = 0;
    浮mAngle2_roll = 0;

    浮mAngle0_filtered_azimuth = 0;
    浮mAngle1_filtered_pitch = 0;
    浮mAngle2_filtered_roll = 0;

    私人Compass3DView mCompassView;

    私人的SensorManager的SensorManager;
    //传感器的计算值
    浮动[] mGravity = NULL;
    浮动[] mGeomagnetic = NULL;
    浮Rmat [] =新的浮动[9];
    浮动的iMAT [] =新的浮动[9];
    浮方位[] =新的浮动[3];
    SensorEventListener mAccelerometerListener =新SensorEventListener(){
        公共无效onAccuracyChanged(传感器传感器,诠释精度){}

        公共无效onSensorChanged(SensorEvent事件){
            如果(event.sensor.getType()== Sensor.TYPE_ACCELEROMETER){
                mGravity = event.values​​.clone();
                processSensorData();
            }
        }
    };
    SensorEventListener mMagnetometerListener =新SensorEventListener(){
        公共无效onAccuracyChanged(传感器传感器,诠释精度){}

        公共无效onSensorChanged(SensorEvent事件){
            如果(event.sensor.getType()== Sensor.TYPE_MAGNETIC_FIELD){
                mGeomagnetic = event.values​​.clone();
                processSensorData();
                更新();
            }
        }
    };

    私人浮动restrictAngle(浮点tmpAngle){
        而(tmpAngle&GT; = 180)tmpAngle- = 360;
        而(tmpAngle&LT; -180)tmpAngle + = 360;
        返回tmpAngle;
    }

    // x是getOrientation原始角度值(...)
    // y是电流滤波角度值
    私人浮动calculateFilteredAngle(浮X,浮动Y){
        最终浮动的α= 0.3f;
        浮动差异= X-Y;

        //这里,我们确保ABS(差异)&LT; = 180
        差异= restrictAngle(差异);

        Y + =阿尔法*差异;
        //确保Ÿ停留[-180,180范围内
        Y = restrictAngle(Y);

        返回是;
    }



    公共无效processSensorData(){
        如果(mGravity = NULL和放大器;!&安培;!mGeomagnetic = NULL){
            布尔成功= SensorManager.getRotationMatrix(Rmat,IMAT,mGravity,mGeomagnetic);
            如果(成功){
                SensorManager.getOrientation(Rmat,方向);
                mAngle0_azimuth =(浮点)Math.toDegrees((双)方向[0]); //方向包括:方位角,俯仰和横滚
                mAngle1_pitch =(浮点)Math.toDegrees((双)方向[1]); //沥青
                mAngle2_roll =  - (浮点)Math.toDegrees((双)方向[2]); //滚
                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; //迫使焕发出新的刷新
            mGeomagnetic = NULL; //迫使焕发出新的刷新
        }
    }

    / **第一次创建活动时调用。 * /
    @覆盖
    公共无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的LinearLayout LL =新的LinearLayout(本);
        LinearLayout.LayoutParams llParams =新LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);
        ll.setLayoutParams(llParams);
        ll.setOrientation(LinearLayout.VERTICAL);
        ViewGroup.LayoutParams txtParams =新ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        mTextView_azimuth =新的TextView(本);
        mTextView_azimuth.setLayoutParams(txtParams);
        mTextView_pitch =新的TextView(本);
        mTextView_pitch.setLayoutParams(txtParams);
        mTextView_roll =新的TextView(本);
        mTextView_roll.setLayoutParams(txtParams);
        mTextView_filtered_azimuth =新的TextView(本);
        mTextView_filtered_azimuth.setLayoutParams(txtParams);
        mTextView_filtered_pitch =新的TextView(本);
        mTextView_filtered_pitch.setLayoutParams(txtParams);
        mTextView_filtered_roll =新的TextView(本);
        mTextView_filtered_roll.setLayoutParams(txtParams);

        mCompassView =新Compass3DView(本);
        ViewGroup.LayoutParams compassParams =新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(Ⅱ);

        的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);
        更新();
    }


    @覆盖
    公共无效的onDestroy(){
        super.onDestroy();
        sensorManager.unregisterListener(mAccelerometerListener);
        sensorManager.unregisterListener(mMagnetometerListener);
    }


    私人无效更新(){
        mCompassView.changeAngles(mAngle1_filtered_pitch,mAngle2_filtered_roll,mAngle0_filtered_azimuth);

        mTextView_azimuth.setText(方位角:+将String.valueOf(mAngle0_azimuth));
        mTextView_pitch.setText(间距+将String.valueOf(mAngle1_pitch));
        mTextView_roll.setText(卷:+将String.valueOf(mAngle2_roll));

        mTextView_filtered_azimuth.setText(方位角:+将String.valueOf(mAngle0_filtered_azimuth));
        mTextView_filtered_pitch.setText(间距+将String.valueOf(mAngle1_filtered_pitch));
        mTextView_filtered_roll.setText(卷:+将String.valueOf(mAngle2_filtered_roll));

    }
}
 

文件2:Compass3DView.java:

 包com.epichorns.compass3D;

进口android.content.Context;
进口android.opengl.GLSurfaceView;

公共类Compass3DView扩展GLSurfaceView {
    私人Compass3DRenderer mRenderer;

    公共Compass3DView(上下文的背景下){
        超(上下文);
        mRenderer =新Compass3DRenderer(上下文);
        setRenderer(mRenderer);
    }

    公共无效changeAngles(浮动angle0,浮动角度1,浮ANGLE2){
        mRenderer.setAngleX(angle0);
        mRenderer.setAngleY​​(角度1);
        mRenderer.setAngleZ(ANGLE2);
    }

}
 

文件3:Compass3DRenderer.java:

 包com.epichorns.compass3D;


进口java.nio.ByteBuffer中;
进口java.nio.ByteOrder中;
进口java.nio.FloatBuffer中;
进口java.nio.ShortBuffer中;

进口javax.microedition.khronos.egl.EGLConfig;
进口javax.microedition.khronos.opengles.GL10;

进口android.content.Context;
进口android.opengl.GLSurfaceView;


公共类Compass3DRenderer实现GLSurfaceView.Renderer {
    语境mContext;

    //原始缓冲区持有指数
    ShortBuffer _indexBuffer;
    //原始缓冲区来承载顶点
    FloatBuffer _vertexBuffer0;
    FloatBuffer _vertexBuffer1;
    FloatBuffer _vertexBuffer2;
    FloatBuffer _vertexBuffer3;
    FloatBuffer _vertexBuffer4;
    FloatBuffer _vertexBuffer5;
    INT _numVertices = 3; //标准的三角形顶点= 3

    FloatBuffer _textureBuffer0123;



    //私人FloatBuffer _light0Position;
    //私人FloatBuffer _light0Ambient;
    浮_light0Position [] =新的浮动[] {10.0f,10.0f,10.0f,0.0};
    浮_light0Ambient [] =新的浮动[] {0.05f,0.05f,0.05f,1.0F};
    浮_light0Diffuse [] =新的浮动[] {0.5F,0.5F,0.5F,1.0F};
    浮_light0Specular [] =新的浮动[] {0.7f,0.7f,0.7f,1.0F};
    浮_matAmbient [] =新的浮动[] {0.6f,0.6f,0.6f,1.0F};
    浮_matDiffuse [] =新的浮动[] {0.6f,0.6f,0.6f,1.0F};




    私人浮动_angleX = 0F;
    私人浮动_angleY​​ = 0F;
    私人浮动_angleZ = 0F;


    Compass3DRenderer(上下文的背景下){
        超();
        mContext =背景;
    }

    公共无效setAngleX(浮动角){
        _angleX =角;
    }

    公共无效setAngleY​​(浮动角){
        _angleY​​ =角;
    }

    公共无效setAngleZ(浮动角){
        _angleZ =角;
    }

    FloatBuffer InitFloatBuffer(浮法[] SRC){
        ByteBuffer的BB = ByteBuffer.allocateDirect(4 * src.length);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer INBUF = bb.asFloatBuffer();
        inBuf.put(SRC);
        返回INBUF;
    }

    ShortBuffer InitShortBuffer(简称[] SRC){
        ByteBuffer的BB = ByteBuffer.allocateDirect(2 * src.length);
        bb.order(ByteOrder.nativeOrder());
        ShortBuffer INBUF = bb.asShortBuffer();
        inBuf.put(SRC);
        返回INBUF;
    }

    //初始化数据为我们呈现金字塔
    私人无效initTriangles(){

        //侧面的三角形
        浮动[] COORDS = {
            -0.25f,-0.5f,0.25f,
            0.25f,-0.5f,0.25f,
            0F,0.5F,0F
        };

        浮动[] coords1 = {
            0.25f,-0.5f,0.25f,
            0.25f,-0.5f,-0.25f,
            0F,0.5F,0F
        };

        浮动[] coords2 = {
            0.25f,-0.5f,-0.25f,
            -0.25f,-0.5f,-0.25f,
            0F,0.5F,0F
        };

        浮动[] coords3 = {
            -0.25f,-0.5f,-0.25f,
            -0.25f,-0.5f,0.25f,
            0F,0.5F,0F
        };

        //基地三角形
        浮动[] coords4 = {
            -0.25f,-0.5f,0.25f,
            0.25f,-0.5f,-0.25f,
            0.25f,-0.5f,0.25f
        };

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


        浮动[] textures0123 = {
                //贴图坐标的顶点(UV贴图CW)
                0.0,0.0,//左下
                1.0F,0.0,//右下
                0.5F,1.0F,//顶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);

        短[]指数= {0,1,2};
        _indexBuffer = InitShortBuffer(指标);
        _indexBuffer.position(0);

    }


    公共无效onSurfaceCreated(GL10 GL,EGLConfig配置){

        gl.glEnable(GL10.GL_CULL_FACE); //使哪边的分化可以是可见
        gl.glShadeModel(GL10.GL_SMOOTH);

        gl.glFrontFace(GL10.GL_CCW); //这是前面?这是逆时针绘制所述一个
        gl.glCullFace(GL10.GL_BACK); //哪一个不应该被绘制

        initTriangles();

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

    公共无效onDrawFrame(GL10 GL){


        gl.glPushMatrix();

        gl.glClearColor(0,0,0,1.0F); //裁剪背景颜色
        //清除颜色缓冲区,以显示我们在上面叫ClearColor ...
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        //设置旋转
        gl.glRotatef(_angleY​​,0F,1F,0F); //滚
        gl.glRotatef(_angleX,1F,0F,0F); //海拔
        gl.glRotatef(_angleZ,0F,0F,1F); //方位角

        //引起我们的金字塔

        // 4侧面
        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);

        //基面
        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();
    }

    公共无效onSurfaceChanged(GL10 GL,INT W,INT高){
        gl.glViewport(0,0,W,H);
        gl.glViewport(0,0,W,H);

    }



}
 

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

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

解决方案

Well, I can see at least 1 problem with this approach of yours.

I assume that you combine a 3D vector corresponding to your magnetometer with an averaging low pass filter to smoothen the data. Although such approach would work great for a sensor value which varies without discontinuities, such as raw data from accelerometer, it doesn't work so great verbatim with angular variables fetched from your magnetometer. Why, one might ask?

Because those angular variables (azimuth, pitch, roll) have an upper-bound and a lower-bound, which means that any value above 180 degrees, say 181 degrees, would wrap around to 181-360 = -179 degrees, and any variable below -180 degrees would wrap around in the other direction. So when one of those angular variables get close to those thresholds (180 or -180), this variable will tend to oscillate to values close to those 2 extremes. When you blindly apply a low-pass filter to those values, you get either a smooth decreasing from 180 degrees towards -180 degrees, or a smooth increasing from -180 towards 180 degrees. Either way, the result would look quite like your video above... As long as one directly applies an averaging buffer onto the raw angle data from getOrientation(...), this problem will be present (and should be present not only for the case where the phone is upright, but also in the cases where there are azimuth angle wraparounds too... Maybe you could test for those bugs as well...).

You say that you tested this with a buffer size of 1. Theoretically, the problem should not be present if there is no averaging at all, although in some implementations of a circular buffer I've seen in the past, it could mean that there is still averaging done with at least 1 past value, not that there is no averaging at all. If this is your case, we have found the root cause of your bug.

Unfortunately, there isn't much of an elegant solution that could be implemented while sticking with your standard averaging filter. What I usually do in this case is switch to another type of low pass filter, which doesn't need any deep buffer to operate: a simple IIR filter (order 1):

diff = x[n] - y[n-1]

y[n] - y[n-1] = alpha * (x[n] - y[n-1]) = alpha * diff

...where y is the filtered angle, x is the raw angle, and alpha<1 is analogous to a time constant, as alpha=1 corresponds to the no-filter case, and the frequency cutoff of the low-pass filter gets lowered as alpha approaches zero. An acute eye would probably have noticed by now that this corresponds to a simple Proportional Controller.

Such a filter allows the compensation of the wraparound of the angle value because we can add or subtract 360 to diff so as to ensure that abs(diff)<=180, which in turn ensures that the filtered angle value will always increase/decrease in the optimal direction to reach its "setpoint".

An example function call, which is to be scheduled periodically, that calculates a filtered angle value y for a given raw angle value x, could be something like this:

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;
}

The function calculateFilteredAngle(float x, float y) can then be called periodically using something like this (example for azimuth angle from getOrientation(...) function:

filteredAzimuth = calculateFilteredAngle(azimuth, filteredAzimuth);

Using this method, the filter would not misbehave like the averaging filter as mentioned by the OP.

As I could not load the .apk uploaded by the OP, I decided to implement my own test project in order to see if the corrections work. Here is the entire code (it does not use a .XML for the main layout, so I did not include it). Simply copy it to a test project to see if it works on a specific device (tested functional on a HTC Desire w/ Android v. 2.1):

File 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));

    }
}

File 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);
    }

}

File 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);

    }



}

Please note that this code does not compensate for tablet default landscape orientation, so it is only expected to work correctly on a phone (I didn't have a tablet close by to test any correction code).

这篇关于安卓getOrientation()方法返回坏的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆