1. slowsingle

    Posted

    slowsingle
Changes in title
+LeapMotionによる仮想マウス
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,120 @@
+格安でLeapMotionが手元に届いたので、何か作ってみようと思い、仮想マウスを作りました。
+
+LeapMotionの上で指を動かすとポインタが動き、クリックもできるようにしました。また、Macにおける画面遷移もできるようにしました。画面遷移ができるとカッコイイよね!
+
+# ソースコードの在り処
+
+コードはgithubにあります。コード量は少なめです。 </br>
+https://github.com/slowsingle/leapmotion_controller/tree/master
+
+ReadMeにインストール方法が記載されていますので、お使いになる場合は是非ごらんください。
+
+# プログラム
+
+リープモーションから取得した人差し指の座標(X, Y, Z)から、PCのディスプレイ上の座標系に変換して、その座標にポインタを移動させます。
+
+![01.gif](https://qiita-image-store.s3.amazonaws.com/0/72509/639b4fde-4264-5734-8b89-6692b17e3a12.gif)
+
+```cpp:座標系の変換
+new_x = (x + 120.0) / 240.0 * 1440.0;
+if (new_x < 0.0) {
+ new_x = 0.0;
+} else if (new_x > 1440.0 - 1.0) {
+ new_x = 1440.0 - 1.0;
+}
+new_y = (250.0 - y) / 150.0 * 900.0;
+if (new_y < 0.0) {
+ new_y = 0.0;
+} else if (new_y > 900.0 - 1.0) {
+ new_y = 900.0 - 1.0;
+}
+```
+
+touchDistance()を用いることでタッチの奥行きを測ることができます。
+
+![03.gif](https://qiita-image-store.s3.amazonaws.com/0/72509/1b9a56df-eb7f-5dca-a9ff-224d8361f31d.gif)
+
+一定のしきい値(以下のコードの場合は-0.1)を下回ると、押されたと判定し、そこから先ほどとは異なる一定のしきい値(以下のコードの場合は0.0)を上回ると離されたと判定し、タッチされたと判定する。
+
+```cpp:タッチ認識
+// タッチの認識
+if (isPressed) {
+ if (pointable.touchDistance() > 0.0) {
+ isPressed = false;
+ PyObject_CallMethod(pModule, "touch", "(dd)", touch_x, touch_y);
+ std::cout << "===== PRESS_OK =====" << std::endl;
+ }
+} else {
+ if (pointable.touchDistance() < -0.1) {
+ isPressed = true;
+ touch_x = new_x;
+ touch_y = new_y;
+ std::cout << "***** PRESS_Prepare *****" << std::endl;
+ }
+}
+```
+
+スワイプのジェスチャー認識は以下の通りです。
+
+```cpp:スワイプのジェスチャー認識
+Leap::GestureList gestures = frame.gestures();
+
+// スワイプのジェスチャーを認識する
+if (swipe_counter == 0) {
+ for (int g = 0; g < gestures.count(); g++) {
+ gesture = gestures[g];
+
+ if (gesture.type() == Leap::Gesture::TYPE_SWIPE) {
+ Leap::SwipeGesture swipe = gesture;
+ if (swipe.direction().x < -0.7) {
+ std::cout << swipe.direction().x << std::endl;
+ PyObject_CallMethod(pModule, "swipe_screen", "(i)", 0);
+ // スワイプを認識したら関数を抜ける
+ isPressed = false;
+ swipe_counter = 100;
+ return;
+ } else if (swipe.direction().x > 0.7) {
+ std::cout << swipe.direction().x << std::endl;
+ PyObject_CallMethod(pModule, "swipe_screen", "(i)", 1);
+ // スワイプを認識したら関数を抜ける
+ isPressed = false;
+ swipe_counter = 100;
+ return;
+ }
+ }
+ }
+}
+```
+
+リープモーションによる認識はC++で実装し、PCに対してポインタを移動させたりスワイプさせたりするのはPythonで行わせています。未確認ですが、これでOSに非依存な仮想マウスができたかと思います。
+
+C++からPythonを呼び出すのには`PyObject_CallMethod()`を使用しています。</br>
+この関数を呼ぶ前に初期化が必要になります。おまじない的な`Py_Initialize()`を呼び出し、その後(必要があれば)パスを通し、モジュールのインポートを行います。
+
+```cpp:C++からPythonを呼び出すための初期化処理
+bool SampleListener::initializeForPython() {
+ Py_Initialize();
+ // カレントディレクトリのパスを通す
+ PyObject *sys = PyImport_ImportModule("sys");
+ PyObject *path = PyObject_GetAttrString(sys, "path");
+ PyList_Append(path, PyString_FromString("."));
+
+ // カレントディレクトリにあるpythonモジュールをインポートする
+ pModule = PyImport_ImportModule("script");
+
+ if (pModule != NULL) {
+ return true;
+ } else {
+ return false;
+ }
+}
+```
+
+なお、`PyObject_CallMethod()`の引数は、1つ目がインポートしたモジュールのポインタ、2つ目が呼び出す関数名、3つ目がこれから呼び出すpython内の関数における引数の数と型((i)ならint型の引数が1つで、(dd)ならdouble型の引数が2つ)を表し、4つ目がその引数の具体的な値となります。
+
+`PyObject_CallMethod(pModule, "swipe_screen", "(i)", 1)`ならば、pModule(インポートしたモジュール)の`swipe_screen`関数に、引数としてint型の引数1を渡して、関数を実行することになります。
+
+# 終わりに
+
+やる前から何となく覚悟してたけどタッチしづらいです。  </br>
+ただ、画面遷移が手の動きでできるとカッコイイのは確かです。未来感を感じます。