测量倾斜角度与CMMotionManager



假设你正拿着iphone/ipad,屏幕正对着你。你把设备倾斜到一边,让屏幕对着你。如何使用CMMotionManager测量静态倾斜角度?这似乎是一个简单的问题,应该有一个简单的答案,但我找不到任何方法,不消失在四元数和旋转矩阵。

谁能给我一个工作的例子?

gravity:

self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
// UIDevice *device = [UIDevice currentDevice];
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
                                                        toQueue:self.deviceQueue
                                                    withHandler:^(CMDeviceMotion *motion, NSError *error)
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        CGFloat x = motion.gravity.x;
        CGFloat y = motion.gravity.y;
        CGFloat z = motion.gravity.z;
    }];
}];

在这个参考系(CMAttitudeReferenceFrameXArbitraryZVertical)中,如果z接近零,你将它放在与地面垂直的平面上(例如,如果你将它放在墙上),并且当你在该平面上旋转它时,xy的值会发生变化。垂直表示x接近于零,y接近于-1。


看这篇文章,我注意到,如果你想把这个向量转换成角度,你可以使用以下算法。

如果你想计算设备从垂直方向旋转了多少度(正数为顺时针,负数为逆时针),你可以这样计算:

// how much is it rotated around the z axis
CGFloat angle = atan2(y, x) + M_PI_2;           // in radians
CGFloat angleDegrees = angle * 180.0f / M_PI;   // in degrees

你可以用这个来计算通过Quartz 2D transform属性旋转视图的程度:

self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);

(就我个人而言,我在startDeviceMotionUpdates方法中更新了旋转角度,并在CADisplayLink中更新了这个transform,这将屏幕更新与角度更新解耦。)

你可以看到你向后/向前倾斜了多远:

// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltForwardBackward = acosf(z/r) * 180.0f / M_PI - 90.0f;

这是一个迟来的答案,但你可以在github和博客文章中找到一个工作示例。

总结一下上面提到的文章,你可以使用四元数来避免你在垂直手持iPhone时可能面临的万向锁问题。

这是计算倾斜(或偏航)的编码部分:

CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...

你甚至可以添加一个简单的卡尔曼滤波器来缓解偏航:

CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
    self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1;   // process noise
static float r = 0.1;   // sensor noise
static float p = 0.1;   // estimated error
static float k = 0.5;   // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...

下面是一个例子,当你倾斜设备时,旋转UIView self.horizon使其与地平线保持水平。

- (void)startDeviceMotionUpdates 
{
    CMMotionManager* coreMotionManager = [[CMMotionManager alloc] init];
    NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init]
    CGFloat updateInterval = 1/60.0;
    CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXArbitraryCorrectedZVertical;
    [coreMotionManager setDeviceMotionUpdateInterval:updateInterval];
    [coreMotionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
                            toQueue:motionQueue
                            withHandler:
     ^(CMDeviceMotion* motion, NSError* error){
         CGFloat angle =  atan2( motion.gravity.x, motion.gravity.y );
         CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
         self.horizon.transform = transform;
     }];
}

这有点过于简化了——你应该确保在你的应用程序中只有一个CMMotionManager实例,所以你想要预先初始化它,并通过一个属性来访问它。

由于iOS8 CoreMotion也返回给你一个CMAttitude对象,它包含pitch, rollyaw属性,以及四元数。使用这个方法意味着你不需要手动计算将加速度转换为方向。

相关内容

  • 没有找到相关文章

最新更新