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

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

Leap MotionでWebカメラを操作する

2013年10月15日

Leap MotionでWebカメラを操作して、撮った写真画像にフレームを合成したり、音声を録音したりできるWPFアプリを作ってみよう。

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

 今回のサンプルは、WebカメラをLeap Motionで操作するWPFのアプリだ。撮った写真画像にフレームを合成したり、音声を録音したりと、機能が豊富である。そのため、少し解説も長くなるが、ご了承願いたい。

注意

 このアプリは解像度を1920×1080を対象に作っていますので、この解像度より低い場合は、画面のコントロールが切れて表示され、操作ができませんので、ご注意ください。

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

 今回もLeap MotionのアプリはWPFで作成する。具体的なアプリ内容は後述するが、まずは基礎部分だけ準備しよう。

 Visual Studio 2012(以下、VS 2012)のIDEを起動して、メニューバーから[ファイル]-[新規作成]-[プロジェクト]と選択して、Visual Basicのテンプレートから「WPF アプリケーション」を選択する。[名前]欄には、ここでは「WebCameraImageBlitLeapMotion」と指定する。

 ソリューション・エクスプローラーでプロジェクト内にImagesフォルダーを作成して、フレーム(=写真の回りを飾る枠)となる画像(=サイズ640×480のPNG画像)を複数枚(本稿の例では「frame_31.png」~「frame_37.png」のような名前にした)、そのフォルダーの中に追加する。追加した画像の[ビルド アクション]プロパティが「Resource」、[出力ディレクトリにコピー]プロパティが「コピーしない」とデフォルトのままにしていると、今回のアプリ上に配置する[合成保存]ボタンをタッチした際にエラーが表示される。そこで、Imagesフォルダー内の画像を全て選択して、[プロパティ]ウィンドウの、[ビルド アクション]に「コンテンツ」を、[出力ディレクトリにコピー]に「常にコピーする」と指定しておくことが必要だ。

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

Webカメラに必要なコントロールをダウンロードする

 まず、「WPF Mediakit」をダウンロードし、適当なフォルダーに解凍しておく。この中には後述する「WPFMediaKit.dll」ファイルが含まれている。

 次に、「WPF:Webcam Control」から、[Download DLL - 7.9 KB]をダウンロードし、適当なフォルダーに解凍しておく。この中には後述する「WebCamControl.dll」ファイルが含まれている。

 さらに、Webカメラを扱うために、Microsoft Expression Encoder 4の機能も使用する。そのため、「Microsoft Expression Encoder 4 Service Pack 2」をダウンロードし、ローカル環境にインストールして、Microsoft.Expression.Encoderアセンブリを参照しておこう。このアセンブリは、例えばWindows 8(64bit)OSでは「C:\Program Files (x86)\Microsoft Expression\Encoder 4\SDK\Microsoft.Expression.Encoder.dll」ファイルとしてインストールされているので、これへの参照設定を追加すればよい(追加手順は通常と同じなので割愛)。

ダウンロードしたコントロールをツールボックスに追加する

 ツールボックスの右クリック・メニューで[アイテムの選択]を選択し、そこで表示されるダイアログで[WPFコンポーネント]タブを開いて[参照]ボタンをクリックして、先ほどダウンロードして解凍しておいた「WPFMediaKit.dll」ファイルを追加する。同様に、先ほど解凍しておいた「WebCamControl.dll」ファイルも指定する。すると、次の画面のように、「DvdPlayerElement」「MediaDetectorElement」「MediaUriElement」「MFMediaUriElement」「VideoCaptureElement」「Webcam」コンポーネントがツールボックスに追加される。

読み込んだDLLに含まれている各コンポーネントが追加されたツールボックス
読み込んだDLLに含まれている各コンポーネントが追加されたツールボックス

NuGetパッケージの管理

 ソリューション・エクスプローラー内の[参照設定]の右クリック・メニューで表示される[NuGet パッケージの管理]から、「WriteableBitmapEx」パッケージをインストールする必要がある。なお、このパッケージのコンポ―ネットには画像を合成するメソッドなどが含まれている。

WriteableBitmapExをインストールする

 [NuGet パッケージの管理]ダイアログの検索欄に「WriteableBitmapEx」と入力すると、WriteableBitmapExパッケージのインストールが表示されるので、[インストール]ボタンをクリックすればインストールできる。

XMLファイルの追加

 VS 2012のメニューバーから[プロジェクト]-[新しい項目の追加]と選択。これにより表示されるダイアログの左側から[データ]を選び、右側から「XML ファイル」を選択。[名前]欄に「colorFrame.xml」と指定し、[追加]ボタンをクリックして新規XMLファイルを追加する。

 IDEのXMLエディターが起動して、先ほど作成したXMLファイルが開くので、リスト1のような内容に書き換える。このXMLファイルには、先ほどImageフォルダーに追加した全フレーム画像ファイルの名前を定義している(実際に試す場合は、自分が作成した画像ファイル名のみを記述すればよい)。

XML
<?xml version="1.0" encoding="utf-8" ?>
<枠>
  <画像名>frame_31.png</画像名>
  <画像名>frame_32.png</画像名>
  <画像名>frame_33.png</画像名>
  <画像名>frame_34.png</画像名>
  <画像名>frame_35.png</画像名>
  <画像名>frame_36.png</画像名>
  <画像名>frame_37.png</画像名>
  ……以下、上記の繰り返しのため省略……
</枠>
リスト1 colorFrame.xmlファイルのコード内容

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

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

 今回のアプリは、ちょっと複雑だ。2つの機能を有している。1つはWebカメラから取り込んだ画像に各種フレームを適用させてローカルに保存させる機能と、もう1つは、Webカメラの動画と音声を保存して再生する機能の2つを実装している。

 まずは、Webカメラの画像にフレームを合成して保存する処理を見てみよう(次の画面を参照)。

[開始]ボタンでWebカメラを起動したところ

[開始]ボタンをタッチして、実際にWebカメラが表示されるまでには、多少時間がかかる場合がある。フリーズしているのではないかと勘違いをしないように、気長に待っていただきたい。

[撮る]ボタンでWebカメラからの画像を取得し、[次]/[前]ボタンでフレームを適用して[合成保存]ボタンをタップする
フレームが合成された画像ファイルが「C:\LeapMotionBlitImage」フォルダーに保存される

 次に、Webカメラの画像と音声を同時に録音して再生させる処理を見てみよう(次の画面を参照)。

動画と音声が再生されている

[開始]ボタンをタッチした後は、[撮る]ボタンは使用しない。[録音開始]ボタンをタップする。順番を間違えないように注意してほしい。

 このアプリで扱う各種ファイルは、以下の各フォルダーに格納される。また、これらのフォルダーは自動的に作成される。

  • [撮る]ボタンで撮った画像ファイルは、「C:\LeapMotionImage」フォルダーに保存される
  • フレームを合成した画像ファイルは、「C:\LeapMotionBlitImage」フォルダーに保存される
  • 「動画と音声のファイル」は、「C:\VideoLeapMotion」フォルダーに保存される。

 なお、ファイルの削除機能は搭載していないため、不要なファイルはフォルダーに移動して手動で削除する必要がある。

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

 デフォルトで配置されているGridコントロールをCanvasコントロールに変えておく。Canvasコントロールの方が、座標値を取得しやすいからだ。

写真を撮ってフレームを適用させる

 Borderコントロールを配置し、その子要素としてWebcamコントロールを配置する。ツールボックスから「Webcam」を配置すると、「WebcamControl」という名前空間が追加され、<Webcam>要素の接頭辞にも「WebcamControl:」が追加される。なお、Webcamコントロールの名前は「myWebCam」とする。

 BorderコントロールのBorderThicknessプロパティには「1」を指定し、BorderBrushプロパティには「Black」を指定する。これにより、Webcamコントロールの周囲に黒い枠線が表示される。

 ComboBoxコントロールを2個配置する、名前は「ComboBox1」と「ComboBox2」としている。ComboBox1コントロールには「Webカメラのデバイス名」が表示される。ComboBox2コントロールには、「マイク・デバイスの名前」が表示される。

 筆者は起動時にComboBoxコントロールのSelectedIndexプロパティに「1」を指定することで、カメラのデバイスや、マイクのデバイスを起動時点で指定しているが、初期指定のデバイス以外を指定したい場合は、ComboBoxコントロールのドロップダウンリストに表示されたデバイス名をエンド・ユーザーが選択し直す必要がある。その際、タッチ・ポイントがドロップダウンリストの背後に隠れて選択しづらくなってしまう。そのため、エンド・ユーザー環境のデバイス名が初めから分かっているのであれば、それに該当する「Webカメラのデバイス名」や「マイク・デバイスの名前」が初期選択されるように実装しておいた方がいいだろう。

 または、2つのComboBoxコントロールの値のみ、マウスで選択するという方法も選択肢の1つだ。ただしマウスで選択する場合は、後述するMainWindow_Loadedメソッド内の次の2行は削除しておく必要がある。

Visual Basic
ComboBox1.SelectedIndex = 1
ComboBox2.SelectedIndex = 1
リスト2 2つのComboBoxコントロールの値をマウスで選択する場合に削除するコード

 次に[開始]ボタンとなる「Button1」、[撮る]ボタンとなる「Button2」という名前のButtonコントロールを配置する。[撮る]ボタンは、最初は使用不可になっており、[開始]ボタンでWebカメラが開始されると使用が可能になる。

 [撮る]ボタンで撮った画像が表示される「Image1」という名前のImageコントロールを配置する。Widthプロパティは「640」、Heightプロパティは「480」とする。全く同じ位置に同じサイズの、「ShowArea」というCanvasコントロールを配置する。このCanvasコントロールの上にフレームが表示される。

 次に、フレーム画像を切り替えるための、[前]と表示するButtonコントロール(名前は「prevButton」)と、[次]と表示するButtonコントロール(名前は「nextButton」)を配置する。2つのボタンとも最初の状態では使用不可となっている。

 [合成保存]と表示するButtonコントロール(名前は「gouseiButton」)も配置する。最初の状態では使用不可になっている。

 画像を保存した旨のメッセージを表示する、「TextBlock1」という名前のTextBlockコントロールを配置しておく。

録音再生

 [録音開始]と表示するButtonコントロール(名前は「recordButton」)と、[録音停止]と表示するButtonコントロール(名前は「recordStopButton」)を配置しておく。最初の状態では使用不可としている。

 録音した音声と画像を再生するために、「MediaElement1」という名前のMediaElementコントロールを配置しておく。UnloadedBehaviorプロパティに「Manual」、LoadedBehaviorプロパティに「Manual」と必ず指定しておく。この指定を忘れるとエラーが発生するので注意が必要だ。

 動画と音声を再生する[再生]ボタン(名前は「playButton」)を配置する。最初の状態では使用不可となっている。

 最後に、最前面に「paintCanvas」という名前のInkPresenterコントロールを配置する。

 以上でXAMLデザインは完成だ。書き出されるXAMLコードは次のリスト3のようになる。

XAML
<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WebcamControl="clr-namespace:WebcamControl;assembly=WebcamControl" x:Class="MainWindow"

    Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized">
  <Canvas>
    <Border Canvas.Left="399" Canvas.Top="100" Height="414" Width="520" BorderThickness="1" BorderBrush="Black">
      <WebcamControl:Webcam  Canvas.Left="399" Canvas.Top="100"  x:Name="myWebCam" Height="412" Width="518"/>
    </Border>

    <ComboBox x:Name="ComboBox1" Height="63" Canvas.Left="233" Canvas.Top="19" Width="298" FontSize="18" FontWeight="Bold"/>
    <ComboBox x:Name="ComboBox2" Height="63" Canvas.Left="564" Canvas.Top="19" Width="448" FontSize="18"  FontWeight="Bold"/>
     <Button x:Name="Button1" Content="開始" Height="85" Canvas.Left="1078" Canvas.Top="19" Width="163" FontSize="24" FontWeight="Bold" />
    <Button x:Name="Button2" Content="撮る" Height="85" Canvas.Left="1350" Canvas.Top="19" Width="163" IsEnabled="False" FontSize="24" FontWeight="Bold"/>

    <Image x:Name="Image1" Height="480" Canvas.Left="1088" Canvas.Top="143" Width="640" Stretch="Fill"/>
    <Canvas x:Name="ShowArea" Height="480" Canvas.Left="1088" Canvas.Top="143" Width="640"/>
    <Button x:Name="prevButton" Content="前" Height="66" Canvas.Left="981" Canvas.Top="320" Width="90" FontSize="36" FontWeight="Bold" IsEnabled="False"/>
    <Button x:Name="nextButton" Content="次" Height="66" Canvas.Left="1745" Canvas.Top="320" Width="90" FontSize="36" FontWeight="Bold" IsEnabled="False"/>
    <Button x:Name="gouseiButton" Content="合成保存" Height="103" Canvas.Left="1247" Canvas.Top="647" Width="347" FontSize="72" FontWeight="Bold" IsEnabled="False"/>
    <TextBlock x:Name="TextBlock1" Height="84" Canvas.Left="220" TextWrapping="Wrap"  Canvas.Top="666" Width="900" FontSize="36" FontWeight="Bold" Foreground="Red"/>
    <Button x:Name="recordButton" Content="録音開始" Height="76" Canvas.Left="399" Canvas.Top="547" Width="176" FontSize="36" FontWeight="Bold" IsEnabled="False"/>
    <Button x:Name="recordStopButton" Content="録音停止" Height="76" Canvas.Left="606" Canvas.Top="547" Width="176" FontSize="36" FontWeight="Bold" IsEnabled="False"/>
    <MediaElement x:Name="MediaElement1" Height="194" Canvas.Left="455" Canvas.Top="768" Width="291"  UnloadedBehavior="Manual" LoadedBehavior="Manual"/>
    <Button x:Name="playButton" Content="再生" Height="76" Canvas.Left="798" Canvas.Top="547" Width="121" FontSize="36" FontWeight="Bold" IsEnabled="False"/>
    <InkPresenter x:Name="paintCanvas"/>

  </Canvas>
</Window>
リスト3 書き出されたMainWindow.xamlファイルのコード内容

 レイアウト図は次の画面のようになる。

各コントロールのレイアウト内容

先ほどの手順で以下のコントロールを配置した。いずれもコントロールの名前を表記している。カッコ内は、ボタン上に表示するテキスト内容。

  • 1ComboBox1コントロール。
  • 2ComboBox2コントロール。
  • 3Borderコントロール(無名)の子要素としてmyWebCamコントロール。
  • 4Image1コントロール。
  • 5MediaElement1コントロール。
  • 6Button1(開始)コントロール。
  • 7Button2(撮る)コントロール。
  • 8prevButton(前)コントロール。
  • 9nextButton(次)コントロール。
  • 10recordButton(録音開始)コントロール。
  • 11recordStopButton(録音停止)コントロール。
  • 12playButton(再生)コントロール。
  • 13gouseiButton(合成保存)コントロール。
  • 14最前面にpaintCanvasコントロール。

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

 では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていこう。

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

名前空間の読み込み

 (1)Webカメラやマイクのデバイスを取得するクラスを使うために、「Microsoft.Expression.Encoder.Devices名前空間」を読み込む。次にWebcamコントロールを利用可能にするために、「WebcamControl名前空間」を読み込む。

 (2)Webcamコントロールで撮影した画像ファイルを保存する際に、画像のフォーマットを指定するために必要な、「System.Drawing.Imaging名前空間」を読み込む。

 (3)ファイルや、ディレクトリを作成したりする場合に必要な、「System.IO名前空間」を読み込む。

メンバー変数の宣言

 (4)今回はWin32 APIを使用する(次のリストを参照)。これらのAPIは各種ボタンをタップする場合に必要だ。

Visual Basic
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
Private Const MOUSEEVENTF_LEFTUP = &H4
リスト4 MainWindowクラス内にWin32 APIの宣言を追記

 (5)XML要素を表すXElement型のメンバー変数「Private xmldoc As XElement」を宣言する。

 (6)文字列型のリストである「Private ImageList As New List(Of String)」メンバー変数を宣言する。

 (7)Imageクラス型のメンバー変数「Private myImage As Image」を宣言しておく。

 (8)画像や録音データを保存するメンバー変数「Private ImagePath As String」と「Private videoPath As String」を宣言する。

 (9)Indexを格納するメンバー変数「Private ResultIndex As Integer」を宣言する。

 (10)画像ファイル名を格納するメンバー変数「Private imageFileName As String」を宣言する。

 (11)合成された画像を格納するメンバー変数「Private BiltImagePath As String」を宣言する。

 (12)第1回のリスト3にある「Private Message As String」という行は削除する。

MainWindow_Loadedメソッドの処理

 XElement.LoadメソッドでcolorFrame.xmlファイルを読み込む。Descendantsメソッドで、読み込んだXMLデータにおける全ての子孫要素「<画像名>」の内容を、変数「result」に格納しながら、AddメソッドでImageListオブジェクトに<画像名>要素の内容を追加していく。

 メンバー変数「ImagePath」に、Webカメラで撮影した画像ファイルを保存するディレクトリのパスを指定する。BiltImagePathメンバー変数には、フレームを合成させた画像ファイルを保存するディレクトリのパスを指定する。

 上記の各ファイル・パスのディレクトリが存在しない場合は、Directoryクラス(System.IO名前空間)のCreateDirectoryメソッドで該当するディレクトリを作成する。

 myWebCamコントロールのFrameRateプロパティに「30」、FrameSizeプロパティに「640×480」を指定する。ImageDirectoryプロパティにはメンバー変数「ImagePath」の値を指定する。PictureFormatプロパティには「ImageFormat.Png」(System.Drawing.Imaging名前空間)を指定する。

 EncoderDeviceType列挙体(Microsoft.Expression.Encoder.Devices名前空間)の「Video」(=動画)を引数に指定してEncoderDevicesクラス(Microsoft.Expression.Encoder.Devices名前空間)のFindDevicesメソッド(=システム上にある特定タイプのデバイス群を見付けるためのメソッド)を呼び出し、その戻り値を変数「vidDevice」に格納しておく。同様にEncoderDeviceType.Audio列挙体値(=音声)を指定して呼び出したFindDevicesメソッドの戻り値を、変数「audDevic」に格納する。

 vidDeviceとaudDevideという2つのコレクション型変数内を反復処理して、ビデオ・デバイス名とオーディオ・デバイス名をComboBox1とComboBox2の各コントロールの項目(=Itemsプロパティ)に追加する。

 筆者の環境では、各ComboBoxコントロールのSelectedIndexプロパティに「1」を設定し、初期デバイスを選択する*1

  • *1デバイス数など、エンド・ユーザーの環境に応じてSelectedIndexプロパティに指定する値は変える必要がある。またマウスで選択する場合は前述の方法に従い、この行は削除する。

 Binding型(System.Windows.Data名前空間)の変数「myVideo」を宣言し、「SelectedValue」をコンストラクタ引数に指定してBindingクラスの新しいインスタンスを作成し、myVideo変数に代入する。さらに、myVideo変数のSourceプロパティに「ComboBox1コントロール」を指定する。これによりComboBox1コントロールのSelectedValueプロパティ値(=エンド・ユーザーにより選択されているビデオ・デバイス名)がバインド対象となる。

 同様にBinding型変数の「myAudio」を宣言し、コンストラクタ引数として「SelectedValue」を指定してBinding型の新規インスタンスを作成して代入する。ここではmyAudio変数のSourceプロパティに「ComboBox2コントロール」を指定する。これによりComboBox2コントロールのSelectedValueプロパティ値がバインド対象となる。

 myWebCamコントロールのSetBindingメソッドで、Webcamクラス(WebcamControl名前空間)のVideoDeviceProperyフィールドにmyVideoオブジェクトを指定する。同様にAudioDevicePropertyフィールドにmyAudioオブジェクトを指定する。これで、ビデオ・デバイスにComboBox1コントロール上で選択された値がバインドされ、オーディオ・デバイスにComboBox2コントロール上で選択された値がバインドされることになる。

 次に、動画と音声を保存するディレクトリのパスをメンバー変数「videoPath」に指定する。

 指定したディレクトリが存在しない場合は、DirectoryクラスのCreateDirectoryメソッドで作成する。

 myWebCamコントロールのVideoDirectoryプロパティにvideoPath変数の値を指定し、VidFormatプロパティには動画形式として「VideoFormat.wmv」(WebcamControl名前空間)を指定する。

 最後にこれまでの連載内容と同じように、AddHandlerステートメントを使って、構成ツリーのオブジェクトがレンダリングされる直前に発生するCompositionTarget.RenderingイベントにUpdateイベント・ハンドラーを追加する。

 インク・ストローク(=System.Windows.Ink名前空間のStrokeクラスで表現される、WPF上でのインクの線)の外観を指定するDrawingAttributessクラス(System.Windows.Ink名前空間)のインスタンス「touchIndicatorメンバー変数」のWidthプロパティとHeightプロパティにそれぞれ「15」を指定する。スタイラスの形状を指定するStylusTipプロパティに「Ellipse」を指定して円形とする。これらの指定により、Leap Motionの上で指をかざすと、かざした指の本数に応じて15pxの円が表示される。

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

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

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

  ' colorFrame.xmlファイルの<画像名>要素の値をImageListメンバー変数に追加していく
  For Each result In From c In xmldoc.Descendants("画像名") Select c
    ImageList.Add(result.Value)
  Next

  ImagePath = "C:¥LeapMotionImage"
  BiltImagePath = "C:¥LeapMotionBiltImage"
  If Directory.Exists(ImagePath) = False Then
    Directory.CreateDirectory(ImagePath)
  End If
  If Directory.Exists(BiltImagePath) = False Then
    Directory.CreateDirectory(BiltImagePath)
  End If

  ' Webcamコントロールの各プロパティを設定する
  myWebCam.FrameRate = 30
  myWebCam.FrameSize = New System.Drawing.Size(640, 480)
  myWebCam.ImageDirectory = ImagePath
  myWebCam.PictureFormat = ImageFormat.Png

  ' ビデオデバイス名とオーディオデバイス名をComboBox1とComboBox2に追加する。
  Dim vidDevice = EncoderDevices.FindDevices(EncoderDeviceType.Video)
  Dim audDevice = EncoderDevices.FindDevices(EncoderDeviceType.Audio)
  For Each dvc In vidDevice
    ComboBox1.Items.Add(dvc.Name)
  Next

  For Each avc In audDevice
    ComboBox2.Items.Add(avc.Name)
  Next

  ' マウスで各ComboBoxコントロールの値を選択する場合は、以下の2行は不要なので削除する
  ComboBox1.SelectedIndex = 1
  ComboBox2.SelectedIndex = 1

  ' Binding型変数の「myVideo」を宣言し、コンストラクタ引数に「SelectedValue」を指定したBinding型の新規インスタンスを作成する
  Dim myVideo As Binding = New Binding("SelectedValue")
  myVideo.Source = ComboBox1

  ' Binding型変数の「myAudio」を宣言し、コンストラクタ引数に「SelectedValue」を指定したBinding型の新規インスタンスを作成する
  Dim myAudio As Binding = New Binding("SelectedValue")
  myAudio.Source = ComboBox2

  ' myWebCamコントロールのSetBindingメソッドで、WebcamクラスのVideoDevideProperyフィールドにmyVideoオブジェクトを指定する。同様にAudioDevicePropertyフィールドにmyAudioオブジェクトを指定する
  myWebCam.SetBinding(Webcam.VideoDeviceProperty, myVideo)
  myWebCam.SetBinding(Webcam.AudioDeviceProperty, myAudio)

  ' 動画と音声を設定する
  videoPath = "C:¥VideoLeapMotion"
  If Directory.Exists(videoPath) = False Then
    Directory.CreateDirectory(videoPath)
  End If

  myWebCam.VideoDirectory = videoPath
  myWebCam.VidFormat = VideoFormat.wmv

  ' Updateイベント・ハンドラーを実行する
  AddHandler CompositionTarget.Rendering, AddressOf Update
  touchIndicator.Width = 15
  touchIndicator.Height = 15

  ' Leap Motionの上で指をかざすと、かざした指の本数に応じて15pxの円が表示される
  touchIndicator.StylusTip = StylusTip.Ellipse

End Sub
リスト5 MainWindowが読み込まれたときの処理(MainWindow.xaml.vb)

Updateメソッドの処理

 これについて第1回目と同じ処理の解説になるので割愛する。

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

 これについても第5回の「Updateメソッドの処理(タッチ処理部分)」と同じ説明になるので割愛する。ここでは第5回の「ホバー時の処理(MainWindow.xaml.vb)」と「タッチ時の処理(MainWindow.xaml.vb)」のリストのコードを実装すればよい。

[開始]ボタンがタッチされたときの処理

 [開始]ボタンがタッチされたときの処理を実装するには、Button1コントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 Image1コントロールに画像が表示されていたら消去し、[録音開始]ボタンの使用は不可とする。

 myWebCamコントロールのStartCaptureメソッドを使ってWebカメラの利用を開始する。[撮る]ボタンの使用を可能にする。この際、例外が発生する可能性があるので、Try~Catch~End Tryステートメントで例外処理(具体的にはWebカメラの利用解除)を行っておく。

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

Visual Basic
Private Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click

  Image1.Source = Nothing
  recordButton.IsEnabled = True

  Try
    ' Webカメラの利用を開始する
    myWebCam.StartCapture()
    Button2.IsEnabled = True
  Catch
    myWebCam.StopCapture()
  End Try

End Sub
リスト8 [開始]ボタンがタッチされたときの処理

[撮る]ボタンがタッチされたときの処理

 [撮る]ボタンがタッチされたときの処理を実装するには、Button2コントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 myWebCamコントロールのTakeSnapShotメソッドを呼び出すことで、Webカメラからの映像をスナップショット画像ファイル(PNG形式)として、ImagePathメンバー変数値のフォルダー内に保存する。なお、ファイル名はTakeSnapShotメソッドにより自動的に付けられる。

 スナップショット画像のファイル保存が終了したので、myWebCamコントロールのStopCaptureメソッドを呼び出してWebカメラの利用を停止する。

 DirectoryInfoクラス(System.IO名前空間)のコンストラクター引数にImagePathオブジェクト(=画像や録音データが保存されているフォルダー)を渡して新規インスタンスを生成して、変数「dir」に代入する。先ほどのdir変数のGetFilesメソッドを呼び出して、フォルダー内にある拡張子が「.png」のファイル一覧を取得する。そして、そのファイル一覧の最後にあるファイルの情報をFileInfoクラスの変数「fi」で受けて、下記の処理を行う。

  まず、メンバー変数「imageFileName」に取得したファイル名を格納する。

  次に、Image1コントロールのSourceプロパティに、取得した画像ファイルのBitmapImageオブジェクト(System.Windows.Media.Imaging名前空間)を指定する。これにより、一番新しく保存された画像ファイルがImage1コントロール上に表示されることになる。ファイルが複数存在していても、一番新しいタイムスタンプの画像ファイルのみしか表示されないので、注意していただきたい。

 最後に[撮る]ボタンの使用を不可とし、次のフレームを表示するための[次]ボタンの使用を可能にする。

 以上のコードを実装すると次のようになる。

Visual Basic
Private Sub Button2_Click(sender As Object, e As RoutedEventArgs) Handles Button2.Click

  ' Webカメラからの画像をImagePathメンバー変数値のフォルダーに保存する
  myWebCam.TakeSnapshot()
  myWebCam.StopCapture()
  Dim dir As DirectoryInfo = New DirectoryInfo(ImagePath)
  Dim fi As FileInfo

  ' ImagePath変数値のフォルダー内の、一番新しいPNG画像ファイルを取得して、Image1コントロールに表示する
  Dim files = dir.GetFiles("*.png")
  If files.Count > 0 Then
    fi = files.Last()
    imageFileName = fi.Name
    Image1.Source = New BitmapImage(New Uri(ImagePath & "/" & imageFileName, UriKind.Absolute))
  End If

  Button2.IsEnabled = False
  nextButton.IsEnabled = True

End Sub
リスト9 [撮る]ボタンがタッチされたときの処理

[次]ボタンがタッチされたときの処理

 [次]ボタンがタッチされたときの処理を実装するには、nextButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 Imageクラス(System.Windows.Controls名前空間)の新しいインスタンスでmyImageオブジェクトを作成する。そのWidthプロパティに「640」、Heightプロパティに「480」と指定する。Sourceプロパティに、フレームの画像名を格納しているImageListオブジェクトのコレクションの中から、メンバー変数「Index」の値に該当する「BitmapImageオブジェクト」を指定する。

 ShowAreaコントロール(=Canvasコントロール)にmyImageオブジェクトを追加する。これにより、フレーム画像が表示されることになる。

 メンバー変数「Index」の値が、ImageListコレクション変数が格納している画像の個数より小さい場合は、Index変数の値を1ずつ加算する。この数値を見て、[次]ボタンの有効/無効を切り替える。

 詳しくは下記のコードを参照してほしい。

Visual Basic
Private Sub nextButton_Click(sender As Object, e As RoutedEventArgs) Handles nextButton.Click

  prevButton.IsEnabled = True
  ShowArea.Children.Clear()

  ' 新しいImageオブジェクトを作成して、メンバー変数「Index」に対応するフレーム画像を読み込む
  myImage = New Image
  With myImage
    .Width = 640
    .Height = 480
    .Source = New BitmapImage(New Uri("Images/" & ImageList(Index), UriKind.Relative))
    .Stretch = Stretch.Fill
  End With

  ResultIndex = Index

  ' 読み込んだフレーム画像をShowAreaコントロールに表示する
  ShowArea.Children.Add(myImage)

  ' メンバー変数「Index」の値が、ImageListコレクション変数が格納している画像の個数より小さい場合は、Index変数の値を1ずつ加算する
  If Index >= ImageList.Count - 1 Then
    Index = ImageList.Count - 1
    nextButton.IsEnabled = False
  Else
    Index += 1
  End If

  gouseiButton.IsEnabled = True
  TextBlock1.Text = String.Empty

End Sub
リスト10 [次]ボタンがタッチされたときの処理

[前]ボタンがタッチされたときの処理

 [前]ボタンがタッチされたときの処理を実装するには、prevButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 基本的なコード内容は前述の[次]ボタンと同じだ。異なる点のみ簡単に説明しておく。

 メンバー変数「Index」の値が0より大きい場合は、Index変数の値を1ずつ減算する。この数値を見て、[前]ボタンの有効/無効を切り替える。

 詳しくは下記のコードを参照してほしい。

Visual Basic
Private Sub prevButton_Click(sender As Object, e As RoutedEventArgs) Handles prevButton.Click

  nextButton.IsEnabled = True
  ShowArea.Children.Clear()

  ' 新しいImageオブジェクトを作成して、メンバー変数「Index」に対応するフレーム画像を読み込む。
  myImage = New Image
  With myImage
    .Width = 640
    .Height = 480
    .Source = New BitmapImage(New Uri("Images/" & ImageList(Index), UriKind.Relative))
    .Stretch = Stretch.Fill
  End With
  ResultIndex = Index

  ' 読み込んだフレーム画像をShowAreaコントロールに表示する
  ShowArea.Children.Add(myImage)

  ' メンバー変数Indexの値が、0より大きい場合は、Indexの値を1ずつ減算する
  If Index <= 0 Then
    Index = 0
    prevButton.IsEnabled = False
  Else
    Index -= 1
  End If

  gouseiButton.IsEnabled = True
  TextBlock1.Text = String.Empty

End Sub
リスト11 [前]ボタンがタッチされたときの処理

[合成保存]ボタンがタッチされたときの処理

 [合成保存]ボタンがタッチされたときの処理を実装するには、gouseiButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 Pbgra32フォーマット(=ピクセルあたりビット数(BPP)が32のsRGB形式)にコンバートされた、Webカメラから撮られた画像で初期化された(=コンストラクタ引数に指定された)、WriteableBitmapクラスの新しいインスタンスを代入した変数「myImage1」を宣言する。次に、Pbgra32フォーマットにコンバートされた、フレームの画像で初期化された、WriteableBitmapクラスの新しいインスタンスを代入した変数「myImage2」を宣言する。

 myImage1オブジェクトのBlitメソッドで、そのmyImage1オブジェクトと、メソッドの第2引数として指定されたmyImage2オブジェクトを合成する。カラーには、「Colors.White」と指定する。White以外の色を指定すると、その色で画像にフィルターがかかる。また第5引数には、必ず「Alpha」(=BlendMode列挙体(System.Windows.Media.Imaging.WriteableBitmapExtension名前空間)の値)を指定する。

 Image1コントロールのSourceプロパティに、フレーム画像と合成されたmyImage1オブジェクトを指定する。

 Image1.Sourceプロパティ値で初期化された、WriteableBitmapクラスの新しいインスタンスを代入した変数「bmp」を作成する。PngBitmapEncoderクラス(System.Windows.Media.Imaging名前空間)の新しいインスタンスを代入した変数「myEncoder」を作成する。PngBitmapEncoderクラスは、PNG形式のイメージのエンコードに使用されるエンコーダーを定義するクラスだ。

 myEncoder.Frames.Addメソッドで、エンコーダー内のイメージの1フレームとしてBitmapFrameオブジェクト(System.Windows.Media.Imaging名前空間)を追加する。このBitmapFrameオブジェクトは、先ほどのbmpオブジェクトをBitmapFrameクラスのオブジェクトとして作り直したもの(=具体的には、引数としてbmpオブジェクトを指定して呼び出したBitmapFrame.Createメソッドの戻り値)である。

 myEncoderオブジェクトのSaveメソッドを使って、合成後のファイル名を「Test.png」として、変数「BlitImagePath」の値が示すフォルダー内に保存する。「Test.png」とファイル名を固定しているため、常に上書き保存になる。

 そのほか、画像保存の後処理を行う。具体的なコードは以下のとおり。

Visual Basic
Private Sub gouseiButton_Click(sender As Object, e As RoutedEventArgs) Handles gouseiButton.Click

  ' Webカメラで撮影した画像
  Dim myImage1 As New WriteableBitmap(BitmapFactory.ConvertToPbgra32Format(New BitmapImage(New Uri(ImagePath & "/" & imageFileName, UriKind.Absolute))))

  ' フレームの画像
  Dim myImage2 As New WriteableBitmap(BitmapFactory.ConvertToPbgra32Format(New BitmapImage(New Uri("Images/" & ImageList(ResultIndex), UriKind.Relative))))

  ' myImage1オブジェクトのBlitメソッドでWebカメラから撮られた画像とフレームを合成する
  myImage1.Blit(New Rect With {.Width = 640, .Height = 480, .X = 0, .Y = 0}, myImage2, New Rect With {.Width = 790, .Height = 560, .X = 0, .y = 0}, Colors.White, WriteableBitmapExtensions.BlendMode.Alpha)
  myImage1.Invert()
  myImage.Source = Nothing
  Image1.Source = Nothing
  Image1.Source = myImage1
  Dim bmp As New WriteableBitmap(Image1.Source)

  ' 保存する画像の形式をPNGにする
  Dim myEncoder = New PngBitmapEncoder
  myEncoder.Frames.Add(BitmapFrame.Create(bmp))

  ' BlitImagePath変数値のフォルダーに、合成された画像を「Test.png」ファイルとして保存する
  Using fs As FileStream = File.Open(Path.Combine(BiltImagePath, "Test.png"), FileMode.Create)
    myEncoder.Save(fs)
  End Using

  gouseiButton.IsEnabled = False
  TextBlock1.Text = "合成画像は" & BiltImagePath & "に保存しました。"
  Index = 0
  ShowArea.Children.Clear()
  myImage.Source = Nothing
  Image1.Source = Nothing
  nextButton.IsEnabled = False
  prevButton.IsEnabled = False

End Sub
リスト12 [合成保存]ボタンがタッチされたときの処理

[録音開始]ボタンがタッチされたときの処理

 [録音開始]ボタンがタッチされたときの処理を実装するには、recordButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 myWebCamコントロールのSrartRecordingメソッドを使って録音を開始する。

 実際のコードは下記のとおり。

Visual Basic
Private Sub recordButton_Click(sender As Object, e As RoutedEventArgs) Handles recordButton.Click

  TextBlock1.Text = String.Empty
  recordStopButton.IsEnabled = True
  myWebCam.StartRecording()

End Sub
リスト13 [録音開始]ボタンがタッチされたときの処理

[録音停止]ボタンがタッチされたときの処理

 [録音停止]ボタンがタッチされたときの処理を実装するには、recordStopButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 myWebCamコントロールのStopRecordingメソッドを呼び出すことで、録音が停止され、変数「videoPath」の値として格納していたフォルダー内に「.wmv形式」の動画が音声付きで保存される。なお、画像と同じく、StopRecordingメソッドによりファイル名は自動的に付けられる。

 実際のコードは下記のとおり。

Visual Basic
Private Sub recordStopButton_Click(sender As Object, e As RoutedEventArgs) Handles recordStopButton.Click

  recordStopButton.IsEnabled = False
  recordButton.IsEnabled = True
  playButton.IsEnabled = True
  myWebCam.StopRecording()

End Sub
リスト14 [録音停止]ボタンがタッチされたときの処理

[再生]ボタンがタッチされたときの処理

 [再生]ボタンがタッチされたときの処理を実装するには、playButtonコントロールのClickイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 変数「videoPath」の値として格納されているフォルダー内の「.wmv形式」のファイル・パスを取得して、それをMediaElement1コントロールのSourceプロパティに指定する。

 最後にMediaElement1コントロールのPlayメソッドで再生する。この場合も前述の画像と同じように、一番新しいタイムスタンプの音声付き動画しか再生されない。

 実際のコードは下記のとおり。

Visual Basic
Private Sub playButton_Click(sender As Object, e As RoutedEventArgs) Handles playButton.Click

  MediaElement1.Visibility = Windows.Visibility.Visible
  recordButton.IsEnabled = False
  recordStopButton.IsEnabled = False

  ' .wmvファイルを取得してMediaElement1コンポーネントで再生する。
  ' 一番新しく追加された音声付き動画しか再生されない
  Dim dir As DirectoryInfo = New DirectoryInfo(videoPath)
  Dim fi As FileInfo
  Dim videoFileName As String
  Dim files = dir.GetFiles("*.wmv")
  If files.Count > 0 Then
    fi = files.Last()
    videoFileName = fi.Name
    MediaElement1.Source = New Uri(videoPath & "/" & videoFileName, UriKind.Absolute)
  End If

  MediaElement1.Play()

End Sub
リスト15 [再生]ボタンがタッチされたときの処理

動画が最後まで再生された場合の処理

 動画が最後まで再生された場合の処理を実装するには、MediaElement1コントロールのMediaEndedイベント・ハンドラーを追加して、その中に以下の処理を実装する。

 再生を停止し、MediaElement1コントロールの開始位置を先頭に戻す。[再生]ボタンの使用を可能にする(これにより、再生は何度でも実行できるようになる)。

 [録音開始]ボタンの使用を可能にし、[録音停止]ボタンの使用を不可とする。MediaElement1コントロールを非表示にする。

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

Visual Basic
Private Sub MediaElement1_MediaEnded(sender As Object, e As RoutedEventArgs) Handles MediaElement1.MediaEnded

  MediaElement1.Stop()
  MediaElement1.Position = New TimeSpan(0)
  playButton.IsEnabled = True
  recordButton.IsEnabled = True
  recordStopButton.IsEnabled = False
  MediaElement1.Visibility = Windows.Visibility.Collapsed

End Sub
リスト16 再生が最後まで再生されたときの処理

 今回のサンプルのコードは下記のリンク先よりダウンロードできる*2

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

 今回はこれで終わりだ。ちょっと長めの解説になり、また機能も複数実装したため、少々複雑な内容になってしまった。Leap Motionをお持ちの方は、サンプルをダウンロードして、実際に動かしてみると、どんなふうに動作するかが分かるだろう。今回のアプリはLeap Motionのアプリというより、普通のアプリになってしまった感がある。しかし、マウスでタッチするのではなく、Leap Motionを使って空中でタッチして、カメラの機能が使えるのだから、なかなか面白いアプリに仕上がったのではないかと思う。

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

【お知らせ】筆者によるThinkITでのLeap Motion連載記事のご紹介

  ThinkITでもLeap Motionの記事を書いています。興味のある方は下記のリンク先をご覧ください。

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

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

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

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

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

Leap Motion実用サンプル(Visual Basic編)
6. 【現在、表示中】≫ Leap MotionでWebカメラを操作する

Leap MotionでWebカメラを操作して、撮った写真画像にフレームを合成したり、音声を録音したりできるWPFアプリを作ってみよう。

Leap Motion実用サンプル(Visual Basic編)
7. 3D回転で表示される画像をLeap Motionで切り替える

画像が立体的に回転して表示され、Leap Motionで任意の画像を空中タッチすると、アニメーションを伴ってその画像が大きく表示されるサンプルWPFアプリを作ってみよう。

Leap Motion実用サンプル(Visual Basic編)
8. Leap Motionの軌跡に従って連続した円を描く

Leap Motionで取得した手の動きに合わせて、ランダムな色の円が大きくなりながら連続で描画されるサンプルWPFアプリを作ってみよう。

サイトからのお知らせ

Twitterでつぶやこう!