はじめてのF# de KINECTプログラミング #kinectsdk_ac
このエントリはKINECT SDK Advent Calendar 2011 : ATNDの12月2日分です
F#とは
F#はMicrosoft社製のマルチパラダイム言語であり、OCamlという関数型言語に強い影響を受けています。.NET Framework上で動作するため、C#やVBとの相互やり取りが簡単に行えます。
C#やVBとのやり取りができるということは、つまり.NETの資産を有効利用できることを意味しています。これは、もちろんKINECT for Windows SDKにもあてはまります。つまり、F#もKinectプログラミングが可能なのです!
では早速F#でプログラミング…といきたいところですが、何も知らない状態でいきなりスタートするのは少々厳しいものがあります。
今回は、下記エントリとその過去記事を併用してみてください。
手軽なスクリプト言語としてのF# その15「WPFしてみた」 - かずきのBlog@hatena
また、本格的にF#を勉強してみたくなった方は、以下の書籍で勉強してみることをお勧めします。
- 作者: 荒井省三:いげ太
- 出版社/メーカー: 技術評論社
- 発売日: 2011/01/07
- メディア: 大型本
- 購入: 6人 クリック: 264回
- この商品を含むブログ (26件) を見る
ただし、今回のコードでは上記エントリや書籍では触れられていない内容についても触っているので、簡単に補足説明をいれていくことにします。
実行環境
今回は以下の環境を使いました。
- Window 7 64bit
- Visual Studio 2010 Professional
- KINECT for Windows SDK Beta2
KINECT for Windows SDK Beta2のインストールに関しては、KINECT SDK Adbent Calendar 2011/12/1 @kaorun55さんの記事を参照して下さい。
Windows で KINECT + KINECT for Windows SDK Beta2 の環境を作成する #kinectsdk_ac - かおるんダイアリー
F#のコンパイラとインタラクティブ シェルを無償で単独インストールしたい方は、下記エントリ内の「インストール」の項目を参考にしてみてください。
注意事項
今回はWPFを利用してGUIを作成しますが、IDEサポートがC#に比べると弱く、また、特にXAMLを使用した場合は、C#でのWPF連携ではできていたことがF#ではできない場合もあります。なので一般的にはC#でView部を、F#でロジック部を記述することがほとんどなのですが、今回は"あえて"すべてF#で記述していきます。
プログラムの作成
KINECTプログラミングの初回なので、KINECTから取得した画像の色情報を表示する簡単なプログラムを作成したいと思います。
今回作成したコードはGitHubに置いてあるので、先に試してみたいかたはダウンロードしてみてください。
pocketberserker/FsSampleKinectApplication · GitHub
プロジェクトの作成と設定
まず、F#のコンソールアプリケーションのプロジェクトを作成します。ここでは「FsSampleKinectApplication」にしました。
次に、プロジェクトのプロパティを開きます。"出力の種類"がコンソールアプリケーションという設定になっているので、これをWindowsアプリケーションに変更します。
そして、参照設定を追加します。今回は以下の形になりました。
XAMLを記述する
プロジェクトを右クリックし、追加から新しい項目を選びます。テンプレートはXMLファイルを選択し、MainWindow.xmlとして作成します。ファイル拡張子がxmlではないことに注意してください。
生成されたファイルをソリューションエクスプローラでクリックすると、プロパティウィンドウにプロパティ一覧が表示されると思います。その中に"ビルド アクション"という項目があるので、ビルド アクションを「Resource」にしてください。これにより、MainWindow.xamlがプロジェクト アセンブリに埋め込まれるようになり、XAMLファイルの読み込みが行いやすくなります。
XAMLは次のような形にしました。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MyKinectApplication" Height="600" Width="800"> <Grid> <Image Name="kinectImage" Height="480" Width="640" /> </Grid> </Window>
ウィンドウを表示する
プロジェクトに新しい項目としてMainWindow.fsを作成します。そして、以下のコードを記述します。
module FsSampleKinectApplication open System open System.Windows type MainWindow() = let window = Application.LoadComponent(new System.Uri("/FsSampleKinectApplication;component/MainWindow.xaml", System.UriKind.Relative)) :?> Window member this.Window = window [<STAThread>] (new MainWindow()).Window |> (new Application()).Run |> ignore
1行目はモジュールの宣言、3,4行目は今回のコードに必要な名前空間やモジュールのインポートを行っています。
6行目はクラスの宣言であり、クラス名がMainWindow、引数のないコンストラクタが存在することがわかります。なお、C#ではIDEがクラスを自動生成してくれますが、F#では自動生成されません。さらにいうと、F#でWPFのコンポーネントを継承したクラスを記述しXAMLでそのクラスを参照するのは、やれなくはないと思いますがわりと面倒です。なので、私の場合はコンポーネントを所持するクラスを作ることで代替しています。
8,9行目でXAMLからのコンポーネントのロードとwindow変数への束縛を行っています。Uriの第1引数にxamlファイルへのパスを、第2引数で第1引数が相対パスであるであることを指定しています。今回は、XAMLファイルをビルド アクションをResourceにすることでプロジェクト アセンブリに埋め込まれるようにしているので、パスの記述形式は"/アセンブリ名;component/ファイル名"となります。また、Application.LoadComponentで取得できるオブジェクトはobj型なので、":?>"を使ってWindow型にダウンキャストします。なお、ダウンキャストに失敗すると例外が発生するので、確実にダウンキャストが成功すると保障できるとき以外は、事前に型テストを行ったほうがよいです。今回は確実に成功するので型テストは省略しています。
11行目はWindowというプロパティを定義しています。ここまでがMainWindowクラスの中身になります。
最後にApplicationの実行なのですが、PWFアプリケーションはSingle Thread Apartment(STA) Threadで実行される必要があります。なので、13行目でSTAThread属性をつけています。
ここで、"|>"という見慣れない演算子がでてきています。この演算子は「関数の最後の引数を前にもぅてくる」演算子です。この演算子を使うことで、データに対する処理の流れが追いやすくなります。
ここでアプリケーションを実行してみると、真っ白な画面のアプリケーションが起動すると思います。Windowしか表示していないので当たり前といえば当たり前です。
Kinectランタイムを取得する
クラス内に以下の一行を加えます。なお、letはmemberよりも上部に書かないとコンパイラに怒られるので注意してください。F#では書く順序やインデントも重要なのです。
let nui = Runtime.Kinects.[0]
Runtime.Kinectsというコレクションの1つ目の要素からRuntimeオブジェクトを取得し、nui変数に束縛しています。F#では
コレクション名.[添字]
という形でコレクションから要素を取得できます。
オブジェクトを取得しただけで何も操作していないので、まだ画面は白いままです。
WindowのLoad、Unload時にRuntimeの設定を行う
WindowのLoadやUnloadにあわせてRuntimeの設定を行いたいですよね?
今回はFRP(Functional Reactive Programming)のような形で処理を行ってみましょう。FRPについては、書き始めると長くなるのでここでは触れないことにします。
さて、それでは以下のコードをnui変数とmemberの間に記述しましょう。
do window.Loaded |> Observable.subscribe begin fun _ -> nui.Initialize(RuntimeOptions.UseColor) nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color) end |> ignore do window.Unloaded |> Observable.subscribe (fun _ -> nui.Uninitialize() ) |> ignore
window.Loadedの発生をObservable.subscribeで釣り上げ、引数に指定された関数を実行します。ここではfun式を使って関数を記述しています*1。この関数ではまず、nui.InitializeでRuntimeの初期化を行っています。引数にはOptionが設定でき、今回はカラー映像を取得したいのでUseColorをオプションに指定します。次にVideoStreamをOpenします。これでKINECTから映像が順次取得できるようになります。なお、Observable.subscribeの戻り値はIDisposableであり、戻り値を変数に束縛してDisposeすると、このイベントを解除できます。今回は解除しないのでignore関数を使って戻り値を破棄しています。
window.UnloadではUninitializeメソッドを実行して、Runtimeを初期化前の状態に戻しています。こちらもObservable.subscribeの戻り値は破棄しています。
描画用コンポーネントの取得
Runtimeの設定は終わりましたが、肝心の描画用コンポーネントを用意できていません。クラス内に用意しましょう。
let kinectImage = window.FindName "kinectImage" :?> Image
ImageコンポーネントをkinectImage変数に束縛させます。このコンポーネントにKINECTから取得した映像を描画することになります。
次にwindowからFindNameメソッドを使ってGridを取得し、grid変数に束縛させます。Gridは登録されているコンポーネントの位置を、規則にあわせて簡単に指定することができるものです。実際、do 以下ではgridにimageを追加しています。
Imageを取得してコンポーネントに表示してもらう
まだ画面は白いままです。足りないものはなんでしょうか。
そう、カメラから実際に映像を取得してImageコンポーネントに渡す処理がありませんね。追加しましょう。
do nui.VideoFrameReady |> Observable.subscribe begin fun args -> let image = args.ImageFrame.Image let source = BitmapSource.Create(image.Width, image.Height, 96.0, 96.0, Media.PixelFormats.Bgr32, null, image.Bits, image.Width * image.BytesPerPixel) kinectImage.Source <- source end |> ignore
VideoFrameReadyが発生したら、渡されたデータからImage(映像)を作成し、そのデータをもとにBitmapSourceを作成します。そしてBitmapSourceをImageコンポーネントに渡すことで映像を表示してもらいます。
*1:F#では()のかわりにbegin/endが使えます … http://d.hatena.ne.jp/mzp/20101212/fsharp