測域センサー(レーザー・レンジ・スキャナー)を使うと壁や床をタッチパネルのようにすることができます。
有名なものとしては、チームラボさんの「未来の遊園地」があります。
弊社でも去年やったイベント「ジョジョウォール」で、測域センサーを使用しています。
測域センサーは北陽電機さんのURGセンサーを使用しています。
ジョジョウォールでは、センサー値の取得から映像表現まで全てopenFrameworksを使用しています。
そこらへんの中身についての記事はコチラ。
そこで、この記事ではopenFrameworksではなく、Unityと連携してみることにしました。
実は測域センサーとUnityの連携については、上述のチームラボの斎藤 康毅さんのブログで丁寧に書かれています。とても参考になりました。ありがとうございます。
この記事では、センサー値をUnityで受け取るまでを実装レベルでみていきたいと思います。
諸々のバージョンなどの開発環境は以下の通りです。
【環境】
UST-10LX(測域センサー)
Mac OS X Ysemite 10.10.5
Xcode Version 7.2 (7C68)
Unity version 5.3.1f1
Apple LLVM version 7.0.2 (clang-700.1.81)
URG library v1.2.0
oscpack v1.1.0
UnityOSC v1.2
OpenCV v2.4.12
まずは、北陽電機さんのドキュメントに従ってURGセンサーのライブラリをインストールします。
c/c++、c#、javaの3種類がありますが、Macなので、環境を作るのが一番楽なc/c++を使いました。
とりあえず、この段階でちゃんと動くか試してみます。
今回使っているセンサー(UST-10LX)は、インターフェースがEthernetになっていて、センサーを電源に繋いで、PCのEthernetのとこに挿した後にネットワーク環境設定でIPアドレスを設定することで、センサーからの値をPCで受け取れるようになります。
センサーは初期設定ではIPが192.168.0.10になっているので、192.168.0の部分を同じにして、10の部分は1~254のうちの10以外の値を適当に設定します。
ライブラリに入っているc++のサンプル、get_distanceを動かしてみました。
Ethernetなので、実行時の引数に -e を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cd /hoge/urg_library_ja-1.2.0/samples/cpp $ ./get_distance -e 2646 [mm], (16157603 [msec]) 2639 [mm], (16157628 [msec]) 2642 [mm], (16157653 [msec]) 2646 [mm], (16157678 [msec]) 2638 [mm], (16157703 [msec]) 2637 [mm], (16157728 [msec]) 2644 [mm], (16157753 [msec]) 2642 [mm], (16157778 [msec]) 2634 [mm], (16157803 [msec]) 2637 [mm], (16157828 [msec]) |
センサーから値が取得できました。
このサンプルに手を加えて、Unityと連携させます。
その前にせっかくMacなので、Xcodeを使いたいと思います。
Xcodeを起動してコンソールアプリケーションのプロジェクトを作成します。
Build Settings -> Search Paths -> Header Search Paths にURGセンサーのライブラリのパスを設定します。
ライブラリインストール時に特に何もしていなければ、パスは /usr/local/include/urg_cpp です。
次に Build Phases -> Link Binary With Libraries のところに /usr/local/lib/liburg_cpp.a をドラッグ&ドロップします。
そして、get_distance.cppの中身をmain.cppにコピペするか、main.cppを消してget_distance.cppを入れるかすればOKです。
最後に実行時に引数として -e を指定するために、Edit Scheme -> Run -> Argumetnts Passed On Launch に -e を設定して、実行する準備完了です。
get_distance.cppの22行目のフラグを0にして、前方だけではなく、全てのデータを取得するようにしましょう。
URGセンサーから取得できる値は、30°の方向の距離1,300mmといった極座標で値が取れるので、三角関数を用いてX-Y 座標系に変換する必要があります。
詳しくは北陽電機さんのチュートリアルで丁寧に記載されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
long min_distance = urg.min_distance(); long max_distance = urg.max_distance(); size_t data_n = data.size(); for (size_t i = 0; i < data_n; ++i) { long l = data[i]; if ((l <= min_distance) || (l >= max_distance)) { continue; } double radian = urg.index2rad(i); long x = static_cast<long>(l * cos(radian)); long y = static_cast<long>(l * sin(radian)); cout << "(" << x << ", " << y << ")" << endl; } |
センサーからのX-Y座標が取得できたので、次はこの値にホモグラフィ行列をかけて、Unity側の座標の位置に対応させます。
ホモグラフィ行列を求めるにはopenCVを使うのが簡単で、
URGセンサーを使って反応させたい範囲の四隅の値と、Unityの中での四隅の値を引数にして、以下のように求めることができます。
以下では、200mm×400mmのURGセンサーの範囲を、Unityの座標における500×1,000に対応させるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// URGセンサーの座標 const cv::Point2f urg_pt[]={ cv::Point2f(0.0 , 200.0), // 左下 cv::Point2f(200.0 , 200.0), // 左上 cv::Point2f(200.0 ,-200.0), // 右上 cv::Point2f(0.0 ,-200.0)}; // 右下 // Unityの座標 const cv::Point2f unity_pt[]={ cv::Point2f(-500.0 ,0.0), // 左下 cv::Point2f(-500.0 ,500.0), // 左上 cv::Point2f( 500.0 ,500.0), // 右上 cv::Point2f( 500.0 ,0.0)}; // 右下 // homography 行列を算出 cv::Mat homography_matrix = cv::getPerspectiveTransform(urg_pt, unity_pt); |
センサーから取得できた値を3×1の行列にして、上記で得られたホモグラフィ行列をかけてあげれば、Unityでの座標が求まります。
1 2 |
cv::Mat urgPosMat = (cv::Mat_<double>(3, 1) << (double)x, (double)y, 1.0); cv::Mat unityPosMat = homography_matrix * urgPosMat; |
Unityでの座標が求まったので、次はそれをOSCを使ってUnityに送信します。
c/c++でOSCを扱うにあたって、シンプルで使いやすそうだったoscpackというライブラリを使いました。
今回は自分のPCのUnityアプリに送信するので、127.0.0.1にして、適当に8,000番ポートにしています。
1 2 3 4 5 6 7 8 9 10 11 12 |
cv::Point2d pos(x座標, Y座標); UdpTransmitSocket *oscSocket = new UdpTransmitSocket(IpEndpointName( 127.0.0.1, 8000 )); char buffer[1024]; osc::OutboundPacketStream p( buffer, 1024 ); p << osc::BeginBundleImmediate << osc::BeginMessage("/urg/pos/") << pos.x << pos.y << osc::EndMessage << osc::EndBundle; oscSocket->Send( p.Data(), p.Size() ); |
そして、ようやくここから、Unityです。
UnityにはOSCを扱うのにUnityOSCというライブラリがGithubにあるので、それを使います。
マニュアルもあるのですが、このマニュアル通りだと、OSCのメッセージが最後の1つしか受け取れません。
ググると同じ問題にあたっている方がいましたので、参考にさせて頂きました。ありがとうございました。
適当にUnityでプロジェクトを作成して、見えやすいように500×1,000の青いQuadをおいて、
受信したOSCメッセージから得られた座標にSphereを生成してみました。
下の白い四角がセンサーの位置です。自分の指を反応させてみました。(右上の部分の白い点)
今回使用したURGセンサーは、 センサーの周り270°の計測範囲があり、単純に取得すると1081ものが値が取れます。
計測範囲を指定したり、一定距離以上の値は無視するなどして、Unityに送る前にフィルターをかけてあげた方が良いと思います。
今回は、計測範囲を-90°〜90°にして、半径200mm以内の値のみOSCで送るようにしました。
しかし、単に指を反応させただけでも、21個も値が取れています。(距離が近いのもありますが。)
どんなものを作るかによりますが、取得した値の平均を求めて、それだけOSCで送信するなどしたら良いかもしれませんね。