自然非言語処理第１１日目：運動を用いた非言語処理(知覚・認知編)

![hi-2292499_1280.jpg](https://qiita-image-store.s3.amazonaws.com/0/78052/6f0df063-4adc-8251-8642-f36f1e1b6a54.jpeg)

# 運動によるコミュケーション
*みなさん、筋肉は好きですか？*

私は筋肉が大好きです。
特に背筋が好きで、ラットプルダウンは欠かせません。
IT系ならば好きな筋肉は必ず言えるはずです。
あなたは胸筋が好きですか？
それとも腹筋？
上僧帽筋？

**はい**

我々は、何かしらを相手に伝えたい時、多くは運動を通じて相手に思いを表現します。例えば、表情。表情筋を駆使して笑顔を作り、相手に好意を伝えます。他にも、横隔膜と声帯、表情筋を使い、声で相手を威嚇します。

最もわかりやすい運動でのコミュニケーションの例は、ジェスチャーです。ジェスチャーには、指を二本立て数を伝えたり、言葉で伝わりにくい体積や長さを二本の手で表したりなどたくさんあります。ダンスも運動によるコミュニケーションの例としてあげられます。

このジェスチャーを用いた遊びの代表例として、じゃんけんがあります。じゃんけんでは三すくみの概念を学ぶにはもってこいの教材です。

そこで今回はじゃんけんで機械とコミュニケーションを取って見ましょう。
今回は知覚・認知編ということでまずじゃんけんの手をセンシングすることから始めましょう。

その前に例によって、運動による非言語処理を学びましょう。

# 運動による非言語処理
運動による非言語処理は現在発展途上にあります。近年のセンシング技術の向上、ディープラーニングの発展で、運動の把握が劇的に向上しています。また、ロボティクス技術の向上、また、CGや画像処理の技術の向上から、運動の記号表現能力も向上しました。
ここでは運動を把握するための技術を説明します。

## ハードウェア
運動を把握するためのハードウェアは筋電センサとモーションキャプチャがあります。

筋電センサ(正しくは表面筋電図用センサ、sEMG)とは筋肉の力の入れ具合を測定するセンサです。皮膚の表面に電極を貼るだけで筋電位を測定できるので、センサのものはピンからキリまであります。手作りすると1000円ぐらいで作れます。最高級なのはDelsysのものです。高級車が買えるくらいの値段だと思いましょう。

モーションキャプチャは人間の動きを骨の動きぐらいにまで抽象化して3次元で測定するセンサです。モーションキャプチャーには大きく光学式と慣性式、機械式があります。光学式モーションキャプチャでは人の動きをマーカーなどの特徴となる点だけカメラで撮影し、その点の動きから骨の動きを復元して推定するタイプのセンサです。光学式はマーカータイプとマーカレスタイプの2種類あります。マーカータイプで測定するにはピチピチの黒タイツを体に身につけ、体に蛍光マーカーをマジックテープでバリバリつける必要があります。一方でマーカレスタイプのものは着の身着のままで使えます。マーカレスタイプは最近出てきた技術でKinect などのデプスセンサを使ったり、ディープラーニングを使い、ただのカラーカメラから推定できるようになりました。ただし、精度や速度はマーカータイプの方が圧倒的に上です。一方、慣性式では、ピチピチスーツに慣性センサIMUを入れて、各骨の加速度やジャイロ、地磁気を使い、人間の動きを推定します。光学式のマーカータイプよりは装着がかんたんですが、測定されている人が静止しているとちょっとずつ位置がずれていくドリフトという現象が置きます。これは加速度と位置の関係が２階の微積分になっているため、この差を埋めようとすると誤差が大きくなるからだと推察されます。機械式モーションキャプチャとは、ロータリーエンコーダーなどを使い関節角を直接測定する手法です。現在ではデメリットが多すぎて使うことはほとんどありません。ただし、手の動きを取るために使うことがあります。このタイプのモーションキャプチャをデータグローブと言います。最高級なのはVicon社の光学式モーションキャプチャです。家が買える値段だと思いましょう。

![crunches-500x286.png](https://qiita-image-store.s3.amazonaws.com/0/78052/b322d032-3be3-8a4b-ac53-dbcaca5c3e6e.png)
![Xsens-Motion-Capture-Sport-500x281.png](https://qiita-image-store.s3.amazonaws.com/0/78052/ea3459ee-8d02-23dd-277f-66ce6238e735.png)
図 慣性式モーションキャプチャーの例 Xsens MVN（[1]より引用）

## ソフトウェア
やはりMatlabかRでしょうか。
EMGを解析するのに適しています。
また、モーションキャプチャで得られたデータを可視化するためにCGソフトを使うことがあります。
大体の場合、ハードウェアのおまけについてくるソフトが高性能なので、それを使えばだいたい事足りるような気もします。

# 実装:LeapMotionによるじゃんけんの認識
LeapMotionとは手に特化した光学式のマーカレスモーションキャプチャーです。
ヘッドマウントディスプレイをつけると、自分の手が見えないので、自分の手をVR空間に飛ばすためにLEAPが使われる傾向にあります。
SDKも充実しており、何より安いため、使い勝手が良いです。
ただし、あくまでモーションキャプチャーなので、運動の知覚しかしてくれません。認識処理はこちら側で作る必要があります。

![DQxk6APVoAAyKFf.jpg](https://qiita-image-store.s3.amazonaws.com/0/78052/35036561-093b-0570-4ed1-e7175d7dc1bb.jpeg)

図 LeapMotionの外箱

# LeapMotionを使った認識アルゴリズム
とてもかんたんなルールに基づくでじゃんけんの手を判定します。
じゃんけんのときの指先の方向を考えると、
親指以外相手の方向を向くか自分の方向を向くかのいずれかになります。
そこで、指先の方向を使ってじゃんけんの手を推定します。
具体的には指先の方向のうち、z方向の正負を見ればいいのです。
ルールは表1のとおりです。

表1　じゃんけんの手の判定ルール

| \ | 親指 | 人差し指 | 中指 | 薬指 | 小指 |
|:-:|:-:|:-:|:-:|:-:|:-:|
| グー  | － |  ＋ |  ＋ | ＋ | ＋ |
| チョキ | － | － |  － | ＋ | ＋ |
| パー  | － |  －  | －  | －  | －  |

# 材料
開発用PC(Win) 1
Leapmotion 1

# ソースコード

```python:Janken.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, sys, inspect
src_dir = os.path.dirname(inspect.getfile(inspect.currentframe()))
arch_dir = 'lib/x64' if sys.maxsize > 2**32 else 'lib/x86'
sys.path.insert(0, os.path.abspath(os.path.join(src_dir, arch_dir)))

import Leap
import cv2


class SampleListener(Leap.Listener):
    images={}
    images["choki"]=cv2.imread("img/janken_choki.png")
    images["gu"]=cv2.imread("img/janken_gu.png")
    images["pa"]=cv2.imread("img/janken_pa.png")

    def on_init(self, controller):
        print("Initialized")

    def on_connect(self, controller):
        print("Connected")

    def on_disconnect(self, controller):
        # Note: not dispatched when running in a debugger.
        print("Disconnected")

    def on_exit(self, controller):
        print("Exited")

    def on_frame(self, controller):
        # Get the most recent frame and report some basic information
        frame = controller.frame()

        # Get hands
        for hand in frame.hands:
            isPointToFront=[]
            for finger in hand.fingers:
                if(finger.direction[2]<0.0):
                    isPointToFront.append(True)
                else:
                    isPointToFront.append(False)

            if(isPointToFront[0] and isPointToFront[1] and isPointToFront[2] and
                   not isPointToFront[3] and not isPointToFront[4]):
                cv2.imshow("Your Hand", self.images["choki"])

            if(isPointToFront[0] and not isPointToFront[1] and not isPointToFront[2] and
                   not isPointToFront[3] and not isPointToFront[4]):
                cv2.imshow("Your Hand", self.images["gu"])

            if(isPointToFront[0] and isPointToFront[1] and isPointToFront[2] and
                   isPointToFront[3] and  isPointToFront[4]):
                cv2.imshow("Your Hand", self.images["pa"])

            cv2.waitKey(32)

        if not frame.hands.is_empty:
            print("")

def main():
    # Create a sample listener and controller
    listener = SampleListener()
    controller = Leap.Controller()

    # Have the sample listener receive events from the controller
    controller.add_listener(listener)

    # Keep this process running until Enter is pressed
    print("Press Enter to quit...")
    try:
        sys.stdin.readline()
    except KeyboardInterrupt:
        pass
    finally:
        # Remove the sample listener when done
        controller.remove_listener(listener)


if __name__ == "__main__":
    main()

```
# 実験結果
動画は以下のとおりです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">本日のデモ、Leapによるじゃんけん認識です。 <a href="https://t.co/4nDCmOirbj">pic.twitter.com/4nDCmOirbj</a></p>&mdash; あるふ (@alfredplpl) <a href="https://twitter.com/alfredplpl/status/940234384359043074?ref_src=twsrc%5Etfw">2017年12月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 感想
すごく単純なルールなのに、
結構すいすい動くなと思いました。
いや、むしろ単純すぎるからスイスイ動くのか。

# まとめ
今回は運動のうち、運動の把握について説明しました。
次回は運動の本番である行動のメカニズムやその非言語処理などを説明していきます。

# 参考文献
[1] https://www.xsens.com/products/xsens-mvn-analyze/