Вопрос по java, android – Android getOrientation () метод возвращает плохие результаты

10

Я создаю приложение 3D Compass.

Я используюGetOrientation метод, чтобы получить ориентацию (почти такая же реализация, какВот). Если я положу телефон на стол, он будет работать хорошо, но когда верх телефона указывает на небо (минус ось Z на снимке; сфера - это Земля), getOrientation начинает давать действительно плохие результаты. Это дает значения для оси Z от 0 до 180 градусов в нескольких реальных градусах. Есть ли способ, как подавить это поведение? Я создалмаленькое видео что описывает проблему (извините за плохое качество). Заранее спасибо.

enter image description here

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
Рад, что мой код помог, друг :-) epichorns
Привет, не могли бы вы прислать свой апк снова? Версия, которую вы включили в свое сообщение, неправильно декодируется как .apk под Android на моем телефоне и планшете ... epichorns
Я загрузил его снова, тот же адрес. Все та же проблема? skywall
(1) Какова задержка вашего датчика? (2) Проводите ли вы фильтрацию низких частот ваших данных? Morrison Chang
1. Задержка установлена на Fastest (как для акселерометра, так и для датчика магнитного поля). 2. У меня есть круговой буфер, и я вычисляю среднее значение из последних десяти входящих значений. После изменения размера буфера до 1 проблема все еще остается, но модель колеблется гораздо больше. skywall

Ваш Ответ

4   ответа
0

I would suggest to do averaging on Magnetic Field Vector itself before turning it into angles. It is wrong to do averaging/smoothing only on angles without use of some sort of magnitude. Angles themselves are not providing enough data to detect direction/heading/bearing. Example: When you want to know average wind direction during the whole day you must use the strength of the wind, not just only angles. If you will average only angles you will get absolutely wrong wind direction. As for bearing direction I would use the speed for magnitude.
3

вероятно, следует попробовать более длительную задержку, такую как Game, и / или сохранить / увеличить размер циклического буфера. Датчики (акселерометр, компас и т. Д.) На мобильных устройствах по своей сути шумные, поэтому, когда я спросил о «фильтре нижних частот», я имел в виду, что вы используете больше данных для уменьшения частоты полезных обновлений вашего приложения. Ваше видео было сделано внутри, я бы также порекомендовал пойти в место с меньшими электромагнитными помехами, например, в парк, просто чтобы проверить, соответствует ли поведение как стандартному действию сброса компаса (поверните устройство на рисунке-8). В конце вам, возможно, придется применить некоторые эвристические методы, чтобы отбросить «плохие»; данные, чтобы сделать плавный опыт для пользователя.

1. Задержка игры - та же проблема 2. Изменение размера буфера - та же проблема 3. Парковка - все та же :( skywall
1

у меня была точно такая же проблема, как и при поиске ориентации. Дело в том, что я не понял, что это решено (я должен был установить ограничение, когда дело доходит до положения устройства при его извлечении), и я не знаю, сможете ли вы когда-нибудь это сделать.

Выберите магнитный компас и попытайтесь ориентироваться на север, когда компас находится в ситуации, которую вы описываете - вы получите те же результаты, не связанные с ощущениями. Таким образом, вы не можете ожидать, что компас устройства сделает это лучше!

Да, но в подавляющем большинстве устройств на рынке магнитометр представляет собой трехосный магнитометр, в то время как обычный магнитный компас в основном «проецирует». вектор магнитного поля на его механическую 2D плоскость POV ...
11

Я предполагаю, что вы комбинируете трехмерный вектор, соответствующий вашему магнитометру, с усредняющим фильтром нижних частот, чтобы сгладить данные. Хотя такой подход будет работать отлично для значения датчика, которое изменяется без разрывов, такого как необработанные данные от акселерометра, он не работает так дословно с угловыми переменными, выбранными из вашего магнитометра. Почему, можно спросить?

Поскольку эти угловые переменные (азимут, угол наклона, крен) имеют верхнюю и нижнюю границы, это означает, что любое значение выше 180 градусов, скажем 181 градуса, будет охватывать 181-360 = -179 градусов, а любая переменная ниже -180 градусов будет вращаться в другом направлении. Поэтому, когда одна из этих угловых переменных приближается к этим порогам (180 или -180), эта переменная будет иметь тенденцию колебаться до значений, близких к этим двум крайностям. Когда вы слепо применяете фильтр нижних частот к этим значениям, вы получаете плавное уменьшение от 180 градусов до -180 градусов или плавное увеличение от -180 до 180 градусов. В любом случае, результат будет выглядеть так же, как ваше видео выше ... До тех пор, пока один непосредственно применяет буфер усреднения к необработанным данным угла изgetOrientation(...)эта проблема будет присутствовать (и должна присутствовать не только в случае, когда телефон находится в вертикальном положении, но также и в случаях, когда также есть обтекание по азимутальному углу ... Может быть, вы также можете проверить эти ошибки ...) ,

Вы говорите, что тестировали это с размером буфера 1. Теоретически, проблема не должна присутствовать, если вообще нет усреднения, хотя в некоторых реализациях циклического буфера, который я видел в прошлом, это может означать, что усреднение все еще выполняется, по крайней мере, с 1 прошлым значением, но усреднение вообще не производится. Если это ваш случай, мы нашли причину вашей ошибки.

К сожалению, не так много элегантного решения, которое можно было бы реализовать, придерживаясь стандартного фильтра усреднения. В этом случае я обычно переключаюсь на другой тип фильтра нижних частот, который не нуждается в каком-либо глубоком буфере для работы: простой фильтр БИХ (порядок 1):

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

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

...гдеy угол фильтрации,x это необработанный угол, иalpha& lt; 1 аналогична постоянной времени, поскольку альфа = 1 соответствует случаю отсутствия фильтра, а отсечка частоты фильтра нижних частот снижается, когда альфа приближается к нулю. Острый глаз, вероятно, уже заметил, что это соответствует простомуProportional Controller.

Такой фильтр позволяет компенсировать изменение значения угла, потому что мы можем добавить или вычесть 360 кdiff чтобы гарантировать, чтоabs(diff)<=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.

Поскольку я не мог загрузить .apk, загруженный OP, я решил реализовать свой собственный тестовый проект, чтобы посмотреть, работают ли исправления. Вот весь код (он не использует .XML для основного макета, поэтому я не включил его). Просто скопируйте его в тестовый проект и посмотрите, работает ли он на определенном устройстве (проверенный функционал на HTC Desire с Android v. 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);

    }



}

Обратите внимание, что этот код не компенсирует альбомную ориентацию планшета по умолчанию, поэтому ожидается, что он будет корректно работать только на телефоне (у меня не было планшета рядом, чтобы проверить любой код исправления).

Привет. Да, но я должен отметить, что для решения этой проблемы,diff должно быть между [-180, 180 [, а не [0, 360 [, что не указано в вашей формуле. Так что если вы добавите что-то вродеif(new_angle>=180) new_angle-=360 это должно быть хорошо. Дело в том, что используя диапазон 0-360 дляdiff приведет к поведению фильтра, который не будет использовать оптимальный путь для достижения желаемого места назначения. Это означает, что вблизи вашего порога угла 360, когдаdiff может прыгать вперед и назад от 0 до 360, вы получите ту же проблему, отсюда необходимость в [-180, 180 [ограничение угла дляdiff.
Итак, предварительный просмотр гораздо более плавный, но проблема не решена вообще. skywall
Извиняюсь за (несколько) недостаток полноты в моем предыдущем комментарии: на самом деле, еслиdiff находится между [0, 360 [, проблемы будут возникать по всему диапазону вашего угла, так как это только увеличит ваш фильтрованный угол, e монотонно! Это по-прежнему подразумевает, что при переходе отdiff = 0 доdiff = 360 будет нежелательный прыжок, хотя ...
Прежде всего, спасибо за отличный ответ. 1. Я уже решил180 & -180 проблема с преобразованием значений между 160 & amp; -160 до значений 160 & amp; 200. Я использовал формулуnew_angle = (old_angle+360)%360, 2. Я попробую ваш БИХ-фильтр, выглядит действительно хорошо. Спасибо. skywall
Итак, вы уже попробовали мой подход? Это что-то изменило?

Похожие вопросы