I really hope someone is able to help me:
I am working on an Android App that displays the angle the phone is held
I am using this code:
protected void onCreate(Bundle savedInstanceState)
{
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mAccelerometer,100000);
mSensorManager.registerListener(this, mMagnetometer, 100000);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = lowPass(event.values.clone(), mGravity);
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = lowPass(event.values.clone(), mGeomagnetic);
if (mGravity != null && mGeomagnetic != null) {
boolean success = SensorManager.getRotationMatrix(Ra, Ia, mGravity, mGeomagnetic);
if (success) {
SensorManager.getOrientation(Ra, orientation);
float tempxGy = (orientation[0]);// az
float tempyGy = (orientation[1]);// pitch
float tempzGy = (orientation[2]); //roll
}
Im further processing the values that I get here but thats not the problem here.
When the phone is flat on the table the values look like that:
roll -0,018
pitch 0,024
yaw 2,51
that looks fine to me (also if I convert the values to euler angles)
now imagine that I take the phone from the table - the bottom of the phone (where the micro usb port is) stays on the table and I lift the side with the camera
the values of the pitch are getting more and more negative - till the pitch reaches -1.53 (-90 in euler) as soon as this value is reached the values are increasing again and if the phone is on the table with the display facing the table the pitch is back to 0 again.
The problem is, that I have to differenciate between the two values (for example -1 and -1
How can I do that? The roll isnt a problem as the roll has a 180 degree maximum and not this 90 degree problem..
I really hope you can help me!
So
if(mGravity[2]<0) orientation[1] = (float) (Math.PI + orientation[1]);
is enough to get values that are like I need them --- but now I have a new problem - I would like to remap the coordinate system, so I get 0 degrees (euler) when the phone is on the table and +90 if the phone is standing on the micro usb port
I tried
SensorManager.remapCoordinateSystem(Ra, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, Ri);
but now the roll and pitch are swapped --- what would I have to do to simply get positive pitch values as long as the display is facing up and negative values if the display is facing down?
Is the remap the wrong idea and I could do it simpler by doing something else?
Related
For an application I'm making, I need to have a camera and a compass. The application is set to be at landscape mode in the manifest.
First I've implemented the compass. As suggested in Android Developers, I used two sensors - Accelerometer and Magnetic Field. This is how I've done it:
I have my activity implement SensorEventListener. In onCreate() I initialize my sensorManager using:
sManager = (SensorManager) getSystemService(SENSOR_SERVICE);
I register my listeners in onResume() like so:
sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.SENSOR_DELAY_NORMAL);
sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),SensorManager.SENSOR_DELAY_NORMAL);
and of course unregister them in onPause().
I don't use onAccuracyChanged(). this is what I do in onSensorChanged():
#Override
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD:
mags = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
accels = event.values.clone();
break;
}
if (mags != null && accels != null) {
gravity = new float[9];
magnetic = new float[9];
SensorManager.getRotationMatrix(gravity, magnetic, accels, mags);
float[] outGravity = new float[9];
float inclination = (float) Math.acos(gravity[8]);
if (inclination < Math.toRadians(25)
|| inclination > Math.toRadians(155)) {
// device is close to flat. Remap for landscape.
SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_Y,SensorManager.AXIS_MINUS_X, outGravity);
SensorManager.getOrientation(outGravity, values);
} else {
// device is not flat. Remap for landscape and perpendicular
SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_X,SensorManager.AXIS_Z, outGravity);
SensorManager.getOrientation(outGravity, values);
}
azimuth = Math.round(Math.toDegrees(values[0]));
}
}
As you can see, I differentiate between when the phone is lying flat on the table, and when the user holds it (as you would when taking a picture). When I use this code alone, everything works great more or less. I'm getting correct azimuth values both when phone is lying on the table and when holding it perpendicular to the table (about 5-10 degrees difference, but I can live with that).
The problem starts when adding the camera preview to the application.
I have my activity implement SurfaceHolder.Callback. I initialize my camera in onCreate():
SurfaceView cameraView = (SurfaceView)findViewById(R.id.camera_view);
surfaceHolder = cameraView.getHolder();
surfaceHolder.addCallback(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
This is how I implement the interface:
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
camera = Camera.open();
camera.setDisplayOrientation(0);
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
if (isCameraOn) {
camera.stopPreview();
isCameraOn = false;
}
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
isCameraOn = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
camera.stopPreview();
camera.release();
camera = null;
}
When I add the camera code to my project, and show the camera on the phone's screen, my sensors dont work properly when phone is perpendicular suddenly. If phone is lying flat on the table, the azimuth values I'm getting are correct. When phone is being held perpendicular to the table, my azimuth values are off by about 40 degrees (though stable).
I've tried looking for a solution (both by myself and online), but so far my efforts were in vain. I would love to get some direction on how to tackle this problem.Thanks!
First TYPE_MAGNETIC_FIELD sensor will not available in all devices.
You can use TYPE_ACCELEROMETER sensor alone to accomplish your requirement.
Retrieve accelerometer sensor
Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Just compare and copy values when sensor change event call
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values;
}
Then you can use below function to get sensor values of all axis.
public int[] getDeviceAngles() {
float[] g = mGravity.clone();
double normOfG = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);
// Normalize the accelerometer vector
g[0] = (float) (g[0] / normOfG);
g[1] = (float) (g[1] / normOfG);
g[2] = (float) (g[2] / normOfG);
int x = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[0])));
int pitch = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[2])));
int rollValue = (int) Math.round(Math.toDegrees(Math.atan2(g[2], g[0])));
int pitchValue = pitch * -1;
int[] values = new int[3];
values[0] = x;
values[1] = pitchValue;
values[2] = rollValue;
//values contains: azimut, pitch and roll
return values;
}
I am designing an app that needs to check the device's orientation (Azimuth, Pitch and Roll). I use the following code to achieve this:
#Override
public void onSensorChanged(SensorEvent event) {
if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
gravityMatrix = event.values.clone();// Fill gravityMatrix with accelerometer values
else if(event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
geomagneticMatrix = event.values.clone();// Fill geomagneticMatrix with magnetic-field sensor values
if(gravityMatrix != null && geomagneticMatrix != null){
RMatrix = new float[16];
IMatrix = new float[16];
SensorManager.getRotationMatrix(RMatrix, IMatrix, gravityMatrix, geomagneticMatrix);// Retrieve RMatrix, necessary for the getOrientation method
SensorManager.getOrientation(RMatrix, orientation);// Get the current orientation of the device
}
}
Now I am able to get azimuth, pitch and roll values from the 'orientation' float[]. Everything goes fine for the azimuth and roll values (it returns the correct angle), however when I print the pitch value (orientation[1]), I always retrieve an angle between PI/2 and -PI/2. I don't understand why? I am unable to retrieve an angle greater than PI/2 or less than -PI/2. As soon as I have an angle of +- PI/2 and I keep on rotating my device (Samsung Galaxy S2) the angle suddenly decreases after it reached the PI/2 value.
Can anyone explain me why the pitch-angle is behaving so uncommon?
Thanks in advance!
Pitch is calculated as pitch = (float) Math.asin(-RMatrix[7]); The range of the arcsin function is [-PI/2, PI/2], so asin can only take value in between -PI/2 and PI/2.
I have found many threads about how to handle the device' rotation,orientation with motion and position sensors.
I would like to create an app which i will use in my car, first i would like to measure the rotation degree of the car.
So i put my phone to a phone case and for example when i turn left with the car i would like to see the car' turning degree on the phone.
Is it possible by magnetic and accelero meter?
I post a code that for first i think okay. (let's say that i hold my phone "portait" mode so not landscape for first)
private static SensorManager sensorService;
//magnetic
private Sensor mSensor;
//accelerometer
private Sensor gSensor;
private float[] mValuesMagnet = new float[3];
private float[] mValuesAccel = new float[3];
private float[] mValuesOrientation = new float[3];
private float[] mRotationMatrix = new float[9];
#Override
public void onCreate(Bundle savedInstanceState) {
nf.setMaximumFractionDigits(2);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensorService = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
this.mSensor = sensorService.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
this.gSensor = sensorService.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorService.registerListener(this, gSensor, SensorManager.SENSOR_DELAY_NORMAL);
sensorService.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
}
#Override
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, mValuesAccel, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, mValuesMagnet, 0, 3);
break;
}
SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);
// double azimuth = Math.toDegrees(mValuesOrientation[0]); //azimuth, rotation around the Z axis.
// double pitch = Math.toDegrees(mValuesOrientation[1]); // pitch, rotation around the X axis.
double roll = Math.toDegrees(mValuesOrientation[2]); //roll, rotation around the Y axis.
//normalize
// azimuth = azimuth>0?azimuth:azimuth+360;
roll = roll>0?roll:roll+360;
String txt = "roll= "+Math.round(roll);
((EditText)findViewById(R.id.szog)).setText(txt);
}
Questions:
- How accurate will this app in a car? (what can i do to be more accurate?)
- What should i do when i hold my phone at "landscape" mode?
Is the roll from orientation still okay?
Please note that this is a very first try so there are so much to do!
But first i want to see how can i achive that
Thanks!
If you had your Android device set up like a compass, then there would be an arrow that always pointed to magnetic north. So by measuring the change in the direction of that arrow, you could measure the rotation of your car.
To get a better indication of the orientation of your Android device, use
Sensor.TYPE_GRAVITY and Sensor.TYPE_MAGNETIC_FIELD, instead of Sensor.TYPE_ACCELEROMETER and Sensor.TYPE_MAGNETIC_FIELD. This is better because Sensor.TYPE_GRAVITY tries to filter out the effects due to the movement of the device.
If the Android device is lying flat on a surface, then the direction of magnetic north is defined by the azimuth that comes out of SensorManager.getOrientation(...). When it's standing up or when it's on its side then it's a bit more complicated. However, I suggest starting with the device lying flat first because that's the easiest case, and then you can progress to more difficult cases.
I'm currently experimenting with some sensors of Android phones. For testing, I'm using a Samsung Galaxy S. As it does not have a gyroscope, I'm using accelerometer and sensor for magnetic field.
What I basically want to do, is to get a certain angle, when moving the device. I try to explain: consider you are holding the phone in landscape mode in front of your face and then you turn yourself by 90 degrees to the right.
I use the following code to get the current rotation matrix:
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mGravity = event.values.clone();
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mGeomagnetic = event.values.clone();
}
if (mGravity != null && mGeomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity,
mGeomagnetic);
}
}
This works well and then I use SensorManager.getAngleChange(angleChange, R, lastR); to achieve the angle change.
I then get (roughly) 65° in angleChange[1] if I turn myself as described above and do not tilt the phone or change anything else...
But if I also tilt the phone by 90° when turning myself (so that display is looking to the ceiling afterwards) I get (roughly) 90° in angleChange[1].
I'm very confused now, why such a rotation affects the value in angleChange[1] and on the other hand why it is needed to get the expected 90°.
What I want to achieve is to get the angle when moving the phone as described above (not in 90° degree steps but this sort of orientation change) no matter which other orientation changes (along the two other axes) are made.
Is there any possibility for this?
i made my own application in Android that use compass and accelerometer sensors to display the degrees of rotation and inclination of my device. I initialized all the listener and objects i needed (i followed some tutorials), and now i can catch the degrees as i wished. The problem is that the measures that sensors return aren't accurate. I mean even if i try to round the values of degrees i catch from the sensor, they oscillate between -/+ 7 (or 8) degrees every fraction of a second, even if i stay in a grass field away from any disturbing source. What i want to have is a accurate measure of the degrees, something like a method to round the values i recieve from the sensors.
float[] mags = null;
float[] accels = null;
float[] R = new float[matrix_size];
float[] outR = new float[matrix_size];
float[] I = new float[matrix_size];
float[] values = null;
private void startSensor() {
sensorMan.registerListener(this, sensorMan.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
sensorMan.registerListener(this, sensorMan.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
return;
}
switch (event.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD:
mags = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
accels = event.values.clone();
break;
}
if (mags != null && accels != null) {
SensorManager.getRotationMatrix(R, I, accels, mags);
// Correct if screen is in Landscape
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X,
SensorManager.AXIS_Z, outR);
SensorManager.getOrientation(outR, values);
azimuth = (float) Math.round((Math.toDegrees(values[0]))*7)/7;
azimuth = ( azimuth + 360)%360;
//here is inclination. The problem is just the same with compass
//inclination=-Math.round((float) (values[1]*(360/(2*Math.PI))));
//other code to update my view
//in azimuth i have the degree value. It changes continuously
//even if i aim still the same direction
}
}
See my answer here: Smoothing data from a sensor
I run this filter on both the accelerometer and magetometer event values before passing them to SensorManager.getRotationMatrix(). I think this algorithm has the advantage of not having to keep a large array of historic values, just the prior low-pass output array.
The algorithm was derived from this Wikipedia entry: http://en.wikipedia.org/wiki/Low-pass_filter#Algorithmic_implementation
What you're seeing is the real thing- the orientation sensors on most phones are only good enough to give you a rough compass heading.
If you want to smooth the displayed value out so it gives you something that's doesn't appear to change randomly I recommend implementing a http://en.wikipedia.org/wiki/Moving_average or other smoothing filter in Java on that orientation result.
For the highest performance you could write the filter using the NDK and use the Boost Accumulators library: http://www.boost.org/doc/libs/1_46_1/doc/html/accumulators.html
I did this using a Kalman filter from here:
Greg Czerniak's Website
I'm sending data to a udp port and smoothing it on the PC using python.
But I guess you can find a Kalman filter implementation for java/android out there.