Leap Motion実用サンプル(Visual Basic編)
オブジェクト(Button)をLeap Motionでタッチして背景色を変化させる
Leap Motionの動くサンプルを実際に作ってみる連載スタート。今回はLeap Motionのタッチ操作で背景色を変化させるWPFアプリをVBで開発する。
はじめに
この連載は、Leap Motionの現物を持っている方が対象だ。しかし、Leap Motionの現物を持っていなくても、この連載で、Leap Motionを使うと、どのようなことができるのかの参考にはなるだろうと思う。この連載で、Leap Motionに興味を持たれた方は、購入を考えてみられたらいかがだろうか。直販で、1万5百円で入手できる。決して高い買い物ではないだろう(「Buy the Leap Motion Controller」で購入できる)。
Leap Motionを使う前に
実際にサンプルを作って動かす前に、「環境構築」と言うほどでもないが、「Get started with yourLeap Motion Controller.」から、[Windows Download]ボタンをクリックして、「Leap_Motion_Installer_release_public_win_x86_1.0.7+7648_ah744.exe」ファイルをダウンロードして、Leap Motionの実行環境をインストールしておく必要がある。インストールが完了すると、Leap Motionを接続可能になる。
次に、「Leap Motion Developer SDK」のページから[v.1.0.8.7665 for Windows](次の画面を参照)を任意のフォルダーにダウンロードして、解凍しておいてほしい*1。この解説はWindowsをメインとするので、Windows版をダウンロードする。
- *1Leap Motionはしきりにバージョンアップを繰り返しているので、上記のURLに入って時々Leap Motionのバージョンをチェックして、バージョンアップされている場合は、最新のファイルをダウンロードしてほしい。また、更新の連絡も来るので、そのときは更新してほしい。この記事が掲載されるころには、また新しいバージョンになっている可能性もある。その点をご了承願いたい。
任意のフォルダーに解凍すると、「<解凍したフォルダー>\LeapDeveloperKit\LeapSDK\lib」フォルダー内に「LeapCSharp.NET4.0.dll」ファイルが展開されている。このDLLファイルへの参照を、Visual Studio 2012(以下、VS 2012)の[参照の追加]で追加する必要がある。
また、先ほどのlibフォルダーの中にあるx86フォルダー内には、「LeapCSharp.dll」と「Leap.dll」というファイルが存在している。これら2つのDLLファイルを、[ソリューション エクスプローラー]でプロジェクトのルートに追加して、[プロパティ]ウィンドウで「常にコピーする」と指定しておく。VS 2012のメニューバーから[ビルド]-[構成マネージャ]と選択して、[アクティブ ソリューション プラットホーム]が「Any CPU」または「x86」の場合は、x86フォルダー内のDLLファイル、「x64」ならx64フォルダー内のDLLファイルを選択する必要がある。これを間違わないでほしい。筆者の環境では[構成マネージャ]が「Any CPU」になっているので、x86フォルダー内のファイルを指定している。各自の[構成マネージャ]の値に対応して読み込むフォルダーを指定してほしい。
これらの参照設定の手順については、実際のサンプル作成時に、必要に応じて解説していく予定だ。
まずWPFプロジェクトを作成しよう
Leap MotionのアプリはWPFで作成する。VS 2012のIDEを起動して、メニューバーから[ファイル]-[新規作成]-[プロジェクト]と選択して、その後に表示される[新しいプロジェクト]ダイアログで「Visual Basic」の「WPF アプリケーション」テンプレートを選択する(Leap Motionは.NET Framework 3.5と4.0に対応している。しかし、.NET Framework 4.5でも動作する。今回のアプリは全て.NET Framework 4.5で作成している。しかし、あくまでも対応しているのは、.NET Framework 3.5と4.0だ。心配な方は、.NET Framework 4.0で作成すると安心だろう)。[名前]欄には、今回は「ChangeBackgroundLeapMotion」と指定する。
参照の追加
プロジェクトが作成されたら、[ソリューション エクスプローラー]の上部にある[すべてのファイルを表示]アイコンをクリックして、[参照設定]を表示させる。その[参照設定]の右クリック・メニューで[参照の追加]を選択する。次の画面が表示されるので、「最近使用したファイル」内に、「LeapCSharp.NET4.0.dll」が見当たらない場合は、[参照]ボタンから、「<解凍したフォルダー>\LeapDeveloperKit\LeapSDK\lib」フォルダー内の「LeapCSharp.NET4.0.dll」ファイルを指定して[OK]ボタンをクリックする。
プロジェクトのルートに「LeapCSharp.dll」と「Leap.dll」を追加する
[ソリューション エクスプローラー]内のプロジェクト項目の右クリック・メニューから[追加]-[既存の項目]を選択して(次の画面)、「<解凍したフォルダー>\LeapDeveloperKit\LeapSDK\lib\x86」フォルダー内の「LeapCSharp.dll」と「Leap.dll」の2つのファイルを追加する。
[ソリューション エクスプローラー]内に次の画面のように「LeapCSharp.dll」と「Leap.dll」が追加される。
プロパティを設定する
「LeapCSharp.dll」と「Leap.dll」のプロパティから、[出力ディレクトリーにコピー]を「常にコピー」するに変更しておく。これは、2つのDLLファイル共にしておく必要がある(次の画面を参照)。
今回のLeap Motionアプリについて
今回のアプリは、画面上に配置したボタンを、Leap Motionで作成した円(=タッチ・ポイント)でタップすることで、画面の背景色を、ボタンの表面に書かれている文字の背景色に変化させるものだ(次の画面を参照)。
ボタン上に黒い円(=「●」のような塗りつぶされた丸)を乗せ、画面を突くような動作をすると、円が赤に変化する(=タッチ状態になる)。すると背景色が変化する。円が黒のままでは、タッチした動作にはならないので、背景色は変化しないので注意してほしい。5本の指をかざして、5個の円の色を赤にすると、デフォルトの白の背景色に戻る。
画面のレイアウト(MainWindow.xaml)
デフォルトでGridコントロールが配置されているのをCanvasコントロールに変更する。その理由は、座標値が取得しやすいからだ。
XAMLデザイナー画面上に、先ほどのCanvasコントロール上に、さらに「ShowArea」という名前のCanvasコントロールを配置し、その子要素として5つのButtonコントロールを配置する。Buttonコントロールの名前には、左から順に「redButton」「blueButton」「greenButton」「yellowButton」「blackButton」としておく。
また、Leap Motionのタッチ・ポイントを表示させるために、「paintCanvas」という名前のInkPresenterコントロールをルートのCanvasコントロール上に配置する。このInkPresenterコントロールは、どのコントロールよりも一番手前に配置しておく必要がある。最終的なレイアウトは次のようになる。
Buttonコントロールは「ShowArea」というCanvasコントロールの子要素として配置している。
全てのコントロールの一番前面にpaintCanvas」という名前のInkPresenterコントロールを配置する。この領域にタッチポイントが表示される。
XAMLデザイナーで上記のレイアウトを行うと、書き出されるXAMLコードはリスト1のようになる。
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized">
<Canvas>
<Canvas x:Name="ShowArea" Width="1920" Height="1080">
<Button x:Name="redButton" Content="赤" Height="80" Canvas.Left="59" Canvas.Top="49" Width="131" FontSize="36" FontWeight="Bold" Foreground="Red"/>
<Button x:Name="blueButton" Content="青" Height="80" Canvas.Left="212" Canvas.Top="49" Width="131" FontSize="36" FontWeight="Bold" Foreground="Blue"/>
<Button x:Name="greenButton" Content="緑" Height="80" Canvas.Left="368" Canvas.Top="49" Width="131" FontSize="36" FontWeight="Bold" Foreground="Green"/>
<Button x:Name="yellowButton" Content="黄" Height="80" Canvas.Left="525" Canvas.Top="49" Width="131" FontSize="36" FontWeight="Bold" Foreground="Yellow"/>
<Button x:Name="blackButton" Content="黒" Height="80" Canvas.Left="677" Canvas.Top="49" Width="131" FontSize="36" FontWeight="Bold" Foreground="Black"/>
</Canvas>
<TextBlock x:Name="TextBlock1" Height="63" Canvas.Left="284" TextWrapping="Wrap" Canvas.Top="341" Width="885" FontSize="48" FontWeight="Bold"/>
<InkPresenter Name="paintCanvas"/>
</Canvas>
</Window>
|
Buttonコントロールを4個配置し、Contentプロパティに各色名を指定し、Foregroundプロパティに色名にあった色を指定している。
ルートのCanvasコントロールの最後に<InkPresenter>要素が記載されているが、これによりInkPresenterコントロールが最前面に表示されるようになる。このInkPresenterコントロールをButtonコントロールよりも先に配置すると、表示されるタッチ・ポイントがButtonコントロールの背後に表示されてしまうので、注意してほしい。
プログラム・コード(MainWindow.xaml.vb)
では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていこう。
名前空間の読み込み
まず、Leap Motionを扱うため、「Imports Leap」でLeap名前空間を読み込む。次に、インクの操作を行うクラスを提供するSystem.Windows.Ink名前空間を読み込む(次のリストを参照)。
Imports Leap
Imports System.Windows.Ink
|
メンバー変数の宣言
次にメンバー変数を宣言する。
新しいControllerクラスのインスタンスであるleapメンバー変数を宣言する。次に、インク・ストローク(=System.Windows.Ink名前空間のStrokeクラスで表現される、WPF上でのインクの線)の外観を指定するDrawingAttributesクラス(System.Windows.Ink名前空間)のインスタンスである「touchIndicatorメンバー変数」を宣言する。デジタイザーとスタイラスから収集された単一のデータ・ポイントを表すStylusPoint構造体の「touchPointメンバー変数」を宣言する。
具体的には以下のようなコードになる。このコード例では、そのほかの必要なメンバー変数も宣言している。
……省略……
Class MainWindow
Private leap As New Controller
Private touchIndicator As New DrawingAttributes
Private touchPoint As StylusPoint
Private windowWidth As Double = 1920
Private windowHeight As Double = 1080
Private x As Integer
Private y As Integer
Private tx As Double
Private ty As Double
Private FingersCount As Integer
Private Message As String
Private Index As Integer
End Class
|
MainWindow_Loadedメソッドの処理
MainWindow_Loadedメソッド(=メイン・ウィンドウのLoadedイベントのハンドラー)では、MainWindowが読み込まれたときの処理を実装する。
AddHandlerステートメントを使って、構成ツリーのオブジェクトがレンダリングされる直前に発生する「CompositionTarget.Renderingイベント」に対するイベント・ハンドラーとしてUpdateメソッドを指定する(※Updateメソッドの実装内容は後述)。
インク・ストロークの外観を表す、DrawingAttributesオブジェクトのインスタンス「touchIndicator」のWidthプロパティとHeightプロパティにそれぞれ「20」を指定する。スタイラスの形状を指定するStylusTipプロパティに「StylusTip.Ellipse」を指定して円形とする。Leap Motionの上で指をかざすと、かざした指の本数に応じて20px(px=ピクセル)の円が表示されるようになる。
具体的には次のようなコードになる。
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
AddHandler CompositionTarget.Rendering, AddressOf Update
touchIndicator.Width = 20
touchIndicator.Height = 20
touchIndicator.StylusTip = StylusTip.Ellipse ‘ スタイラスの形状を20pxの円形に指定する
TextBlock1.Foreground = New SolidColorBrush(Colors.White)
End Sub
|
Updateメソッドの処理
次は、CompositionTarget.Renderingイベント・ハンドラーであるUpdateメソッドの処理だ。
まず、上記のコードを実装した段階で、「Update」の位置に[エラー修正のオプション]というスマート・タグが表示されるので、そのタグのメニューから['ChangeBackgroundLeapMotion.MainWindow' に 'Update' のメソッド スタブを生成]をクリックすると、Updateメソッドのひな型が追加される。次に、そのメソッド内を実装していく。
「paintCanvas」という名前のInkPresenter内をクリアする。この処理を行っていないと、Updateメソッドは常に呼び出されているため、円を画面上で動かすと、円の軌跡が残ったまま表示されてしまう。そのため、InkPresenter内をクリアする必要があるのだ。
Leap Motionのフレームを表すFrame型(Leap名前空間)の変数「frame」を宣言する。InteractionBox型(Leap名前空間)の変数「interactionBox」を宣言し、そこにleap.Frame.InteractionBoxプロパティからInteractionBoxオブジェクトを取得する。InteractionBoxオブジェクトは、Leap Motionで認識できる稼働範囲となる。InteractionBoxオブジェクトを使用することで、指やツール(=ペンなど)の位置を実際のディスプレイの座標系に変換できる(次のリストを参照)。詳細な図については、「Leap Motionでのタッチ操作はどう開発するのか?」を参照してほしい。
具体的なコードは次のとおり。
Private Sub Update(sender As Object, e As EventArgs)
paintCanvas.Strokes.Clear()
windowWidth = Me.Width
windowHeight = Me.Height
Dim frame As Frame = leap.Frame
' Leap Motionで認識できる稼働範囲を取得する
Dim interactionBox As InteractionBox = leap.Frame.InteractionBox
End Sub
|
Leap.Frame.Pointablesプロパティで得られるPointableListオブジェクト内を変数「Pointable」で反復処理しながら、1つずつPointableオブジェクトを処理し、それぞれのタッチ位置を取得していく。
interactionBoxオブジェクトのNormalizePointメソッドに引数としてPointable.StabilizedTipPositionプロパティ値を渡し、ポインター上の位置を取得する。
変数「windowWidth」と「windowHeight」で表されたクライアント領域のウィンドウがある場合、以下のコードで示す計算式を使用してこのウィンドウ内のタッチ・ポイントの2D座標を得ることができる。
Private Sub Update(sender As Object, e As EventArgs)
……コード略……
For Each Pointable As Pointable In leap.Frame.Pointables
Dim normalizedPosition As Leap.Vector = interactionBox.NormalizePoint(Pointable.StabilizedTipPosition)
tx = normalizedPosition.x * windowWidth
ty = windowHeight - normalizedPosition.y * windowHeight
touchPoint = New StylusPoint(tx, ty) ' ウィンドウ内のタッチ・ポイントの位置を取得する
……コード略(後述)……
Next
End Sub
|
さらに、上記のFor Eachステートメントの一番下に次のコードを追記して、ディスプレイ上に円形のタッチ・ポイントを表示する。
For Each Pointable As Pointable In leap.Frame.Pointables
……コード略(前述)……
Dim tips As New StylusPointCollection(New StylusPoint() {touchPoint})
Dim touchStroke As New Stroke(tips, touchIndicator)
paintCanvas.Strokes.Add(touchStroke)
Next
|
次にLeap Motionのタッチ処理になる。タッチのイメージは次の図のようなイメージだ。
手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表す。空間の範囲は前後「1」~「-1」となっている。
まずホバーの場合は、表示されている円がNavyの色になる。タッチ・ポイントの位置をメンバー変数「x」と「y」に格納する。
画面に表示されている指の数をleap.Frame.Fingers.Countプロパティで取得して、メンバー変数「FingersCount」に格納しておく。
For Each Pointable As Pointable In leap.Frame.Pointables
……コード略(前述)……
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
touchIndicator.Color = Colors.Navy
x = touchPoint.X
y = touchPoint.Y
FingersCount = leap.Frame.Fingers.Count ' 表示されている指の本数を取得して、メンバー変数FingersCountに格納しておく
……コード略(続きは後述)……
End If
Next
|
次にタッチした処理になる。タッチした場合は、表示されている円が赤に変わる。
それと同時に、タッチしたButtonコントロールの座標を取得して、どのButtonがタッチされたかを判別する。タッチされたButtonによって、メンバー変数「Index」に値を格納する。
具体的には次のコードのようになる。
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
……コード略(前述)……
ElseIf Pointable.TouchDistance <= 0 Then
touchIndicator.Color = Colors.Red
' redButtonの座標を取得し、タッチ状態ならメンバー変数「Index」を「1」で初期化する
If x > redButton.GetValue(Canvas.LeftProperty) And x < redButton.GetValue(Canvas.LeftProperty) + redButton.Width AndAlso y > redButton.GetValue(Canvas.TopProperty) And y < redButton.GetValue(Canvas.TopProperty) + redButton.Height Then
Index = 1
End If
' blueButtonの座標を取得し、タッチ状態ならメンバー変数「Index」を「2」で初期化する
If x > blueButton.GetValue(Canvas.LeftProperty) And x < blueButton.GetValue(Canvas.LeftProperty) + blueButton.Width AndAlso y > blueButton.GetValue(Canvas.TopProperty) And y < blueButton.GetValue(Canvas.TopProperty) + blueButton.Height Then
Index = 2
End If
' greenButtonの座標を取得し、タッチ状態ならメンバー変数「Index」を「3」で初期化する
If x > greenButton.GetValue(Canvas.LeftProperty) And x < greenButton.GetValue(Canvas.LeftProperty) + greenButton.Width AndAlso y > greenButton.GetValue(Canvas.TopProperty) And y < greenButton.GetValue(Canvas.TopProperty) + greenButton.Height Then
Index = 3
End If
' yellowButtonの座標を取得し、タッチ状態ならメンバー変数「Index」を「4」で初期化する
If x > yellowButton.GetValue(Canvas.LeftProperty) And x < yellowButton.GetValue(Canvas.LeftProperty) + yellowButton.Width AndAlso y > yellowButton.GetValue(Canvas.TopProperty) And y < yellowButton.GetValue(Canvas.TopProperty) + yellowButton.Height Then
Index = 4
End If
' blackButtonの座標を取得し、タッチ状態ならメンバー変数「Index」を「5」で初期化する
If x > blackButton.GetValue(Canvas.LeftProperty) And x < blackButton.GetValue(Canvas.LeftProperty) + blackButton.Width AndAlso y > blackButton.GetValue(Canvas.TopProperty) And y < blackButton.GetValue(Canvas.TopProperty) + blackButton.Height Then
Index = 5
End If
' 画面上に表示されている指が1本の場合は、メンバー変数「Index」の値で条件分岐を行う。
' Indexの値で、「ShowArea」という名前のCanvasの背景色を、タッチしたボタンの色に変化させる。
If FingersCount = 1 Then
Select Case Index
Case 1
ShowArea.Background = New SolidColorBrush(Colors.Red)
Message = "背景色は赤です。"
Exit Select
Case 2
ShowArea.Background = New SolidColorBrush(Colors.Blue)
Message = "背景色は青です。"
Exit Select
Case 3
ShowArea.Background = New SolidColorBrush(Colors.Green)
Message = "背景色は緑です。"
Exit Select
Case 4
ShowArea.Background = New SolidColorBrush(Colors.Gold)
Message = "背景色は黄です。"
Exit Select
Case 5
ShowArea.Background = New SolidColorBrush(Colors.Black)
Message = "背景色は黒です。"
Exit Select
End Select
ElseIf FingersCount = 5 Then
ShowArea.Background = New SolidColorBrush(Colors.White)
End If
End If
|
Indexの値によって処理を分岐する。背景色を変化させる。ただし、この場合は、指が1本認識されている場合に限られる。
指を5本表示してタッチ処理を行うと、背景色がデフォルトの白に戻る。
このサンプルのコードは、下記のリンク先よりダウンロードできる*2。
- *2サンプルをダウンロードして動かす場合は、「LeapCSharp.NET4.0.dll」や「LeapCSharp.dll」、「Leap.dll」を読者自身のフォルダー内にあるDLLファイルに指定し直さなければ動かない可能性があるので、動かない場合は再指定していただきたい。
今回はこれで終わりだ。Leap Motionに関する情報はWebを検索すると、ある程度見つけられるが、実際に動くサンプルで、コードまで掲載されているサイトはない。今回のサンプルは、ごく基本のサンプルであるが、Leap Motionで実際に動かすと、簡単なサンプルでも、感動するものである。このサンプルと、これ以降に紹介するサンプルが、読者の皆さまに感動をもたらすものであることを願う。
では、また次回にお会いしよう。
- (※C++の連載記事「連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――」はこちら。「連載:Leap Motion開発入門(C#編)」はこちら)
- (※C#の記事「特集:コードで理解するLeapアプリ開発の概要 C#開発者から見たLeap Motion開発のファースト・インプレッション」はこちら)
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
1. 【現在、表示中】≫ オブジェクト(Button)をLeap Motionでタッチして背景色を変化させる
Leap Motionの動くサンプルを実際に作ってみる連載スタート。今回はLeap Motionのタッチ操作で背景色を変化させるWPFアプリをVBで開発する。
2. Leap MotionによるWPFアプリ上のオブジェクトの移動
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に配置したオブジェクトを、Leap Motionによる操作で移動させるアプリを作成。
3. Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。
4. Leap Motionでパーティクルを使用して軌跡に無数の円や画像を表示する
さまざまな色の粒子(パーティクル)が、Leap Motionによる手の指の動きに合わせて飛び散りながら追従するサンプル・アプリを作ってみよう。
5. Leap MotionでBing Mapsを扱う
リスト内に表示された住所項目をLeap Motionによりタッチすることで、Web上のサービス「Bing Maps」での地図検索を行うサンプル・アプリを作ってみよう。