Comment utiliser les données de capteur onSensorChanged en combinaison avec OpenGL

(edit: j’ai ajouté la meilleure approche de travail dans mon cadre de réalité augmentée et maintenant aussi le gyroscope en compte, ce qui le rend encore plus stable: le framework DroidAR )

J’ai écrit un TestSuite pour savoir comment calculer les angles de rotation à partir des données que vous obtenez dans SensorEventListener.onSensorChanged() . J’espère vraiment que vous pourrez compléter ma solution pour aider les personnes qui auront les mêmes problèmes que moi. Voici le code, je pense que vous l’aurez compris après l’avoir lu.

N’hésitez pas à le changer, l’idée principale était d’implémenter plusieurs méthodes pour envoyer les angles d’orientation à la vue opengl ou à toute autre cible qui en aurait besoin.

les méthodes 1 à 4 fonctionnent, elles envoient directement la rotationMasortingx à la vue OpenGl.

La méthode 6 fonctionne maintenant aussi, mais je n’ai aucune explication sur la raison pour laquelle la rotation doit être effectuée yx z ..

toutes les autres méthodes ne fonctionnent pas ou bogué et j’espère que quelqu’un sait les faire fonctionner. Je pense que la meilleure méthode serait la méthode 5 si cela fonctionnait, car ce serait le plus facile à comprendre mais je ne suis pas sûr de son efficacité . le code complet n’est pas optimisé, je vous recommande donc de ne pas l’utiliser tel quel dans votre projet.

C’est ici:

 /** * This class provides a basic demonstration of how to use the * {@link android.hardware.SensorManager SensorManager} API to draw a 3D * compass. */ public class SensorToOpenGlTests extends Activity implements Renderer, SensorEventListener { private static final boolean TRY_TRANSPOSED_VERSION = false; /* * MODUS overview: * * 1 - unbufferd data directly transfaired from the rotation masortingx to the * modelview masortingx * * 2 - buffered version of 1 where both acceleration and magnetometer are * buffered * * 3 - buffered version of 1 where only magnetometer is buffered * * 4 - buffered version of 1 where only acceleration is buffered * * 5 - uses the orientation sensor and sets the angles how to rotate the * camera with glrotate() * * 6 - uses the rotation masortingx to calculate the angles * * 7 to 12 - every possibility how the rotationMasortingx could be constructed * in SensorManager.getRotationMasortingx (see * http://www.songho.ca/opengl/gl_anglestoaxes.html#anglestoaxes for all * possibilities) */ private static int MODUS = 2; private GLSurfaceView openglView; private FloatBuffer vertexBuffer; private ByteBuffer indexBuffer; private FloatBuffer colorBuffer; private SensorManager mSensorManager; private float[] rotationMasortingx = new float[16]; private float[] accelGData = new float[3]; private float[] bufferedAccelGData = new float[3]; private float[] magnetData = new float[3]; private float[] bufferedMagnetData = new float[3]; private float[] orientationData = new float[3]; // private float[] mI = new float[16]; private float[] resultingAngles = new float[3]; private int mCount; final static float rad2deg = (float) (180.0f / Math.PI); private boolean landscape; public SensorToOpenGlTests() { } /** Called with the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); openglView = new GLSurfaceView(this); openglView.setRenderer(this); setContentView(openglView); } @Override protected void onResume() { // Ideally a game should implement onResume() and onPause() // to take appropriate action when the activity looses focus super.onResume(); openglView.onResume(); if (((WindowManager) getSystemService(WINDOW_SERVICE)) .getDefaultDisplay().getOrientation() == 1) { landscape = true; } else { landscape = false; } mSensorManager.registerListener(this, mSensorManager .getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mSensorManager .getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mSensorManager .getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_GAME); } @Override protected void onPause() { // Ideally a game should implement onResume() and onPause() // to take appropriate action when the activity looses focus super.onPause(); openglView.onPause(); mSensorManager.unregisterListener(this); } public int[] getConfigSpec() { // We want a depth buffer, don't care about the // details of the color buffer. int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE }; return configSpec; } public void onDrawFrame(GL10 gl) { // clear screen and color buffer: gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // set target masortingx to modelview masortingx: gl.glMasortingxMode(GL10.GL_MODELVIEW); // init modelview masortingx: gl.glLoadIdentity(); // move camera away a little bit: if ((MODUS == 1) || (MODUS == 2) || (MODUS == 3) || (MODUS == 4)) { if (landscape) { // in landscape mode first remap the rotationMasortingx before using // it with glMultMasortingxf: float[] result = new float[16]; SensorManager.remapCoordinateSystem(rotationMasortingx, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, result); gl.glMultMasortingxf(result, 0); } else { gl.glMultMasortingxf(rotationMasortingx, 0); } } else { //in all other modes do the rotation by hand //the order yxz is important! gl.glRotatef(resultingAngles[2], 0, 1, 0); gl.glRotatef(resultingAngles[1], 1, 0, 0); gl.glRotatef(resultingAngles[0], 0, 0, 1); } //move the axis to simulate augmented behaviour: gl.glTranslatef(0, 2, 0); // draw the 3 axis on the screen: gl.glVertexPointer(3, GL_FLOAT, 0, vertexBuffer); gl.glColorPointer(4, GL_FLOAT, 0, colorBuffer); gl.glDrawElements(GL_LINES, 6, GL_UNSIGNED_BYTE, indexBuffer); } public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); float r = (float) width / height; gl.glMasortingxMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustumf(-r, r, -1, 1, 1, 10); } public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glDisable(GL10.GL_DITHER); gl.glClearColor(1, 1, 1, 1); gl.glEnable(GL10.GL_CULL_FACE); gl.glShadeModel(GL10.GL_SMOOTH); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // load the 3 axis and there colors: float vertices[] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; float colors[] = { 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1 }; byte indices[] = { 0, 1, 0, 2, 0, 3 }; ByteBuffer vbb; vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); vbb = ByteBuffer.allocateDirect(colors.length * 4); vbb.order(ByteOrder.nativeOrder()); colorBuffer = vbb.asFloatBuffer(); colorBuffer.put(colors); colorBuffer.position(0); indexBuffer = ByteBuffer.allocateDirect(indices.length); indexBuffer.put(indices); indexBuffer.position(0); } public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onSensorChanged(SensorEvent event) { // load the new values: loadNewSensorData(event); if (MODUS == 1) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); } if (MODUS == 2) { rootMeanSquareBuffer(bufferedAccelGData, accelGData); rootMeanSquareBuffer(bufferedMagnetData, magnetData); SensorManager.getRotationMasortingx(rotationMasortingx, null, bufferedAccelGData, bufferedMagnetData); } if (MODUS == 3) { rootMeanSquareBuffer(bufferedMagnetData, magnetData); SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, bufferedMagnetData); } if (MODUS == 4) { rootMeanSquareBuffer(bufferedAccelGData, accelGData); SensorManager.getRotationMasortingx(rotationMasortingx, null, bufferedAccelGData, magnetData); } if (MODUS == 5) { // this mode uses the sensor data recieved from the orientation // sensor resultingAngles = orientationData.clone(); if ((-90 > resultingAngles[1]) || (resultingAngles[1] > 90)) { resultingAngles[1] = orientationData[0]; resultingAngles[2] = orientationData[1]; resultingAngles[0] = orientationData[2]; } } if (MODUS == 6) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); final float[] anglesInRadians = new float[3]; SensorManager.getOrientation(rotationMasortingx, anglesInRadians); //TODO check for landscape mode resultingAngles[0] = anglesInRadians[0] * rad2deg; resultingAngles[1] = anglesInRadians[1] * rad2deg; resultingAngles[2] = anglesInRadians[2] * -rad2deg; } if (MODUS == 7) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in xyz * order Rx*Ry*Rz */ resultingAngles[2] = (float) (Math.asin(rotationMasortingx[2])); final float cosB = (float) Math.cos(resultingAngles[2]); resultingAngles[2] = resultingAngles[2] * rad2deg; resultingAngles[0] = -(float) (Math.acos(rotationMasortingx[0] / cosB)) * rad2deg; resultingAngles[1] = (float) (Math.acos(rotationMasortingx[10] / cosB)) * rad2deg; } if (MODUS == 8) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in zyx */ resultingAngles[2] = (float) (Math.asin(-rotationMasortingx[8])); final float cosB = (float) Math.cos(resultingAngles[2]); resultingAngles[2] = resultingAngles[2] * rad2deg; resultingAngles[1] = (float) (Math.acos(rotationMasortingx[9] / cosB)) * rad2deg; resultingAngles[0] = (float) (Math.asin(rotationMasortingx[4] / cosB)) * rad2deg; } if (MODUS == 9) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in zxy * * note z axis looks good at this one */ resultingAngles[1] = (float) (Math.asin(rotationMasortingx[9])); final float minusCosA = -(float) Math.cos(resultingAngles[1]); resultingAngles[1] = resultingAngles[1] * rad2deg; resultingAngles[2] = (float) (Math.asin(rotationMasortingx[8] / minusCosA)) * rad2deg; resultingAngles[0] = (float) (Math.asin(rotationMasortingx[1] / minusCosA)) * rad2deg; } if (MODUS == 10) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in yxz */ resultingAngles[1] = (float) (Math.asin(-rotationMasortingx[6])); final float cosA = (float) Math.cos(resultingAngles[1]); resultingAngles[1] = resultingAngles[1] * rad2deg; resultingAngles[2] = (float) (Math.asin(rotationMasortingx[2] / cosA)) * rad2deg; resultingAngles[0] = (float) (Math.acos(rotationMasortingx[5] / cosA)) * rad2deg; } if (MODUS == 11) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in yzx */ resultingAngles[0] = (float) (Math.asin(rotationMasortingx[4])); final float cosC = (float) Math.cos(resultingAngles[0]); resultingAngles[0] = resultingAngles[0] * rad2deg; resultingAngles[2] = (float) (Math.acos(rotationMasortingx[0] / cosC)) * rad2deg; resultingAngles[1] = (float) (Math.acos(rotationMasortingx[5] / cosC)) * rad2deg; } if (MODUS == 12) { SensorManager.getRotationMasortingx(rotationMasortingx, null, accelGData, magnetData); rotationMasortingx = transpose(rotationMasortingx); /* * this assumes that the rotation masortingces are multiplied in xzy */ resultingAngles[0] = (float) (Math.asin(-rotationMasortingx[1])); final float cosC = (float) Math.cos(resultingAngles[0]); resultingAngles[0] = resultingAngles[0] * rad2deg; resultingAngles[2] = (float) (Math.acos(rotationMasortingx[0] / cosC)) * rad2deg; resultingAngles[1] = (float) (Math.acos(rotationMasortingx[5] / cosC)) * rad2deg; } logOutput(); } /** * transposes the masortingx because it was transposted (inverted, but here its * the same, because its a rotation masortingx) to be used for opengl * * @param source * @return */ private float[] transpose(float[] source) { final float[] result = source.clone(); if (TRY_TRANSPOSED_VERSION) { result[1] = source[4]; result[2] = source[8]; result[4] = source[1]; result[6] = source[9]; result[8] = source[2]; result[9] = source[6]; } // the other values in the masortingx are not relevant for rotations return result; } private void rootMeanSquareBuffer(float[] target, float[] values) { final float amplification = 200.0f; float buffer = 20.0f; target[0] += amplification; target[1] += amplification; target[2] += amplification; values[0] += amplification; values[1] += amplification; values[2] += amplification; target[0] = (float) (Math .sqrt((target[0] * target[0] * buffer + values[0] * values[0]) / (1 + buffer))); target[1] = (float) (Math .sqrt((target[1] * target[1] * buffer + values[1] * values[1]) / (1 + buffer))); target[2] = (float) (Math .sqrt((target[2] * target[2] * buffer + values[2] * values[2]) / (1 + buffer))); target[0] -= amplification; target[1] -= amplification; target[2] -= amplification; values[0] -= amplification; values[1] -= amplification; values[2] -= amplification; } private void loadNewSensorData(SensorEvent event) { final int type = event.sensor.getType(); if (type == Sensor.TYPE_ACCELEROMETER) { accelGData = event.values.clone(); } if (type == Sensor.TYPE_MAGNETIC_FIELD) { magnetData = event.values.clone(); } if (type == Sensor.TYPE_ORIENTATION) { orientationData = event.values.clone(); } } private void logOutput() { if (mCount++ > 30) { mCount = 0; Log.d("Compass", "yaw0: " + (int) (resultingAngles[0]) + " pitch1: " + (int) (resultingAngles[1]) + " roll2: " + (int) (resultingAngles[2])); } } } 

Je n’ai pas encore pu tester le code (mais je vais le faire, ça a l’air vraiment intéressant). Une chose qui a attiré mon attention est que vous ne semblez pas filtrer les données du capteur de quelque manière que ce soit.

Les lectures de capteur sont très bruyantes par nature, spécialement le capteur magnétique. Je vous suggère de mettre en œuvre un filtrage passe-bas.

Voir ma réponse précédente pour plus de lecture.

Il serait plus facile de tester et de déboguer la méthode 5 en utilisant la fonction lookAt de GLU: http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml

Aussi, comme Villoren a suggéré, il est bon de filtrer les données de votre capteur, mais cela ne causerait pas vraiment de bugs si vous déplacez lentement votre périphérique. Si vous voulez essayer, un simple serait comme suit:

 newValue = oldValue * 0.9 + sensorValue * 0.1; oldValue = newValue; 

Après avoir analysé votre code ci-dessus, dans la méthode 5, vous atsortingbuez les données d’orientation comme suit:

 resultingAngles[1] = orientationData[0]; // orientation z axis to y axis resultingAngles[2] = orientationData[1]; // orientation x axis to z axis resultingAngles[0] = orientationData[2]; // orientation y axis to x axis 

Vous avez fait la rotation de manière yzx. Essayez de changer l’orientation.

Je pense que cela pourrait être le problème là-bas .. S’il vous plaît vérifier et laissez-moi savoir ..

Reportez-vous à la documentation pour connaître les valeurs des événements, http://developer.android.com/guide/topics/sensors/sensors_position.html

Merci pour votre travail acharné ..

Notez que si vous obtenez des lectures systématiquement erronées, vous devrez peut-être étalonner votre boussole en la déplaçant avec vos poignets sur la figure 8.

Difficile d’expliquer cela en mots; regarder cette vidéo: http://www.youtube.com/watch?v=sP3d00Hr14o

Vous pouvez utiliser and-engine pour l’utilisation de capteurs avec OpenGL. Consultez l’exemple https://github.com/nicolasgramlich/AndEngineExamples/tree/GLES2/src/org/andengine/examples/app/cityradar

Découvrez l’ application de démonstration Sensor fusion qui utilise différents capteurs (gyroscope, vecteur de rotation, accéléromètre + boussole, etc.) et affiche les résultats des événements onSensorChanged sous la forme d’un cube coloré qui tourne en fonction de votre téléphone.

Les résultats de ces événements sont stockés sous forme de quaternions et de masortingces de rotation et utilisés dans cette classe, OpenGL.