Измерение угла наклона с помощью CMMotionManager

Предположим, что вы держите iphone/ipad вертикально перед собой с экраном, обращенным к вам, в портретной ориентации. Вы наклоняете устройство в сторону, удерживая экран перед собой. Как вы измеряете этот статический угол наклона с помощью CMMotionManager? Кажется, простой вопрос, который должен иметь простой ответ, но я не могу найти какой-либо метод, который не исчезнет в кватернионы и матрицы вращения.

Может ли кто-нибудь указать мне на обработанный пример?

Ответ 1

Посмотрите 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 близок к нулю, вы держите его на плоскости, перпендикулярной земле (например, как будто вы держите ее у стены), и когда вы вращаете ее на этой плоскости изменяются значения x и y. Вертикаль - это где x близок к нулю, а y близок к -1.


Глядя на это post, я замечаю, что если вы хотите преобразовать этот вектор в углы, вы можете использовать следующие алгоритмы.

Если вы хотите рассчитать, сколько градусов от вертикали устройство повернуто (где положительный по часовой стрелке, отрицательный против часовой стрелки), вы можете рассчитать это как:

// 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 и обновляю этот transform в CADisplayLink, который отделяет обновления экрана от обновлений угла.)

Вы можете видеть, как далеко вы наклонили его назад/вперед с помощью:

// 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;

Ответ 2

Это своего рода поздний ответ, но вы можете найти рабочий пример для github и статьи блога, который приходит с ним.

Чтобы подвести итог упомянутой выше статье, вы можете использовать кватернионы, чтобы избежать проблемы с блокировкой gimbal , с которой вы, вероятно, сталкиваетесь при вертикальном удерживании 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
// ...

Ответ 3

Вот пример, который вращает 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, поэтому вы хотите предварительно инициализировать это и получить к нему доступ через свойство.

Ответ 4

Так как iOS8 CoreMotion также возвращает вам объект CMAttitude, который содержит свойства pitch, roll и yaw, а также кватернион. Использование этого будет означать, что вам не нужно выполнять ручную математику, чтобы преобразовать ускорение в ориентацию.