Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Leap Motion実用サンプル(Visual Basic編)

Leap Motion実用サンプル(Visual Basic編)

Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理

2013年9月11日

Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。

  • このエントリーをはてなブックマークに追加

 今回は、画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるWPFアプリを作成する。さっそく、その開発内容を説明していこう。

まずWPFプロジェクトを作成しよう

 今回のLeap MotionアプリもWPFで作成する。

 これには、Visual Studio 2012(以下、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で作成すると安心だろう)。[名前]欄には、ここでは「SetMouseCursor_LeapMotion」と指定する。

 WPFの基本的な作成手順は、第1回と同じ手順となるので、説明を割愛する。具体的な手順は、第1回の「参照の追加」「プロジェクトのルートに「LeapCSharp.dll」と「Leapd.dll」を追加する」「プロパティを設定する」を参考にしてほしい。

[ソリューション エクスプローラー]内に画像を追加する

 [ソリューション エクスプローラー]内に「Images」というフォルダーを作成して、10枚の(任意の)画像を追加しておく。その際、画像サイズは640×480サイズの画像にしてほしい。

XMLファイルの追加

 [ソリューション エクスプローラー]でプロジェクト項目を選択した状態で、VS 2012のメニューバーから[プロジェクト]-[新しい項目の追加]と選択する。そこで表示されるダイアログで、[データ]から「XML ファイル」を選択し、[名前]欄に「forest.xml」と指定する。

 追加したファイルがXMLエディターで開かれるので、下記のリスト1のような内容のXMLコードを記述する。

XML
<?xml version="1.0" encoding="utf-8" ?>
<画像>
  <画像名>林_01.png</画像名>
  <画像名>林_02.png</画像名>
  <画像名>林_03.png</画像名>
  <画像名>林_04.png</画像名>
  <画像名>林_05.png</画像名>
  <画像名>林_06.png</画像名>
  <画像名>林_07.png</画像名>
  <画像名>林_08.png</画像名>
  <画像名>林_09.png</画像名>
  <画像名>林_10.png</画像名>
</画像>
リスト1 Forest.xmlファイルの内容

 <画像名>要素に指定している画像ファイル名には、[ソリューション エクスプローラー]内の「Images」フォルダー内に実在する画像ファイルの名前を記述している。従って、それぞれの状況に合わせて実際のファイル名に書き直してほしい。

 forest.xmlファイルは.EXEファイルと同じフォルダーに常に配置したいので、[ソリューション エクスプローラー]で「forest.xml」項目の右クリック・メニューで[プロパティ]を実行し、[プロパティ]ウィンドウの[ビルド アクション]の値を「コンテンツ」に、また[出力ディレクトリにコピー]の値を「常にコピーする」に変更しておこう。

今回のLeap Motionアプリについて

 今回のアプリは、ListBoxコントロールに配置された10枚の画像のうち、任意の画像をLeap Motionを使って空中でタッチすると、タッチされた画像がアニメーションを伴って、回転しながら大きく表示されるアプリだ(次の画面を参照)。表示された画像を消すには、5本の指で画像をタッチすればよい。

 今回のアプリではWin32 APIを使用している。

Leap Motionでタッチした画像がアニメーションを伴って大きく表示される

右端の画像をアニメーション表示させた場合、画像が半分ほど隠れてしまう。その場合は、1本の指で移動させることが可能だ。5本の指で空中タッチすると画像は消える。

画面のレイアウト(MainWindow.xaml)

 前回と同じように、デフォルトでGridコントロールが配置されているのをCanvasコントロールに変更する。その方が、座標値が取得しやすいからだ。

 XAMLデザイナー画面上で、先ほどのCanvasコントロール上に、「ListBox1」という名前のListBoxコントロールを1個配置する。

 次に、再びCanvasコントロール上に、「Image1」という名前のImageコントロールを1個配置する。ImageコントロールのWidthプロパティには「20」、Heightプロパティには「15」と指定し、Visibilityプロパティに「Collapsed」を指定して非表示としておく。

 またLeap Motionのタッチ・ポイントを表示させるために、「paintCanvas」という名前のInkPresenterコントロールをCanvasコントロール上に配置する。このInkPresenterコントロールは、どのコントロールよりも一番手前に配置する必要がある。

MainWindow.xamlファイルの編集

 上記のレイアウトを行って書き出されたXAMLコードを、さらに次のリストの太字部分ように編集する。

XAML
<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">

  <Window.Resources>
    <DataTemplate x:Key="ListBoxTemplate">
      <Image Width="320" Height="240" Margin="10" Stretch="Fill" Source="{Binding 画像名}"/>
    </DataTemplate>

    <ItemsPanelTemplate x:Key="WrapPanelTemplate">
      <WrapPanel Width="1800"/>
    </ItemsPanelTemplate>
  </Window.Resources>

  <Canvas>
    <ListBox x:Name="ListBox1"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="1801" Height="574" Canvas.Left="41" Canvas.Top="145"
      ItemTemplate="{StaticResource ListBoxTemplate}" ItemsPanel="{StaticResource WrapPanelTemplate}"/>

    <Image x:Name="Image1" Canvas.Left="647" Canvas.Top="106" Width="20" Height="15" Stretch="Fill"  Visibility="Collapsed"/>

    <InkPresenter Name="paintCanvas"/>
  </Canvas>
</Window>
リスト2 編集されたMainWindow.xamlファイル

 <Window.Resource>プロパティ要素内に、Key名が「ListBoxTemplate」の<DataTemplate>要素を定義する。その子要素として<Image>要素を配置し、Widthプロパティに「320」、Heightプロパティに「240」と指定し、Sourceプロパティに「<画像名>」をバインドする。この<画像名>は、VBコード(詳細後述)の中で、クラスに定義したプロパティ名だ。

 次に、Key名が「WrapPanelTemplate」の<ItemsPanelTemplate>要素を定義する。その子要素として、<WrapPanel>要素を配置し、Widthプロパティを指定する。そのWidthプロパティ値は、ListBox1のWidthプロパティ値に近い値を指定する。必ずWidthプロパティの値は指定する必要がある。

 ここで定義した各テンプレート(ListBoxTemplateやWrapPanelTemplate)を、StaticResourceマークアップ拡張機能を使用して、ListBox1のItemTemplateプロパティやItemsPanelプロパティにリソースとして参照させる。具体的には、ItemTemplateプロパティに「ListBoxTemplate」を、ItemsPanelプロパティに「WrapPanelTemplate」を参照させる。

 こうすることで、普通なら、ListBoxコントロールに画像を表示させる場合、縦一列に表示されるのが、横方向に表示されるようになり、ListBoxコントロールのWidth値以上になれば、折り返して自動的に2列で表示されるようになる。

 次の画面は、ここまでのレイアウト後の表示例である。

各コントロールをレイアウトした結果

Image1(=小さな□)は最初の状態では非表示になって入る。
ListBox1を配置する。
全てのコントロールの一番前面に「paintCanvas」という名前のInkPresenterコントロールを配置する。この領域にタッチ・ポイントが表示される。

Blendを起動してストーリーボードを作成

 次に、アニメーションのためのストーリーボードを作成する。

 まずはBlendを起動する。実際にBlend起動する前に、必ずVS 2012でプロジェクトをビルドしておこう。

 VS 2012からBlendを直接起動する方法もあるが、今回は.exeファイルを実行して起動する。64bitのWindows 8環境であれば、「C:\Program Files (x86)\Microsoft Visual Studio 11.0\Blend\Blend.exe」を実行すればよい。なお、VS 2012をインストールすると、Blendも自動的にインストールされている。Windows 8のスタート画面にも表示されているはずだから、それをタップして起動してもよい。

 Blendが起動したら、[プロジェクトを開く]から、現在作成している「SetMouseCursor_LeapMotion.sln」ファイルを指定して開く。

 Blendにおけるストーリーボードの作成は、Leap Motionとは直接関係がないので、解説は手短にさせていただく。

 「Storybord1」というストーリーボードを作成する(作成手順は「MSDN: ストーリーボードの作成、変更、または削除」を参照されたい)。アートボード全体が赤い枠線で囲まれてストーリーボードの記録が可能になるので、[オブジェクトとタイムライン]で「Image1」を選択して、黄色の再生ヘッドが「0」の位置で、Image1のVisibilityプロパティの値を「Visible」に指定する。次に、再生ヘッドを「1」の位置に移動し、Widthプロパティに「640」、Heightプロパティに「480」と指定する。Image1の[プロパティ]ウィンドウの[変換]タブの[RenderTransform]内にある[回転]タブの[Angle]に「720」と指定する(次の画面を参照)。

[Angle]に「720」と指定した(Blend)

 この状態でストーリーボードの記録をオフにして(=赤い枠線の左上隅にあるアイコンをクリック)、再生ヘッドを「0」の位置に戻して再生すると、画像が回転しながら大きく表示される。Image1は、最初は何も読み込んでいないため、動作確認する意味で、仮にImagesフォルダーの画像を1枚指定しておくといいだろう。ただし、動作を確認した後は、画像の指定を外しておくことを忘れないようにする。

 Blendを閉じて、VS 2012に戻る。保存と適用のメッセージが出るが、保存して適用させる。

 またストーリーボードのコードの下に、次のリストのコードが追加されているので、このコードは削除する。このコードを残したままにしておくと、アプリを実行した際に、即、ストーリーボードが実行されてしまう。

XAML
<Window.Triggers>
  <EventTrigger RoutedEvent="FrameworkElement.Loaded">
    <BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
  </EventTrigger>
</Window.Triggers>
リスト3 削除するコード(MainWindow.xaml)

プログラム・コード(MainWindow.xaml.vb)

 では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていこう。プログラム・コードも、タッチ処理以外は第1回と基本的に同じ内容となるので、説明を割愛する。まずは第1回の「名前空間の読み込み」「メンバー変数の宣言」「MainWindow_Loadedメソッドの処理」「Updateメソッドの処理」の開発手順を参考にタッチ処理の前までを実装してほしい。相違点として、下記の7点を修正してほしい。

(1)名前空間の読み込みに「System.Windows.Media.Animation」を追加する。これはストーリーボードを扱うために必要な名前空間だ
(2)メンバー変数の宣言にWin32 APIの以下のリストを追加する

Visual Basic
' Win32 APIの宣言
Private Declare Function SetCursorPos Lib "user32" (x As Integer, y As Integer) As Boolean
Private Declare Function apimouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Int32, ByVal dx As Int32, ByVal dy As Int32, ByVal cButtons As Int32, ByVal dwExtraInfo As Int32) As Boolean
Private Const MOUSEEVENTF_LEFTDOWN = &H2
リスト4 Win32 APIの宣言(MainWindow.xaml.vb)

(3)XMLの要素を表すメンバー変数「Private xmldoc As XElement」を宣言する
(4)ImageInfo型(後述)のコレクション変数「Private ImageList As New List(Of ImageInfo)」を宣言する
(5)今回はストーリーボードを扱うため、ストーリーボードのメンバー変数「Private strb As New Storyboard」を宣言する
(6)ListBox1より選択された画像名を格納しておく、文字列型のメンバー変数「Private SelectedImage As String」を宣言する
(7)第1回のリスト3にある「Private Message As String」という行は削除する

クラスの定義

 ImageInfoクラスを定義し、その中に「画像名」という文字列型のプロパティを定義する(リスト5)。

Visual Basic
Public Class ImageInfo
  Public Property 画像名 As String
End Class
リスト5 ImageInfoクラス(MainWindow.xaml.vb)

MainWindow_Loadedメソッドの処理

 MainWindowが読み込まれたときの処理を実装する。

 XElememt.Loadメソッドでforest.xmlファイルを読み込む。さらにDescendantsメソッドで全ての子孫要素「<画像名>」の内容を、変数「result」に格納しながら、以下の処理を行う。

  • <画像名>要素の値と、文字列“Images/”を連結した文字列値を、ImageInfoクラスの「画像名」プロパティに指定してImageInfoクラスの新規インスタンスを作成し、それをImageListオブジェクトにAddメソッドで追加していく

 ListBox1のItemsSourceプロパティにImageListオブジェクト指定する。これで、読み込まれた画像がListBox1に表示される。なお、ListBox1にはWrapPanelのテンプレートを適用しているので、画像は右端で折り返して表示されることになる。

 ~以下は第1回目と同じ処理の解説になるので割愛する。~

 具体的には次のようなコードになる。

Visual Basic
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded

  ' XMLファイルを読み込む
  xmldoc = XElement.Load("forest.xml")

  ' コレクション変数に<画像名>要素の内容を指定し、追加していく
  For Each result In From c In xmldoc.Descendants("画像名") Select c
    ImageList.Add(New ImageInfo With {.画像名 = "Images/" & result.Value})
  Next

  ' ListBox1のItemsSourceプロパティにImageListオブジェクトを指定する。
  ' これで、ListBox1コントロール内に画像が表示される
  ListBox1.ItemsSource = ImageList

  ……以下は第1回目と同じ処理になるので割愛する……

End Sub
リスト6 MainWindowが読み込まれたときの処理を行うMainWindow_Loadedメソッドの実装内容(MainWindow.xaml.vb)

Updateメソッドの処理(タッチ処理部分)

 第1回でも説明したが、Leap Motionのタッチ処理について簡単に説明しておこう。

 次にLeap Motionのタッチ処理になる。タッチのイメージは次の図のようなイメージだ。手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表す。空間の範囲は前後「1」~「-1」となっている。

Leap Motionのタッチ検出イメージ(Leap Motion SDKのAPIドキュメントから引用)
Leap Motionのタッチ検出イメージ(Leap Motion SDKのAPIドキュメントから引用)

 今回のサンプルでもホバーの場合は、表示されている円の色をNavyにする。その際、タッチ・ポイントの位置をメンバー変数「x」と「y」に格納する。画面に表示されている指の数をleap.Frame.Fingers.Countプロパティで取得して、メンバー変数「FingersCount」に格納しておく。

 さらに今回は、指が1本表示されている場合は、Image1のSetValueメソッドを使って、Image1のTopPropertyとLeftPropertyに、タッチした座標を指定する。具体的には下記のコードのようになる。

Visual Basic
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」と「y」に格納する
    x = touchPoint.X
    y = touchPoint.Y

    ' 表示されている指の本数を取得して、メンバー変数「FingersCount」に格納しておく
    FingersCount = leap.Frame.Fingers.Count

    If FingersCount = 1 Then
      ' Image1の表示位置を決める
      Image1.SetValue(Canvas.TopProperty, CDbl(y))
      Image1.SetValue(Canvas.LeftProperty, CDbl(x))
    End If

    ……コード略(続きは後述)……
  End If

Next
リスト7 ホバー時の処理(MainWindow.xaml.vb)

 次にタッチした処理になる。以下の処理を行う。

 タッチした場合は、表示されている円の色をRedに変える。

 指が1本だけ表示されている場合は、Win32 APIの「SetCursorPos(<タッチしたx座標>, <タッチしたy座標>)」と、マウスでクリックした動作の「apimouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)」を実行する。これにより、マウス・カーソルの位置と、Leap Motionで描かれた円のタッチ・ポイントの位置が同じになる。

  また、Image1を表示状態にする。

  さらに、「Storyboard1」という名前のStoryboardを見つけて、Beginメソッドでストーリーボードを実行する。これにより、画像をタッチした位置でアニメーションが実行される。

 指を5本表示させてタッチした場合は、Image1を非表示にする。

 具体的なコードは以下のとおり。

Visual Basic
' ホバー状態のときの処理
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
  ……コード略(前述)……

' タッチ状態のときの処理
ElseIf Pointable.TouchDistance <= 0 Then

  touchIndicator.Color = Colors.Red

  If FingersCount = 1 Then
    ' Win32 APIを実行する
    SetCursorPos(x, y)
    apimouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
    Image1.Visibility = Windows.Visibility.Visible

    ' ストーリーボードを実行する
    strb = DirectCast(Me.FindResource("Storyboard1"), Storyboard)
    strb.Begin()
  End If

  ' 指5本をかざすと、Image1が非表示になり、ストーリーボードを停止する
  If FingersCount = 5 Then
    Image1.Visibility = Windows.Visibility.Collapsed
    strb.Stop()
  End If

' タッチ対象外
Else
  touchIndicator.Color = Colors.Gold

End If
リスト8 ListBox内の画像をタッチした位置でアニメ―ションが実行される(MainWindow.xaml.vb)

 次に、ListBox1に表示されている画像が選択されたときの処理だ。ListBox1のSelectionChangedイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 ListBox1から選択された項目をImageInfo型にキャストして、「画像名」プロパティの値を取得して、メンバー変数「SelectedImage」に格納する。その値をUriクラスのコンストラクターに指定してUriクラスの新規インタンスを生成して、それをImage1のSourceプロパティに指定する。この際、Uriクラスのコンストラクターの第2パラメーターに「UriKind.Relative」と指定して、相対URIで指定することを忘れないようにしてほしい。

 前述のように、Win32 APIの「apimouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)」を使用しているため、Leap Motionのタッチとマウス・イベントが関連付けられている。

Visual Basic
Private Sub ListBox1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
  SelectedImage = DirectCast(ListBox1.SelectedItem, ImageInfo).画像名

  ' Image1にListBoxから選択された画像を表示する
  Image1.Source = New BitmapImage(New Uri(SelectedImage, UriKind.Relative))
End Sub
リスト9 ListBox内の画像が選択された時の処理(MainWindow.xaml.vb)

 このサンプルのコードは下記よりダウンロードできる*1

  • *1サンプルをダウンロードして動かす場合は、「LeapCSharp.NET4.0.dll」や「LeapCSharp.dll」、「Leap.dll」を読者自身のフォルダー内にあるDLLファイルに指定し直さなければ動かない可能性があるので、動かない場合は再指定していただきたい。

 今回はこれで終わりだ。今回はWin32 APIを使ったが、見ていただくと分かるように、特に扱いが難しいというものでもない。Leap Motionで動的に作成したオブジェクトを選択する場合は、どうしてもWin32 APIを使う必要がある。これ以降のサンプルでもWin32 APIを多用しているので、いろいろ参考になるだろうと思う。

 次回は、パーティクルを使ったサンプルを2つ同時に紹介する。Leap Motionの醍醐味(だいごみ)はパーティクルにあると筆者は思っている。しかし、パーティクル処理は難しい。筆者自身もよく理解できていないが、筆者なりにアレンジを加えたサンプルを紹介する。パーティクルの処理自体は全てDLL化しているので、コード自体は簡潔なコードになっている。

 では、また次回の記事でお会いしよう。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Leap Motion実用サンプル(Visual Basic編)
1. オブジェクト(Button)をLeap Motionでタッチして背景色を変化させる

Leap Motionの動くサンプルを実際に作ってみる連載スタート。今回はLeap Motionのタッチ操作で背景色を変化させるWPFアプリをVBで開発する。

Leap Motion実用サンプル(Visual Basic編)
2. Leap MotionによるWPFアプリ上のオブジェクトの移動

Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に配置したオブジェクトを、Leap Motionによる操作で移動させるアプリを作成。

Leap Motion実用サンプル(Visual Basic編)
3. 【現在、表示中】≫ Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理

Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。

Leap Motion実用サンプル(Visual Basic編)
4. Leap Motionでパーティクルを使用して軌跡に無数の円や画像を表示する

さまざまな色の粒子(パーティクル)が、Leap Motionによる手の指の動きに合わせて飛び散りながら追従するサンプル・アプリを作ってみよう。

Leap Motion実用サンプル(Visual Basic編)
5. Leap MotionでBing Mapsを扱う

リスト内に表示された住所項目をLeap Motionによりタッチすることで、Web上のサービス「Bing Maps」での地図検索を行うサンプル・アプリを作ってみよう。

サイトからのお知らせ

Twitterでつぶやこう!