I'm a coremotion beginner.
I need to detect iPhone movement on a flat surface like table - so far, I made it to detect its sideways movement by accessing the yaw of the gyro, but I can't think of a way to detect the up/down changes. I tried using the accelerometer, but it detects more of a device tilt than movement. Also, there is a counterforce when the movement stops.
Do you have any idea to do it so that it would be possible to have the movement data with fair precision? I need it for something like air-hockey game.
In order to determine horizontal position from the deviceMotion is not really doable. The horizontal sensors to use would be the accelerometers, but they measure changes in speed. Therefore, to find position from that, you would need to integrate the sensor data twice. Once to get speed, then a second time to get position.
That means that even the smallest inaccuracy in the acceleration data will make an error in the resulting speed, meaning that your program thinks the phone is moving at constant speed, while in reality it is standing still.
If you don't need to be accurate (for example, when you do not need it to come back to the same position when you move it back and forth), then you might get acceptable results if you force the speed to zero if the accelerometer data is quiet and close to zero.
If you are really set on trying this, then a Kalman filter is probably your best bet to derive speed and position from the accelerometer data.
That means that your code will in the end look something like the following, where acceleration
, speed
, and position
are three variables you keep, and acc
is a data sample from the accelerometer.
// correction step:
double delta = (acc - acceleration);
acceleration += FACTOR1 * delta;
speed += FACTOR2 * delta;
position += FACTOR3 * delta;
// prediction step:
position = position + speed * DT + 0.5 * acceleration * DT * DT;
speed = speed + acceleration * DT;
where DT
is the time between data samples, and suitable values for FACTOR1..3
you will need to find out.
By the way, Yaw does not give you horizontal motion, but rotation.
EDIT: Nowadays, you may get good results with ARKit's Motion Tracking, which combines accelerometer input with image analysis from the camera. It does all the hard work for you.
So I'm pretty new to CoreMotion as well. I'll give my best shot at explaining why what you ask can't be done, at least not accurately.
Based on the Apple Docs I read, there are 3 motion handlers for iOS:
(I believe the newest devices have an altimeter?)
Based on my understanding of physics, I gathered that accelerometer would be most useful for your scenario, as the gyroscope and magnetometer are more focused on rotation. I did some research and found this book. Chapter 9.5 in particular. From the book:
While an accelerometer provides measurement of forces in the x-, y-, and z-axes it cannot measure rotations. On the other hand, a gyroscope is a rate-of-change device; as the phone rotates around an axis, it allows you to measure the change in such rotations.
Following their code under figure 9-5 for implementing a simple app to watch the acceleration:
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
@interface ViewController : UIViewController {
CMMotionManager *motionManager;
NSOperationQueue *queue;
}
@property (weak, nonatomic) IBOutlet UILabel *xLabel;
@property (weak, nonatomic) IBOutlet UILabel *yLabel;
@property (weak, nonatomic) IBOutlet UILabel *zLabel;
@property (weak, nonatomic) IBOutlet UIProgressView *xBar;
@property (weak, nonatomic) IBOutlet UIProgressView *yBar;
@property (weak, nonatomic) IBOutlet UIProgressView *zBar;
@end
Then in the .m viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
motionManager = [[CMMotionManager alloc] init];
motionManager.accelerometerUpdateInterval = 1.0/10.0; // Update at 10Hz
if (motionManager.accelerometerAvailable) {
NSLog(@"Accelerometer avaliable");
queue = [NSOperationQueue currentQueue];
[motionManager startAccelerometerUpdatesToQueue:queue
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
CMAcceleration acceleration = accelerometerData.acceleration;
xLabel.text = [NSString stringWithFormat:@"%f", acceleration.x];
xBar.progress = ABS(acceleration.x);
yLabel.text = [NSString stringWithFormat:@"%f", acceleration.y];
yBar.progress = ABS(acceleration.y);
zLabel.text = [NSString stringWithFormat:@"%f", acceleration.z];
zBar.progress = ABS(acceleration.z);
}];
}
}
After linking everything up and running it on the phone, I see that the accelerometer is measuring the changes of my phone accelerating and decelerating as I move it on a flat table. Now we have the acceleration, but to get the change in position (which is what you asked) from that requires double integration (ie fun calculus). While doable, I looked up what the knowledgeable folks over at arduino have to say about that here and boy is it grim news. (They aren't discussing the iPhone accelerometer but I extrapolated that this info applies to this question as well).
The relevant parts I've posted here:
Getting position from acceleration is a double integration, so the error stackup is really evil. Getting good position data with a bolted down IMU (what you are trying to do) is a genuinely hard problem; the kind of accelerometers needed to do it are very expensive.
and
What you are talking about is double integration, which is just too inaccurate with a single accelerometer on this hardware. Even single integration will have a growing error per unit time simply because of the lack of accuracy in this setup.
All of this research leading up to my conclusion that it's not feasible on an iPhone to do what you ask. Though like I said, I'm a beginner at this as well! Sorry that this isn't the answer you wanted to hear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With