watchOS 3 でモーション認識

  • 54
    Like
  • 2
    Comment
More than 1 year has passed since last update.

これまでのあらすじ

WWDC.next にて。

watchOS 3 で「アレ」は本当にできるようになったのか? - Qiita


これまでのあらすじ

CMDeviceMotionでジャイロセンサの値、およびそれを利用した姿勢情報を取れるようになった!

→ ⌚️を利用したモーション認識が可能に!


今日お話しすること

テニスのスウィングを検出して「フォアハンド」か「バックハンド」かを識別するアルゴリズムを紐解きつつ、モーション検出に必要な基礎知識について解説します。



今日お話しすることは、Apple Watchに限らず

  • iPhone
  • Android
  • Android Wear
  • バンド型ウェアラブルデバイス

等々、加速度センサやジャイロセンサの値を取り扱えるデバイス一般に共通する話です。


watchOS 3 に興味がなくてもぜひ聞いてください!


テニスのスウィング検出のアルゴリズム

  1. 角速度と重力加速度のベクトルの内積を計算する
  2. 1の移動平均と、ピーク値を求める
  3. 2で求めた値を閾値処理する
  4. 2の値の符号と、手首の方向からフォアハンドかバックハンドかを判定する

1. 角速度と重力加速度のベクトルの内積を計算する



欲しい情報: 重力方向の軸に対する角速度


CMDeviceMotion

  • watchOS 3 から取得できるようになった
  • デバイスのモーションに関するいろんな情報が入っている

rotationRate: 角速度

CMDeviceMotion
/*
 *  rotationRate
 *  
 *  Discussion:
 *    Returns the rotation rate of the device for devices with a gyro.
 *
 */
open var rotationRate: CMRotationRate { get }

CMGyro
struct CMRotationRate {
    var x: Double
    var y: Double
    var z: Double
}
  • 各軸の単位はラジアン/秒

gravity: 重力加速度

CMDeviceMotion
/*
 *  gravity
 *  
 *  Discussion:
 *    Returns the gravity vector expressed in the device's reference frame. Note
 *      that the total acceleration of the device is equal to gravity plus
 *      userAcceleration.
 *
 */
open var gravity: CMAcceleration { get }

CMAcclerometer
struct CMAcceleration {
    var x: Double
    var y: Double
    var z: Double
}

欲しい情報: 重力方向の軸に対する角速度


ベクトルの内積

{\bf a}\cdot{\bf b} = \|{\bf a}\|\|{\bf b}\|\ \cos\theta

Screen Shot 2016-08-27 at 22.43.47.png

(出展:【数学】「内積」の意味をグラフィカルに理解すると色々見えてくる その1


欲しい情報: 重力方向の軸に対する角速度

→ ⌚️の角速度ベクトルと重力ベクトルの内積で得られる!


内積の計算方法

${\bf a}=(a_1,\cdots,a_n), {\bf b}=(b_1,\cdots,b_n)$


{\bf a} \cdot {\bf b} = a_1b_1+\cdots+a_nb_n = \sum_{i=1}^n a_ib_i

実装

⌚️の角速度と、重力加速度の内積を計算

let gravity = deviceMotion.gravity
let rotationRate = deviceMotion.rotationRate

let rateAlongGravity = rotationRate.x * gravity.x
                     + rotationRate.y * gravity.y
                     + rotationRate.z * gravity.z

これが得られた


2. 1の移動平均と、ピーク値を求める


  • 50サンプル(1秒)分を貯める
rateAlongGravityBuffer.addSample(rateAlongGravity)

  • 50サンプル中での平均値を計算
let accumulatedYawRot = rateAlongGravityBuffer.sum() * sampleInterval
  • 50サンプル中で絶対値が最も大きいものを抽出
let peakRate = accumulatedYawRot > 0 ?
    rateAlongGravityBuffer.max() : rateAlongGravityBuffer.min()

accumulatedYawRot という変数名について

  • accumulate: 蓄積する
  • Rot: Rotation

→ Yaw(ヨー) って?


オイラー角

  • 3次元空間での姿勢を表す角度
  • ロール・ピッチ・ヨーの3つの回転角で表現される

ロール・ピッチ・ヨー

440px-Flight_dynamics_with_text.png

出展: 回転 (数学) - Wikipedia


全然覚えられない・・・


最高の解説

B6Hk08hCAAE-Vld.png


スウィング検出のアルゴリズムに戻ります


これまでのおさらい

  1. 角速度と重力加速度のベクトルの内積を計算する
  2. 1の移動平均と、ピーク値を求める

→ 求めた移動平均が accumulatedYawRot、ピーク値がpeakRate


3. 2で求めた値を閾値処理する


if (accumulatedYawRot < -yawThreshold && peakRate < -rateThreshold) {
    // 4へ
} else if (accumulatedYawRot > yawThreshold && peakRate > rateThreshold) {
    // 4へ
}

平均値とピーク両方について閾値処理する理由(推測)

  • ピーク値だけだと、ホンの一瞬速く動いただけで反応してしまう
  • 平均値だけだと、その間一定以上の速さで動かし続けないといけない

4. 2の値の符号と、手首の方向からフォアハンドかバックハンドかを判定する


⌚️をどちらの手首に付けているか?

WKInterfaceDevice
var wristLocation: WKInterfaceDeviceWristLocation { get }
WKInterfaceDevice
enum WKInterfaceDeviceWristLocation : Int {
    case left
    case right
}
  • watchOS 3 で追加された新API
  • 自動検出ではなく、ユーザーが手動設定した値

if (accumulatedYawRot < -yawThreshold && peakRate < -rateThreshold) {
    // 反時計回り
    if (wristLocationIsLeft) {
        // バックハンド
    } else {
        // フォアハンド
    }
} else if (accumulatedYawRot > yawThreshold && peakRate > rateThreshold) {
    // 時計回り
    if (wristLocationIsLeft) {
        // フォアハンド
    } else {
        // バックハンド
    }
}

(デモ)


CrGxA9UUIAABylu.jpg



まとめ

話したこと

  • CMDeviceMotionで取れる値いろいろ
    • 角速度
    • 重力加速度
  • ベクトルの内積
  • オイラー角
  • ・・・を利用したモーション検出アルゴリズム

ご清聴ありがとうございました!


本記事は、2016.8.30に開催された iOSDC Reject Conference days1 - connpass での発表資料です。