python+keras+LeapMotionで手の形の判別をしてみた。

#LeapMotionとは
LeapMotionとは何か。
このサイトにたどり着いている時点である程度は理解しているので細かい説明はいらないと思いますが、ようするに高性能なハンドトラッキングツールの事です。
大学の卒業研究でLeapMotionをつかうことになり、その際にLeapMotionの性能評価と使い方を軽く練習するためにハンドトラッキングデータからじゃんけんのグー、チョキ、パーの３クラス分類についてやってみました。
#実行環境
-Window10 64bit
-Anaconda python3.5

ただしpythonでLeapMotionを使うにはpython2である必要があるので仮想環境が必要になる
python3でLeapMotionを使う方法はあるらしいですがここでは割愛

#LeapMotionのインストール
LeapMotionをPCで使うにはLeapMotion本体とアプリをインストールする必要があります。現時点での最新のインストーラは[こちら](https://developer.leapmotion.com/get-started/)からダウンロードできます。
現時点での最新はv4です
Pythonから扱うためにはLeap.pyなどのファイルが必要ですがダウンロードしたv4にはそれらのファイルが含まれていません。
なので含まれていないデータは[LeapMotionの旧？公式サイト](https://developer-archive.leapmotion.com/get-started)からv3またはv2のフォルダをダウンロードしてその中にあるPythonファイルを使用します。
v4でセットアップしてもv3の中のPythonファイルは使用することができます。

#LeapMotionを使用するためのファイル構成
-Leap.py(ダウンロードしたフォルダのどこかにある)
-LeapPython.pyd(ダウンロードしたフォルダのどこかにある)
-Leap.dll(ダウンロードしたフォルダのどこかにある)
-leap_learn.py (手のデータを保存)
-leapmotion_pd.py (手の形状の識別)


#手の形のデータの取得
学習させるにはデータが必要なので手のデータを取ります。
ダウンロードしたフォルダの中にSample.pyというファイルがあるのでそれを参考にします。

```python:Sample.py
class SampleListener(Leap.Listener):
    finger_names = ['Thumb', 'Index', 'Middle', 'Ring', 'Pinky']
    bone_names = ['Metacarpal', 'Proximal', 'Intermediate', 'Distal']

    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()

        print "Frame id: %d, timestamp: %d, hands: %d, fingers: %d" % (
              frame.id, frame.timestamp, len(frame.hands), len(frame.fingers))

        # Get hands
        for hand in frame.hands:

            handType = "Left hand" if hand.is_left else "Right hand"

(以下略)
```

このようなファイルなっていると思います。このプログラムを実行すると

```
Frame id: 67333, timestamp: 1719513911597, hands: 1, fingers: 5
  Left hand, id 27, position: (-49.3397, 129.537, 213.691)
  pitch: 77.795364 degrees, roll: 45.088098 degrees, yaw: 79.529863 degrees
  Arm direction: (0.841971, 0.531876, -0.0905182), wrist position: (-105.837, 83.1434, 214.963), elbow position: (-324.197, -54.7952, 238.438)
    Thumb finger, id: 270, length: 49.184170mm, width: 19.963984mm
      Bone: Metacarpal, start: (-89.5553, 99.9879, 241.48), end: (-89.5553, 99.9879, 241.48), direction: (0, 0, 0)
      Bone: Proximal, start: (-89.5553, 99.9879, 241.48), end: (-48.4666, 118.026, 255.558), direction: (-0.873663, -0.383544, -0.299346)
      Bone: Intermediate, start: (-48.4666, 118.026, 255.558), end: (-16.6109, 122.143, 257.415), direction: (-0.990099, -0.127954, -0.0577231)
      Bone: Distal, start: (-16.6109, 122.143, 257.415), end: (0.297749, 121.403, 255.716), direction: (-0.994045, 0.0435127, 0.0999035)
    Index finger, id: 271, length: 53.973137mm, width: 19.069595mm
      Bone: Metacarpal, start: (-95.3462, 114.373, 223.558), end: (-39.7487, 157.623, 229.222), direction: (-0.786765, -0.612028, -0.0801425)
      Bone: Proximal, start: (-39.7487, 157.623, 229.222), end: (-2.65339, 157.176, 243.633), direction: (-0.932068, 0.0112209, -0.36211)
      Bone: Intermediate, start: (-2.65339, 157.176, 243.633), end: (14.5295, 147.615, 253.754), direction: (-0.776958, 0.432336, -0.457627)
      Bone: Distal, start: (14.5295, 147.615, 253.754), end: (22.9031, 141.161, 259.342), direction: (-0.70025, 0.539723, -0.467278)
    Middle finger, id: 272, length: 62.556217mm, width: 18.728878mm
      Bone: Metacarpal, start: (-89.9428, 111.348, 213.299), end: (-34.4068, 148.763, 210.69), direction: (-0.828718, -0.558311, 0.0389291)
      Bone: Proximal, start: (-34.4068, 148.763, 210.69), end: (8.25774, 151.251, 225.342), direction: (-0.944351, -0.0550744, -0.324297)
      Bone: Intermediate, start: (8.25774, 151.251, 225.342), end: (28.5329, 141.802, 239.622), direction: (-0.763982, 0.356055, -0.538104)
      Bone: Distal, start: (28.5329, 141.802, 239.622), end: (37.1578, 135.172, 247.494), direction: (-0.642301, 0.493721, -0.58625)
    Ring finger, id: 273, length: 59.408337mm, width: 17.821714mm
      Bone: Metacarpal, start: (-85.0515, 105.519, 204.445), end: (-33.3995, 134.687, 194.372), direction: (-0.858466, -0.484775, 0.167418)
      Bone: Proximal, start: (-33.3995, 134.687, 194.372), end: (6.64244, 136.545, 204.897), direction: (-0.966177, -0.0448305, -0.253951)
      Bone: Intermediate, start: (6.64244, 136.545, 204.897), end: (26.0159, 127.696, 218.941), direction: (-0.759391, 0.346829, -0.550486)
      Bone: Distal, start: (26.0159, 127.696, 218.941), end: (33.6939, 121.083, 227.359), direction: (-0.582786, 0.502012, -0.639018)
    Pinky finger, id: 274, length: 46.529858mm, width: 15.830640mm
      Bone: Metacarpal, start: (-79.8743, 94.5229, 198.582), end: (-32.6336, 118.689, 181.658), direction: (-0.848178, -0.433886, 0.303869)
      Bone: Proximal, start: (-32.6336, 118.689, 181.658), end: (-0.403066, 119.977, 187.88), direction: (-0.981116, -0.0392083, -0.189403)
      Bone: Intermediate, start: (-0.403066, 119.977, 187.88), end: (12.8916, 114.558, 198.629), direction: (-0.741281, 0.302122, -0.599354)
      Bone: Distal, start: (12.8916, 114.558, 198.629), end: (19.9167, 109.572, 207.225), direction: (-0.577263, 0.409747, -0.706311)
```

これは指の各関節ごとの骨の座標や大きさのデータを表しています。
手が検出されない時はこのような表示になります。

```
Frame id: 67334, timestamp: 1719513920206, hands: 0, fingers: 0
```

Sample.pyには計測結果を表示する気のはありますが、保存する機能はないのでここではpandasを用いてcsv形式で保存できるようにプログラムを書き換えます。

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

-----------------------------------省略----------------------------------------------

                for b in range(0, 4):
                    bone = finger.bone(b)
                    print "      Bone: %s, start: %s, end: %s, direction: %s" % (
                        self.bone_names[bone.type],
                        bone.prev_joint,
                        bone.next_joint,
                        bone.direction)

                    if self.finger_names[finger.type] == 'Thumb':
                        if self.bone_names[bone.type] == 'Metacarpal':
                            Thumb_fin_meta_direction_x.append(bone.direction.x)
                            Thumb_fin_meta_direction_y.append(bone.direction.y)
                            Thumb_fin_meta_direction_z.append(bone.direction.z)
                        if self.bone_names[bone.type] == 'Proximal':
                            Thumb_fin_prox_direction_x.append(bone.direction.x)
                            Thumb_fin_prox_direction_y.append(bone.direction.y)
                            Thumb_fin_prox_direction_z.append(bone.direction.z)
                        if self.bone_names[bone.type] == 'Intermediate':
                            Thumb_fin_inter_direction_x.append(bone.direction.x)
                            Thumb_fin_inter_direction_y.append(bone.direction.y)
                            Thumb_fin_inter_direction_z.append(bone.direction.z)
                        if self.bone_names[bone.type] == 'Distal':
                            Thumb_fin_dist_direction_x.append(bone.direction.x)
                            Thumb_fin_dist_direction_y.append(bone.direction.y)
                            Thumb_fin_dist_direction_z.append(bone.direction.z)
                    if self.finger_names[finger.type] == 'Index':

-----------------------------------省略----------------------------------------------

            hand_position_x.append(hand.palm_position.x)
            hand_position_y.append(hand.palm_position.y)
            hand_position_z.append(hand.palm_position.z)
            pitch_list.append(pitch)
            roll_list.append(roll)
            yaw_list.append(yaw)

-----------------------------------省略----------------------------------------------

def data_save_pandas():
    df = pd.DataFrame({
        "arm_direction_x" : arm_direction_x,
        "arm_direction_y" : arm_direction_y,
        "arm_direction_z" : arm_direction_z,
        "Thumb_fin_meta_direction_x" : Thumb_fin_meta_direction_x,
        "Thumb_fin_meta_direction_y" : Thumb_fin_meta_direction_y,
 
-----------------------------------省略----------------------------------------------


        "label" : label_list,
    })
    df.to_csv("./{0}_{1}.csv".format(current, str(label)))

```

たぶんもっと簡略化して記述する方法があると思いますが、めんどくさいのでとりあえずこのままで。
ここでは一部だけ掲載します。完全なコードはgithubで
https://github.com/tomson784/LeapmotionHandRecognize
これを参照してください。


#学習をさせる
学習には指の各ボーンのベクトルを用います。手の三次元的な形状の学習になるのでCNNが一番精度が出そうですが処理を軽くするためにここでは全結合のみで行います。
画像のような複雑なデータでもないし、分類数も少ないからおそろくかなり小規模なネットワークでも耐えるはず。

というわけで実装(一部省略)

```python:leap_learn.py

x_train, x_test, y_train, y_test = train_test_split(data, label, test_size=0.2)

# normalize
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = np_utils.to_categorical(y_train, len(category))
y_test = np_utils.to_categorical(y_test, len(category))

model = Sequential()
model.add(Dense(512, activation="relu", input_shape=(x_train.shape[1], )))
model.add(Dropout(DROPOUT))
model.add(Dense(512, activation="relu"))
model.add(Dropout(DROPOUT))
model.add(Dense(len(category), activation="softmax"))
model.summary()

model.compile(loss='categorical_crossentropy',
              optimizer=SGD(),
              metrics=['accuracy'])
```

学習結果は以下のようなグラフになりました
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/285306/7a6597bc-8c59-438e-6272-cf9a1cee516d.png)

割と早く収束しました。
まあ、指のベクトルデータに明らかな差があるので、ニューラルネットワークでなくても判別はできると思います。

今回はpythonとleapmotionとkerasを用いたデモということで、雑な仕上がりになってしまいました。

python3でleapmotionを使えるようにすれば、学習データを使ってリアルタイムでの手の形状の判別ができるようになって、さらに活用の幅が広がると思います。
興味を持った方はぜひ頑張ってください。
そして、その成果をぜひネットに公開してください。楽して新しいことを学びたいのでどなたかお願いします!