2014年1月21日火曜日

CoreMotionの使い方

次のアプリに着手してるんですが、CoreMotionで順調にハマっております。
マジで、最高にややこしいです。やっぱ行列苦手とか言ってられないですね。

まず、最初にハマったのが、実行速度が遅い事。
全然数字あってねー、とか思ってたらガンガン処理落ちしてました。

例えば、こんな感じでCoreMotionを呼ぶのですが、
@property (nonatomic, retain) CMMotionManager *manager;
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.manager = [[CMMotionManager alloc] init];
    
    //加速度
    [self startCMGyroData:50];
}

- (void)startCMGyroData:(int)frequency
{
    // ジャイロスコープの有無を確認
    if (self.manager.gyroAvailable) {
        // 更新間隔の指定
        self.manager.gyroUpdateInterval = 1 / frequency;  // 秒
        // ハンドラ
        CMGyroHandler handler = ^(CMGyroData *data, NSError *error) {
            double gyro_x = data.rotationRate.x;
            double gyro_y = data.rotationRate.y;
            double gyro_z = data.rotationRate.z;
            //testGyroRectangle.position= CGPointMake(160+160*gyro_x,240+240*gyro_y);
            testGyroRectangle.frame = CGRectMake(160+160*gyro_x, 240+240*gyro_y, 30*gyro_z, 30*gyro_z);
        };
        // センサーの利用開始
        [self.manager startGyroUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:handler];
    }
}


ここで幾つか落とし穴。
  • Blockの所の引数(ここではCMGyroData *data)は使わない。後述のNSLockを使う場合の障壁になる。
  • 状況に依るけど、空間座標絡みは、CMRotationMatrixを使うべき。ややこしいけど、理解すれば非常にスマートに処理出来る。
  • Block内で、データ取得の際は、NSLockを使う。そーすると、他の処理に引きずられる事無く、割とリアルタイムに数字が取れる。GDCでも良いかも。
  • Blockの中のNSLockで記述される処理は極力減らす。(CALayerの更新とかは1つ以上だとスゲー重くなる。)優先度の低い処理は別途timerなどで処理する。
実際の所、CoreMotionの処理は中々の高負荷なので、サンプルコードまんまだと処理が重い場合が結構ある。処理落ちすると、データの追随性が極端に悪くなって、傾けたのに反映されるのが5-10秒後で全然使い物にならない事がままある。
なぜそんなに重いのかというと、カメラと重ねてたから。描画はCALayerなので比較的軽いハズなのだけれども、AVCapture自体が結構重いので、CoreMotionを重ねると激重となってた次第。別スレッドに分けるなどすれば非常に改善されるので、使用する場合はスレッド処理必須かと思う。

また、CoreMotionで取得出来るデータは、幾つか種類があるけど、加速度データは非常に扱いが難しい。
これはジャイロセンサーの特性に依るので、仕方ない。でも、極座標データに変換してくれる関数が入ってる。
取れる値は、CMRotationMatrix、CMQuaternion、CMAttitudeとあるが、それぞれ特性があるので用途によって使い分けるべきところ。
でもオススメは、CMRotationMatrix。情報が少なく概念を理解するのは骨が折れるが、概念を理解すれば、やりたい事が一発でバチっと決まる。計算量が少なく済むので、当然処理も早い。CMAttitudeはオイラー角なので、変換の計算がどうしても多くなる可能性がある。
RotationMatrixはこんな感じで呼ぶ。
        CMDeviceMotionHandler handler = ^(CMDeviceMotion *motion, NSError *error) {
            
            //引数のmotionは使わない。
            CMDeviceMotion* deviceMotion = self.manager.deviceMotion;
            
            CMRotationMatrix rotmatrix = deviceMotion.attitude.rotationMatrix;

    /*各種処理*/
}
こんな感じで、rotmatrix.m11とかそんな感じの3x3の行列が渡される。 最初これ、中身の意味が解らなくて、結構悩んでたんだけど、恐らく下記の様な感じ。

    rotmatrix.m11  //デバイスのx方向を指示するベクトルを表す絶対x軸方向の座標点  多分-1〜1の範囲。
    rotmatrix.m12  //デバイスのx方向を指示するベクトルを表す絶対y軸方向の座標点 
    rotmatrix.m13  //デバイスのx方向を指示するベクトルを表す絶対z軸方向の座標点 
   /* 以下略 */

中身が解ってしまえば、こっちのもの。後は回転行列に放り込むなりなんなりで、結構自在に操作出来る感じです。
コツは鉛直方向のベクトルは3次元位置補足済みであれば常に(0,0,-1)を示すので、デバイスの表示面がxy平面だと言う事を意識すると色々上手く運びます。
ちなみに、補足までに時間が掛かり、タマに意図せず狂うので、補正が必要です。
こーいうの結構好きなんで楽しかったです。

これを使ったアプリはリリースし次第またご報告します。

0 件のコメント:

コメントを投稿