I've been trying for a long time to try to figure out why the way my game plays varies slightly differently on different devices (i.e.: some devices seem to be more sensitive than others with regards to the accelerometers).
I've just noticed that the when tilting my devices and logging the output, on one device, the output seems to be between -9.5 and + 9.5 and on another, it appears to be around -10.7 to +10.7.
I'm using this returned data to move my sprite.
#Override
public void onSensorChanged(SensorEvent event) {
tiltAmount = event.values[device_rotation];
Log.v("Tag", "Value: " + tiltAmount);
}
In the above code example, as I'm tilting the device to the opposite extremes (90° anti-clockwise and 90° clockwise), I'm getting the ranges described above.
I would like this data to be consistent across device.
Does anyone have any idea how I could normalise it?
It seems like you have extreme values defined. In that scenario, you could divide a given value by the total dynamic range to get normalized value:
NormalizedValue = x / (Highest - Lowest)
Related
I'm trying to detect patterns of knocking on the surface that the device is located on.
For example, if a user knocks one time on the surface, run method A. If the user knocks two times, run method B.
I have everything I need to make this happen except for the logics in the onSensorChanged method.
This is my code right now:
#Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
//Check time interval, not sure if this is correct though.
long actualTime = System.currentTimeMillis();
if ((actualTime - lastUpdate) > 100) {
long diffTime = (actualTime - lastUpdate);
lastUpdate = actualTime;
//This is where the magic should happen
}
}
I guess the main question is, how do I detect vibrations? Almost all other examples on the net is about how to detect shakes and movement.
Real answer- you're going to need to study DSP to get good results. This is a non-trivial problem.
Quick overview- when a vibration occurs, you're going to see a sinusoidal attenuating wave pattern (the attenuating signal after the main vibration is called "ringing" and is a bad thing for us- it means we need to separate ringing from real results). This can be detected and a vibration signalled based on looking for rapid changes in amplitude on the downwards vector (whichever one has gravity on it at the moment). The relative heights of the peak of the waves should be the relative strength of the knock.
So detecting one knock is fairly easy. Things that aren't easy:
*Telling the difference between a knock and footsteps across the room- both cause vibrations. They'll look the same. You may be able to filter it out via frequency analysis and filters
*Telling two knocks vs one knock in a short time frame. The second knock tends to be weaker, and will be difficult to tell apart form the ringing of the first knock. It may also have destructive interference with the first wave.
*Telling exactly when a knock occurred. There will be a time delay that may not be constant, and trying to figure it out means trying to find an exact peak. Difficult to do with noise.
*Telling a knock in a noisy environment (vibrationally noisy, not sound). Again, you'll need filtering.
I've actually done this, somewhat. And failed mostly I think. We were able to detect knocks well, but not to filter out noise at all. Of course we were looking for extremely small (1 finger) knocks, if you're looking for sharp raps you'll have fewer problems as the spike will be larger compared to the noise level. If you're expecting a single sharp knock the basics of looking for large spikes and ignoring secondary spikes for N milliseconds afterwards may be enough for you. If it isn't you're going to spend a lot of time on this.
I used information and code from this answer (rewriting code to Javascript) in my simple demo PhoneGap Buld application, to recalculate gravity (G) to real acceleration (m/s2) with 1 second frequency.
This is actual code (important part):
function onAccelerationSuccess(acceleration)
{
var g = 9.80665;
acceleration.x = (acceleration.x * g).toFixed(2) + ' m/s\u00b2';
acceleration.y = (acceleration.y * g).toFixed(2) + ' m/s\u00b2';
acceleration.z = ((acceleration.z + 1) * g).toFixed(2) + ' m/s\u00b2';
...
}
watchID = navigator.accelerometer.watchAcceleration(onAccelerationSuccess, onAccelerationError, {frequency: 1000});
Mentioned answer and many, many sources claims, that with my phone lying on the table face-up, I should get values of (0, 0, -1) G for the x, y, and z axes respectively. Assuming Earth's natural acceleration (g = 9.80665), I should see real acceleration values of (0, 0, 9.81) m/s2 and these values should not change (as phone is resting still). Am I right?
However, actually I'm seeing, that my real values are:
X axis: -1.87, -1.50, -2.25,
Y axis: 2.26, 1.88, 1.51, 0.76,
Z axis: 101.87, 101.49, 102.25, 102.62, 103.37.
These values are constantly changing, but only between these mentioned, and not every axis gets changed value each second. Sometimes, a value for some axis remains for 2-3 seconds.
What is happening? How can a phone variate its acceleration, if it is holding still on my desk? How can any device, that is not moving in any direction have such enormous acceleration like 100 m/s2?
I have heard that accelerators on-board mobile devices are more like toy than real a measurement device and that they're producing a lot of noise or jitter to returned values. But, for God sake, this is a complete garbage, that is making use of this function completely pointless.
I tested this code on Google Nexus (first edition) phone, with Android 4.2.2. App with Phonegap 2.9.0.
EDIT: I've tested my mobile application with Ripple Emulator and I'm getting perfectly valid values:
Acceleration in the X axis is 0.00 m/s².
Acceleration in the Y axis is 0.00 m/s².
Acceleration in the Z axis is 9.81 m/s².
Is something wrong with accelerometer / compass / gyro on-board my Nexus?
There is no bug. I've read many SO questions about iOS native programming in ObjectiveC, when I was dealing with accelerometer. And I missed PhoneGap API documentation, which says, that values passed are already recalculated:
Acceleration values include the effect of gravity (9.81 m/s^2).
After removing double gravity calculation all seems to be fine.
I'm betting values +/-0.3 m/s2 for x and y axis and around 10.3-10.4 m/s2 for z axis, when phone is lying on my desk. But I assume, these are variations and mentioned noise, that is comming from fairly cheap accelerometer chip used in mobile devices.
I'm in a little bit weird situation. The situation I currently have is typically a good one - no gyro drift at all - but I have no clue why this is the case. I expected the gyroscope to drift a lot as reported everywhere. Therefore this question to find the reason why I do not see any drift.
I use a Galaxy Nexus (Android 4.0.3) and its gyroscope to do some orientation change detection in the end. In the first place, I just wanted to log the sensor readings and expected to see large drifting values as for example here. Also on other websites I read of drifting of about 1 degree per second or similar measurements.
My code to log the sensor data is very basic:
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
LogSensorEventListener listener = new LogSensorEventListener(
SensorLoggerActivity.this, Sensor.TYPE_GYROSCOPE);
sm.registerListener(listener, sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_FASTEST);
LogSensorEventListener is also a very basic implementation of the SensorEventListenerInterface:
#Override
public void onSensorChanged(SensorEvent event) {
final float dT = (event.timestamp - mTimestamp) / 1000000000.f;
if (dT < 1) { // just a fix for the first step
mRelZ += event.values[2] * dT;
list.add(mRelZ);
list2.add(event.timestamp);
}
mTimestamp = event.timestamp;
mRelZ is initially set to 0 and the two lists should keep track of time and measured value. If finished with my measurement (clicking a button) the values are written to file:
try {
for (int i = 0; i < list.size(); i++) {
long time = list2.get(i);
if (mStartTime < 0) {
mStartTime = time;
time = 0;
} else {
time = time - mStartTime;
}
float timef = time / 1000000000.0f;
sb.append(timef);
sb.append(";");
sb.append(Math.toDegrees(list.get(i)));
sb.append("\n");
}
mOutFileStream.write(sb.toString().getBytes());
mOutFileStream.flush();
mOutFileStream.close();
} catch (IOException e) {
}
Everything works fine but the only drift I can see (for example when having the device lying on a table and logging values for let's say 60 seconds) is about 0.2 degree.
If I move the device to get something like in the screenshot above no drift can be observed at all...
What I'm I doing wrong (or right?)??
Thanks for any advice!
the gyro you have might be a post-filtered one, i found this in some of the SAMSUNG I9100 devices. they use gyros produced by INVENSENSE CORP.
edit 2014/12/10 for commenting #Lourenço Castro
i believe #Lourenço Castro was right about the 'factory drift'(or zero-drift) and 'accumulated drift';
Concerning to complementary/kalman filters, on Android, the 6dof(ACC+GYRO) impl. works fine, it can remove both factory and accumulated drift of gyroscope thanks to accelerometer's calibration;
However, the 9dof impl., or adding a magnetometer sensor into consideration, filter runs into status you dont want. According to my tests, it is caused by uncalibed status of magnetometer which need guys to shake (known as drawing 'eight')the phone to remove before fusion.
9dof provides a full rotation estimation while 6dof can not handle the accumulated drift of rotating around y axis(see android's ref. for axis definition). And I dont think that 'shaking-and-drawing-number-8' before one starts an app is a good UE, so we have to go back to 6dof method and try to find a way to remove factory drift of rotation around y-axis.
(a lot of boring but amazing content about complementary/kalman filter should be here. but i guess those come here already know it.)
You can try a 360 panorama app included in Google Camera which can be downloaded from market. This app use a visual-aid(image processing based motion estimation) for calibrating the sensor before we really start to capturing, it's easy to verify this. And no use of Magnetometer, i guess.
So my advice to use sensor-fusion on Android is:
1. no magnetometer; (dont know if this is a hardware problem or can be solved by software.)
2. accelerometer+gyroscope provides smooth and stable 6dof motion estimation;
3. try to solve the drift around y-axis, mainly means that visual-method should be added to;
4. try 3 and try 3...
Maybe someone will run into this question and i hope all above might be helpful.
thanks to those posts related to this questions on StackOverflow.
i dont remember your names exactly but you all helped me a lot. :)
I was browsing for this subject and, although this is an old question, I don't believe the accepted answer is correct.
The drift that is "reported everywhere" is caused by the integration of noisy gyroscope data (as seen on the link provided by the O.P.), not by simply outputting the sensor data. You will need this integration to calculate orientation changes over time.
Longer integration periods will contain larger noise amounts which will accumulate drift fairly quickly. I believe the internal filtering of the signal which happens on Invensense hardware is used to remove drift caused by their own pre-processing of the gyroscope.
In Android's case, in API 18 (Jelly Bean MR2), a GYROSCOPE_UNCALIBRATED sensor was added, in which you can (presumably) verify this factory drift calibration. Anyway, when you try to integrate either the calibrated and uncalibrated sensor events, you will get drift, since both are pretty noisy. To reduce this issue you will have to delve into complementary or Kalman filters.
(Sorry about not posting more informational links, not enough reputation).
Hope this helps future users.
I am creating an app in android where i need to detect if the person has fall down. I know that this question has been asked and answered as to use vector mathematics in other forums but i am not getting the accurate results out of it.
Below is my code to detect the fall:
#Override
public void onSensorChanged(SensorEvent arg0) {
// TODO Auto-generated method stub
if (arg0.sensor.getType()==Sensor.TYPE_ACCELEROMETER) {
double gvt=SensorManager.STANDARD_GRAVITY;
float vals[] = arg0.values;
//int sensor=arg0.sensor.getType();
double xx=arg0.values[0];
double yy=arg0.values[1];
double zz=arg0.values[2];
double aaa=Math.round(Math.sqrt(Math.pow(xx, 2)
+Math.pow(yy, 2)
+Math.pow(zz, 2)));
if (aaa<=6.0) {
min=true;
//mintime=System.currentTimeMillis();
}
if (min==true) {
i++;
if(aaa>=13.5) {
max=true;
}
}
if (min==true && max==true) {
Toast.makeText(FallDetectionActivity.this,"FALL DETECTED!!!!!" ,Toast.LENGTH_LONG).show();
i=0;
min=false;
max=false;
}
if (i>4) {
i=0;
min=false;
max=false;
}
}
}
To explain the above code i have used the vector sum and checking if the value has reached below or equal to 6(while fall) and suddenly greater than 13.5(while landing) to confirm the fall.
Now i was been told in the forums that if the device is still the vector sum will return the value of 9.8. While fall it should be close to 0 and should go to around 20 while landing. This doesn't seem to happen in my case. Please can anybody suggest if i am going wrong anywhere?
There is a guy who developed an android app for that. Maybe you can get some information from his site: http://ww2.cs.fsu.edu/~sposaro/iFall/. He also made an article explaining how he detected the fall. It is really interesting, you should check it out!
Link for the paper: http://ww2.cs.fsu.edu/~sposaro/publications/iFall.pdf
Resuming, the fall detection is based on the resultant of the X-Y-Z acceleration. Based on this value:
When falling, the falling generally starts with a free fall period, making the resultand drop significantly below 1g.
On the impact on the ground, there is a peak in the amplitude of the resultant, with values higher than 3g.
After that, if the person could not move due to the fall, the resultant will remain close to 1G.
Following will happen if person / phone falls down:
absolute acceleration vector value goes to 0 ( with some noise of course )
there will be fair spike in absolute vector value on landing ( up to maximal value provided by accelerometer )
When phone is immobile, you have vector of modulo earth gravity pointing up
Your code is basically correct, but I would use some averaging because accelerometers used in phones are cheap crap - noisy and lacking precision
To add averaging to your signal means:- moving average. It depends on your windows size. For example. Say I have a one vector with the following numbers: 1,2,3,4,5,6. and my window size is 2. Then the moving average is to take every two numbers from your vector and average them by 2. So you would take 1+2/2, and then move one to the next twos. 2+3/2, and so on.
The values I'm getting for accel, x, y and z below are not as expected.
It seems to be acting as a Tilt sensor rather than accelerometer.
When I throw the phone in the air and catch it, the accel value doesn't change by more than about 10%. Contrast this to when I rotate the phone randomly, I get much larger variations of 50-100%!
What could explain this? I simply want to detect when the phone is in freefall, (and/or impacting something).
SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);
Sensor sensor = sm.getDefaultSensor(SensorManager.SENSOR_ACCELEROMETER);
sm.registerListener(
sel = new SensorEventListener(){
#Override public void onAccuracyChanged(Sensor arg0, int arg1) {}
#Override public void onSensorChanged(SensorEvent arg0) {
double x = se.values[0];
double y = se.values[1];
double z = se.values[2];
double accel = Math.sqrt(
Math.pow(x, 2) +
Math.pow(y, 2) +
Math.pow(z, 2));
}
},
sensor,
SensorManager.SENSOR_DELAY_NORMAL
);
(As a side question, the values for x, y and z seem much higher than they should be, with accel averaging at about 50-80, when standing still? Shouldn't it be around 9.8?)
The x, y and z values seem very sensitive to changes in the orientation of the phone, but not at all representative of acceleration. Am I missing something??
Example values with phone still, lying on back:
Accel = 85.36, x = 6.8, y = 45.25 z = 30.125
I had to replace
Sensor sensor = sm.getDefaultSensor(SensorManager.SENSOR_ACCELEROMETER);
with
Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Could be because when you throw the phone it is almost the same plane or angle and at low speed, when you turn the phone it changes its course and orientation rapidly and that gives higher values for the result. Accelerometer may be a misnomer for a multi-function device, there could be a selection parameter for the function you really want to get results from.
With the phone lying on its back you should get close to zero on the X and Y sensors and about 9.8 on the Z sensor. The 9.8 is of course due to gravity.
My first though would be that there is something wrong with the phone and would suggest trying same code on another phone.
However I notice that there is something wrong in the math but haven;t figured out what yet.
with x,y,z having the values you mention the resultant (square root of sum of squares) works out to 54.78 rather than 85.36 as you mention in your post.
I'm quite new to Java so I cannot easily spot what might be wrong and haven't had the opportunity yet to try that piece of code on my phone, but I think the math is simple enough for me to determine that the result is wrong. (or at least I hope so).
The other thing to check (assuming you figureout the math problem) is that the small change when you throw the phone in the air might simply be due to the slow response time. The accelerometer output may simply be changing too slowly so by the time the phone has landed the output wouldn't have changed that much. The response can be improved by using SENSOR_DELAY_GAME or SENSOR_DELAY_FASTEST instead of normal.
By the way, shouldn't that be arg0.values[] rather than se.values[]? Where does the se come from? The sensor values go into the argument of the onSensorChanged (arg0 in this case) so I cannot figure out how they are supposed to end up in se. (But then again there are many things in Java I still don't understand)