F# でKinectプログラミング(はじめのいっぽ)

このたび、諸事情によりKinectセンサーを扱うことになったので勉強がてらブログに貼り付ける。
私はC#をほとんど知らないので、コードはひたすらF#で記述されていく予定です。
なお、Kinect for Windows SDKを使います。


今回ははじめにということで、先人のお力を借りることにしました。

Kinect SDK and F# - a standard library in a non-standard language | .NET Zone

ここに書かれているコードをもとに、RGB情報をそのまま表示させてみます。

必要知識

といっても、上記ページのコード少しの知識でなんとかなる感じになっていました。

そのまま写すのはアレなので、ここではさらに

  • F#とXAMLの連携
  • Functional Reactive Programming(FRP)最初の一歩

を追加しました。単に書くのが面倒になったという噂もありますが…

とりあえずコード全貌

まず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 x:Name="grid" />
</Window>

次にプログラム

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media.Imaging
open Microsoft.Research.Kinect.Nui

type MyKinectApplication(window : Window) =

  let nui = Runtime.Kinects.[0]

  let application = new Application()

  let winImage = new System.Windows.Controls.Image()
  do
    winImage.Height <- 480.0
    winImage.Width <- 640.0
 
  let grid = window.FindName "grid" :?> Grid
  do
    winImage |> grid.Children.Add |> ignore

  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)
           winImage.Source <- source
       end
     |> ignore

  do window.Loaded
     |> Observable.subscribe begin
         fun _ ->
           nui.Initialize(RuntimeOptions.UseColor ||| RuntimeOptions.UseDepth)
           nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
       end
     |> ignore

  do window.Unloaded
     |> Observable.subscribe (fun _ -> nui.Uninitialize() )
     |> ignore

  member this.Run() = window |> application.Run

[<STAThread>]
[<EntryPoint>]
let main _ =
  let window = Application.LoadComponent(new System.Uri("/SampleKinectApplication;component/Window.xaml", System.UriKind.Relative)) :?> Window
  new MyKinectApplication(window) |> fun app -> app.Run()

勢いがあれば30分弱で作れます。

ちょっとした解説

まず参照設定にWPF関連のものを追加します。

Presentation
PresentationFramework
WindowsBase
System.Xaml

さらにKinect for Windows SDKに含まれているライブラリも追加。

Microsoft.Research.Kinect

MyKinectApplicationクラスですが、生成時にWindowオブジェクトを受け取ります。
Kinectランタイムの取得方法は下記のとおり。

let nui = Runtime.Kinects.[0]

KinectDeviceCollectionの先頭要素から取得します。本当はランタイムが存在するかどうかで処理を変えるように

let nui = if Runtime.Kinects.Count = 0 then Some Runtime.Kinects.[0] else None

とやったほうがいいと思いますが、今回はちょっと省略します。
次にImageコントロールの生成とサイズ設定。

let winImage = new System.Windows.Controls.Image()
do
  winImage.Height <- 480.0
  winImage.Width <- 640.0

さらに、Windowから"grid"という名前のGridコントロールを取得しgrid変数に束縛、Imageコントロールをgridの子として追加します。

let grid = window.FindName "grid" :?> Grid
do
  winImage |> grid.Children.Add |> ignore

ここからFRPが登場

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)
         winImage.Source <- source
     end
   |> ignore

VideoFrameReadyイベントが発生したらimageを取得してWPFのImageコントロールに表示。
あとはWindowがloadされたときにランタイムの初期設定を、WindowがunloadされたときにランタイムのUninitializeを行っています。


main関数ではXAMLからコンテンツをロードしてWindowを取得し、MyKinectApplicationを生成してWPFアプリケーションを実行と。
ちょっと汚い感じにクラス化してしまっているので、次回以降はもう少しきれいにします。

動かしてみる

実行すると以下のような感じ。

きちんと表示されると思います。


というわけで、F#でもわりと簡単にKinectプログラミングができます。
ただ、F#+WPFが少し大変だったりするので、基本はViewはC#で作ったほうが楽かもしれません。C#から入ってF#もという方がほとんどで、F#しか知らないようなヒトなんて滅多にいないですしおすし。
というわけで、簡単にですが第1回F# Kinectプログラミングでした。