Thanks Driven Life

日々是感謝

肉体言語 Tython

Tython とは

https://github.com/gongo/Tython/tree/development

肉体言語 Tython は、Kinect センサーを用いて、体の動きを利用してプログラムを入力する言語、というかインターフェースというかフレームワークというか。

図にするとこんな感じです。

https://cacoo.com/diagrams/Bb3xMwCFtoBe92mD-9BCF1.png

  1. Kinect を介して動きを検知 (Detector)
  2. 検知した動きによって、入力するソースコードを決定 (InputMethod)
  3. ソースコードを入力し終わったら、コンパイル (Compile)
  4. コンパイルしてできた命令列を実行 (VM)

デモ 「Hello, World!」

Tython を使って Hello, World! を出力してみました。


  • Hello, World! 出力まで 4分強
  • 一回で成功しなかった
    • 最終的に成功するまでの時間は 90分
    • 一度でも文字入力失敗すると最初から
    • BackSpace ?ねぇよそんなの

Hello, World のソースコードです。

左ジャブ左ジャブ左ジャブ左ジャブ右ストレート右ストレート右ストレート
右ストレート左ジャブ右アッパー左ジャブ左ジャブ左ジャブ左ジャブ
左ジャブ右ストレート右ストレート左ジャブ右ストレート右ストレート右アッパー
左ジャブ左ジャブ左ジャブ左ジャブ左ジャブ右ストレート左ジャブ
左ジャブ右ストレート右ストレート右アッパー左ジャブ左ジャブ左ジャブ
左ジャブ左ジャブ左ジャブ右ストレート右ストレート左ジャブ右ストレート
右アッパー左ジャブ左ジャブ左ジャブ左ジャブ左ジャブ右ストレート
左ジャブ左ジャブ左ジャブ左ジャブ右アッパー左ジャブ左ジャブ
左ジャブ左ジャブ右ストレート左ジャブ右ストレート左ジャブ左ジャブ
左ジャブ右アッパー左ジャブ左ジャブ左ジャブ左ジャブ右ストレート
右ストレート右ストレート右ストレート右ストレート右アッパー左ジャブ左ジャブ
左ジャブ左ジャブ右ストレート左ジャブ左ジャブ右ストレート右ストレート
右アッパー左ジャブ左ジャブ左ジャブ左ジャブ左ジャブ右ストレート
左ジャブ左ジャブ左ジャブ左ジャブ右アッパー左ジャブ左ジャブ
左ジャブ左ジャブ左ジャブ右ストレート左ジャブ左ジャブ右ストレート
右ストレート右アッパー左ジャブ左ジャブ左ジャブ左ジャブ左ジャブ
右ストレート左ジャブ左ジャブ右ストレート右ストレート右アッパー左ジャブ
左ジャブ左ジャブ左ジャブ左ジャブ右ストレート右ストレート左ジャブ
右ストレート左ジャブ右アッパー左ジャブ左ジャブ左ジャブ左ジャブ
右ストレート右ストレート左ジャブ右ストレート右ストレート右ストレート右アッパー
右ストレート左フック 左ジャブ左ジャブ右ストレート左フック左ジャブ
左ジャブ右ストレート左フック 左ジャブ左ジャブ右ストレート左フック 
左ジャブ左ジャブ右ストレート左フック左ジャブ左ジャブ右ストレート
左フック 左ジャブ左ジャブ右ストレート左フック 左ジャブ左ジャブ
右ストレート左フック 左ジャブ左ジャブ右ストレート左フック 左ジャブ
左ジャブ右ストレート左フック 左ジャブ左ジャブ右ストレート左フック 
左ジャブ左ジャブ右ストレート左フック 左ジャブ左ジャブ右ストレート
左フック 左ジャブ左ジャブ

Tython Detector

詳細はソースコードを見ていただければわかると思うので、Tython メインの機能である
Detector について軽く説明していきます。

今のところ、実装してる Detector は Pose 及び Command です。

✓ Pose Detector
その名のとおり、ポーズを検知します。
例として、「\(^o^)/」というポーズを検知する OwataPoseDetector を見ていきます。

まず、\(^o^)/を検知する条件を定義していきます。

https://cacoo.com/diagrams/Bb3xMwCFtoBe92mD-B95AE.png

  1. 両腕ともまっすぐのばす(θ1 = 180度)
  2. 頭(首)と両腕の角度は45度(θ2)
  3. 両手とも頭の上に置く(y1 > y2)

これをソースコードに落とすと以下になります(OwataPoseDetector.cc)

#include "OwataPoseDetector.h"

bool OwataPoseDetector::isPosing(void)
{
    /* 骨格ベクトル */
    Vector rightForearm  = user->skeletonRightForearm();   /* 右前腕 */
    Vector rightUpperArm = user->skeletonRightUpperArm();  /* 右上腕 */
    Vector leftForearm   = user->skeletonLeftForearm();    /* 左前腕 */
    Vector leftUpperArm  = user->skeletonLeftUpperArm();   /* 左上腕 */
    Vector medianLine    = user->skeletonHead(); /* 頭から首にかけて */

    /* 部位座標 */
    Vector rightHand     = user->positionRightHand();
    Vector leftHand      = user->positionLeftHand();
    Vector head          = user->positionHead();

    return rightForearm.isStraight(rightUpperArm.reverse()) // (1)
        && leftForearm.isStraight(leftUpperArm.reverse())
        && medianLine.betweenAngle(rightUpperArm, 45.0f) // (2)
        && medianLine.betweenAngle(leftUpperArm, 45.0f)
        && head.Y < rightHand.Y // (3)
        && head.Y < leftHand.Y;
}

ね、簡単でしょ?

PoseDetector は、ポーズの要素となる部位の座標、
または骨格の向きや角度を定義していく仕様書みたいなものです。
もちろん、上記で示した条件は私が雰囲気で決めたものなので、
気に入らなければ別の条件付けをしていきます。

✓ Command Detector
決められた動きの組み合わせを検知します。
例として、左ジャブを検知する LeftJabCommandDetector を見ていきます。

ここでも、条件付けを行います。

https://cacoo.com/diagrams/Bb3xMwCFtoBe92mD-08781.png

  1. 左腕を90度に曲げた状態 (左手は肩及び肘より上)
  2. 左腕をまっすぐ伸ばした状態 (左手は肩及び肘より上)
  3. 左腕を90度に曲げた状態(1と同じ条件)
  4. 上記3つを 1 秒以内 に行う

ソースコード (LeftJabCommandDetector.cc) は以下のようになります。

#include "LeftJabCommandDetector.h"

LeftJabCommandDetector::LeftJabCommandDetector(User* _user) : CommandDetector(_user)
{
    setCommand(1, 3,
               &LeftJabCommandDetector::isStand,
               &LeftJabCommandDetector::isLeftJab,
               &LeftJabCommandDetector::isStand);
}

LeftJabCommandDetector::~LeftJabCommandDetector(void)
{
}

bool LeftJabCommandDetector::isStand(void)
{
    Vector shoulder = user->positionLeftShoulder();
    Vector elbow    = user->positionLeftElbow();
    Vector hand     = user->positionLeftHand();
    Vector upperArm = user->skeletonLeftUpperArm();
    Vector forearm  = user->skeletonLeftForearm();

    return shoulder.Y < hand.Y
        && hand.Y > elbow.Y
        && upperArm.isOrthogonal(forearm);
}

bool LeftJabCommandDetector::isLeftJab(void)
{
    Vector shoulder = user->positionLeftShoulder();
    Vector elbow    = user->positionLeftElbow();
    Vector hand     = user->positionLeftHand();
    Vector upperArm = user->skeletonLeftUpperArm().reverse();
    Vector forearm  = user->skeletonLeftForearm();

    return shoulder.Y < hand.Y
        && hand.Y > elbow.Y
        && upperArm.isStraight(forearm);
}


ね、簡単でしょ?

CommandDetector では、コンストラクタで 制限時間ポーズの数*1 を設定します。
ポーズをどこまで実行したのか?とか、時間はどうなってる?っていうのは、
親クラスの CommandDetector で行っているので、
継承したクラス (LeftJab) では PoseDetector と同じように、ポーズの仕様を書くだけでいいのです。
もちろん、CommandDetector の中で PoseDetector を呼ぶとさらに OOP っぽいですね!(書いてないけど)

自分好みに拡張するには

Kinect が密接につながってるのは Tython Detector だけなので、
InputMethod、Compiler、VM は好き勝手にできます。

例えば、Tython のデフォルトの InputMethod は以下のようになります。

Detector 入力される文字
LeftJabCommand 'a'
RightStraightCommand '@'
LeftHookCommand ' '
RightUpperCommand 'g'

例えば LeftJab を 'def' とか、RightUpper を 'end' とか
書きたい言語の予約後にしても良し。
もちろん、CommandDetecotr だけでなく、PoseDetector も指定できるので、
OwataPose している間は、文字入力せず thread wait するとか!
それに合わせて Compiler や VM も変更可能!組み合わせは無限大です!!

プレゼン資料

昨日、社内勉強会で発表したときの資料です。

Future

プレゼン資料にも書きましたが、今一番したいのが軌跡検知 (LocusDetector)です。
Pose や Command は、プレイするユーザや状況によって変わることのない指標、
つまり腕の角度なり、部位比較による高さなどで求めることができますが、
対して軌跡検知ではそれが通用しません。というのも、

  • Kinect からの距離(画面内での絶対座標)が人や状況によってかわる

という問題があるからです。

例えば、「EXILEChoo Choo Train」の動きを検知する EXILELocusDetector を作りたいとします。
この時、「(x1,y1) (x2,y2) (x3,y3) を頭が通るようにする」と考えると
CommandDetector でも実装できそうですが、ユーザの立ち位置を固定しないといけません。
それでできると言われれば確かにそうですが、それだと面白くない。
なにより、複数プレイを可能にした場合、
全員分の立ち位置によって、頭の通る座標を変えないといけない。
そうなると、EXILEHIROLocusDetector とか EXILEKENCHILocusDetector とか
作らないといけなくなります。悲しいことだらけです。

一応考えてるのが

  1. 一定時間、指定した部位の座標を記録する
  2. 座標の動きから、それらの点を通る関数を求める(線形補間とか?わかにゃい)
  3. 求めた関数を、予め指定した関数と規模?が同じなるように正規化
  4. 比較して、誤差しきい値以内であれば Detect

こんな感じでしょうか。難しいそうですが、もし成功すればデンプシーロールができます。

参考

kinect-kamehameha
http://code.google.com/p/kinect-kamehameha/

Kinect の基本的な実装の流れ、Detector など参考にさせていただきました!

✓ 暴風型言語 Typhon
https://github.com/mitsuhide/Typhon/

Tython の命名と、制作のきっかけが Typhon*2 でした。

  1. Typhon 発表
  2. 初期、製作者が間違って「Tython」と typo
  3. しばらくして Kinect 購入
  4. Tython のことを思い出す
  5. Kinect + Tython → 体で 'a' や '@' を入力出来れば面白いんじゃないか!?

いろいろあって、Compiler や VM は Typhon との互換性をとれるようにしました。

まとめ

もう Hello, World 打ちたくないです

*1:可変引数を使ってるため、わざわざポーズの数入力してるけど、なんかアレ。純粋にメソッド名だけ記述したい

*2:どちらも 〜thon なのに Python 関係ないっていう。。