KINECT SDKのサンプル「SkeletalViewer」をF# 化する

このエントリはF# Advent Calendar 2011 - PARTAKEKINECT SDK Advent Calendar 2011 : ATNDの12月8日分になります。


今回はKINECT+XNA+WPF連携…を紹介したかったのですが、作成途中で間に合わないと判断したためSkeletalViewerを題材にしたいと思います。

SkeletalViewerのF#化

KINECT SDKをインストールすると、サンプルがいくつかついてきます。そして、Samples.zip内にはC++C#のサンプルコードがいくつか入っています。

今回はサンプルの一つであるSkeletalViewerをF#に書き換えます。

今回必要な環境
  • F#プロジェクトがビルドできる環境
  • KINECT SDK
  • Reactive Extensions(Rx)
  • 1〜2台のKINECTセンサー
ソースコード

F#化したソースコードGitHubにあげておきました。
https://github.com/pocketberserker/FsSkeletalViewer
比較してみたい方はSDKをインストールするなどしてC#C++コードを入手してください。

注意事項
  • 複数台では試していません
  • サンプルコードをほぼそのまま移植した形なので、きれいなF#コードではありません
  • IconはGitHubにあげていないので、自分でicoファイルを用意してください

簡易解説

かいつまんで解説します(都合上インデントを変更したりしています)。

XAML

ほぼそのままです。
ただし、C#サンプルのようなUserControlを継承したControlクラスをXAMLで呼び出す方法ではなく、XAMLからUserControlをロードして機能を追加する形にしています。

KINECTランタイムの保持

Viewer系クラスでは接続されているKINECTランタイムを保持しておくわけですが、C#ではnullを代入したりインスタンスを代入したりしています。F#ではnullをあまり使いたくなかったので、代わりにOptionを使いました。

KinectDepthViewer

C#コードでのConvertDepthFrameメソッドには以下の記述があります。

byte[] convertDepthFrame(byte[] depthFrame16)
{
    bool hasPlayerData = RuntimeOptions.HasFlag(RuntimeOptions.UseDepthAndPlayerIndex);
    for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < depthFrame32.Length; i16 += 2, i32 += 4)
    {
        int player = hasPlayerData ? depthFrame16[i16] & 0x07 : -1;
        int realDepth = 0;

        if (hasPlayerData) realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
        else realDepth = (depthFrame16[i16 + 1] << 8) | (depthFrame16[i16]);

        //以下略

F#ではconvertDepthFrame関数内にconvert再帰関数を作って表現しました。

let convertDepthFrame (depthFrame16:byte array) =

  let hasPlayerData = runtimeOptions.HasFlag(RuntimeOptions.UseDepthAndPlayerIndex)

  let rec convert (i16:int) (i32:int) =
    if i16 < depthFrame16.Length && i32 < depthFrame32.Length then
      let player = if hasPlayerData then byte <| depthFrame16.[i16] &&& 0x07uy else byte <| -1
      let realDepth =
        if hasPlayerData then (depthFrame16.[i16 + 1] <<< 5) ||| (depthFrame16.[i16] >>> 3)
        else (depthFrame16.[i16 + 1] <<< 8) ||| (depthFrame16.[i16])

      (* 中略 *)

      convert (i16 + 2) (i32 + 4)

  convert 0 0
  depthFrame32

また、F#ではif"式"であり値が返るため、realDepthはmutableでなくても問題ありません。
F#は暗黙的型変換が行われないので、byte型が必要な部分ではbyteリテラルを使用しました。

KinectDiagnosticViewer

getDisplayPositionでは、Runtime.SkeletonEngine.SkeletonToDepthImageの第2、第3引数がout引数なのでmutableな変数を参照渡しました。
getBodySegmentの第3引数idsにおいては、C#では可変長引数を使っています。しかし、F#には可変長引数がないので、リストで代用しています。

MainWindow

StatusChangedに登録する処理ははDispatcherSynchronizationContextとObservable.ObserveOnを使って登録しました。
あと、ItemsControl.Itemsで得られるコレクションにはGetEnumeratorメソッド存在が存在しないと怒られたためfor文に使用できず、苦肉の策として範囲式を使っています。
さてここで、C#コードのgetViewerを見てみましょう。

KinectDiagnosticViewer GetViewer(object sender)
{
    while (sender != null && sender is FrameworkElement)
    {
        sender = ((FrameworkElement) sender).Parent;
        if (sender is KinectDiagnosticViewer)
        {
            return sender as KinectDiagnosticViewer;
        }
    }
    return null;
}

引数のsenderに再代入するという香ばしいコードが書かれていました。変数名をつけづらかったのかもしれませんが、この再代入は怖いですね。F#にはシャドウイングがあるのでわりと安心です。

let getViewer (sender:obj) =
  let rec getViewer' (sender:obj) =
    if sender <> null && sender :? FrameworkElement then
      let sender = box (sender :?> FrameworkElement).Parent // シャドウイング
      if sender :? UserControl then kinectViewerDictionary |> List.find (fun (c,viewer) -> obj.ReferenceEquals(c,sender)) |> snd else getViewer' sender
    else Unchecked.defaultof<KinectDiagnosticViewer>

  getViewer' sender

実行結果

実行結果は以下のようになります(本来左側にはRGB映像が表示されるのですが、諸般の都合により塗りつぶしました)。

KINECTを抜くと画面がきちんと更新されます。

こんな風に、F#でもC#と同様のものが出来上がります。

改善したいところ

手続き的な処理の多い関数をある程度小さい関数に分割したいところです。あとは、まだまだ簡潔に記述できそうな箇所がいくつかあるので、そのあたりは改善したいですね。

というわけで

今回はC#のサンプルコードをF#化してみましたが、いかがでしたか?
多少強引な箇所もありますが、F#でのKINECTプログラミングを感じていただけたのではないでしょうか。