watchOS 3 でモーション認識

##これまでのあらすじ

[WWDC.next](http://connpass.com/event/32130/) にて。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/8572ba29-6391-72e4-cd51-dc4fcdbeb45f.jpeg" width="500">

[watchOS 3 で「アレ」は本当にできるようになったのか？ - Qiita](http://qiita.com/shu223/items/31b1b453ee1740d3eaf5)

---

##これまでのあらすじ

CMDeviceMotionでジャイロセンサの値、およびそれを利用した姿勢情報を取れるようになった！<br>
→ ⌚️**を利用したモーション認識が可能に！**

---

##今日お話しすること

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

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="200" align="right">

---

##<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/a4baa589-3390-2de6-163a-e9caae354ca0.png" width="200">

---

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

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

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

---

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


---

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

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

---


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

---

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="200" align="center">

---

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="260" align="center">

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

---

##CMDeviceMotion

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

---

##rotationRate: 角速度

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

---

```swift:CMGyro
struct CMRotationRate {
    var x: Double
    var y: Double
    var z: Double
}
```

- 各軸の単位はラジアン/秒

---

##gravity: 重力加速度

```swift: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 }
```

---

```swift:CMAcclerometer
struct CMAcceleration {
    var x: Double
    var y: Double
    var z: Double
}
```
---

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="200" align="center">

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

---

##ベクトルの内積

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

<img width="460" alt="Screen Shot 2016-08-27 at 22.43.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/3180/1c268772-8371-a599-af0b-9d9e6dd17d23.png">

（出展：[【数学】「内積」の意味をグラフィカルに理解すると色々見えてくる その１]()）

---

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="200" align="center">

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

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

---

##内積の計算方法

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

```math

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

---

##実装

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

```swift
let gravity = deviceMotion.gravity
let rotationRate = deviceMotion.rotationRate

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

---

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/ab4d26aa-911e-2850-9798-8c9f61fab49b.jpeg" width="260" align="center">

これが得られた

---

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

---

- 50サンプル（1秒）分を貯める

```swift
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](https://qiita-image-store.s3.amazonaws.com/0/3180/f4215720-a71b-3fee-eda8-ee9531a912fd.png)

出展： [回転 (数学) - Wikipedia](https://ja.wikipedia.org/wiki/%E5%9B%9E%E8%BB%A2_(%E6%95%B0%E5%AD%A6)#.E3.82.AA.E3.82.A4.E3.83.A9.E3.83.BC.E5.9B.9E.E8.BB.A2)

---

#全然覚えられない・・・

---

##最高の解説

![B6Hk08hCAAE-Vld.png](https://qiita-image-store.s3.amazonaws.com/0/3180/aad9c9a9-4566-f767-8ad0-8236d85986ac.png)

---

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

---

##これまでのおさらい

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

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

---

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

---

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

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

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


---

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

---

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

```swift:WKInterfaceDevice
var wristLocation: WKInterfaceDeviceWristLocation { get }
```

```swift:WKInterfaceDevice
enum WKInterfaceDeviceWristLocation : Int {
    case left
    case right
}
```

- watchOS 3 で追加された新API
- 自動検出ではなく、ユーザーが手動設定した値


---

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

---

##（デモ）

---

![CrGxA9UUIAABylu.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/a10de0ef-95bd-a163-dcba-050418f4e9c6.jpeg)

<blockquote class="twitter-tweet" data-cards="hidden" data-lang="ja"><p lang="ja" dir="ltr">めっちゃいい顔してるw<a href="https://twitter.com/hashtag/iosdcrc?src=hash">#iosdcrc</a> <a href="https://t.co/XUguXfIlp2">pic.twitter.com/XUguXfIlp2</a></p>&mdash; Kohei Tabata (@nerd0geek1) <a href="https://twitter.com/nerd0geek1/status/770591651114778624">2016年8月30日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

---

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">＼fore hand／ オオー ＼back hand／ オオー <a href="https://twitter.com/hashtag/iOSDCRC?src=hash">#iOSDCRC</a></p>&mdash; 長谷川智希 / とむぞう (@tomzoh) <a href="https://twitter.com/tomzoh/status/770591471279800320">2016年8月30日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

---

##まとめ

話したこと

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

---

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

---

本記事は、2016.8.30に開催された [iOSDC Reject Conference days1 - connpass](http://iosdc-reject-conference.connpass.com/event/37017/) での発表資料です。