Kinect

Kinect を使用したマルチモーダルなコミュニケーション

Leland Holmquest

コード サンプルのダウンロード

4 月号 (msdn.microsoft.com/magazine/hh882450) では、オフィス ワーカーの日常業務の支援を目的とした仮想アシスタントの "Lily" を紹介しました。そこでは、Microsoft Kinect for Windows SDK を使用して、ユーザーの意図を Lily が聞き取り、適切に応答できるような、状況に応じた会話を実現する方法について説明しました。

今月は、Kinect デバイスの骨格追跡を利用してジェスチャを使ったユーザー操作を容易にし、自然な UI を実現する手順を取り上げます。また、ジェスチャによって行ったことと、音声コマンドによって指示したことから Lily の出力を決定することによって、2 つの考え方を組み合わせたマルチモーダルなコミュニケーションを示します。これらの 2 つのコミュニケーション モードを組み合わせることで、ユーザーは非常に優れた操作性を得、ユビキタス コンピューティングにまた一歩近付きます。Lily のプレゼンテーションは、Windows Presentation Foundation (WPF) アプリケーションの形式です。

Kinect を初期化する

Kinect デバイスを使用する場合、まず、ランタイムを構築し、さまざまなパラメーターを設定します。図 1 は、今回 Lily に使用する構成を示します。

図 1 Kinect ランタイムの構築

// Initialize Kinect
nui = Runtime.Kinects[0];// new Runtime();
nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |    
  RuntimeOptions.UseDepth | RuntimeOptions.UseColor |
  RuntimeOptions.UseSkeletalTracking);
nuiInitialized = true; nui.SkeletonEngine.TransformSmooth = true;
nui.SkeletonEngine.SmoothParameters = new TransformSmoothParameters
{
  Smoothing = 0.75f,
  Correction = 0.0f,
  Prediction = 0.0f,
  JitterRadius = 0.05f,
  MaxDeviationRadius = 0.04f
};
nui.VideoStream.Open(ImageStreamType.Video, 2, 
  ImageResolution.Resolution640x480, ImageType.Color);
nui.DepthStream.Open(ImageStreamType.Depth, 2,
  ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);

Kinect デバイスの設定時には、たくさんのオプションを使用できます。まず、図 1 のコードの 1 行目を見てください。Kinect for Windows SDK Beta 2 では、ランタイム (Runtime) のコンストラクターは別に用意されています。インデックスを参照する (Runtime.Kinects[0];) ことによって、複数の Kinect ユニットをアプリケーションに簡単に関連付けることができます。今回のアプリケーションでは、使用する Kinect デバイスを 1 台に制限しているため、Runtime は、当然、場所 [0] のものを使用します。必要に応じて、Runtime.Kinects のコレクションを反復処理して、複数の Kinect ユニットを扱うことも可能です。次に、使用予定の機能を Kinect デバイスに指示します。そのためには、希望する機能を Initialize メソッドに渡します。使用できる値は次の 4 つです。

  • UseColor: アプリケーションでカラー画像情報を処理できます。
  • UseDepth: アプリケーションで深度画像情報を利用できます。
  • UseDepthAndPlayerIndex: アプリケーションで深度画像情報と、骨格追跡エンジンが生成するインデックスを利用できます。
  • UseSkeletalTracking: アプリケーションで骨格追跡データを利用できます。

これらの値を渡すことによって、使用予定の Kinect デバイスのサブシステムを API に指示し、複数段階で構成される Runtime のパイプラインの中から適切なパーツを開始することができます。初期化で宣言しなかった機能に、後からアプリケーションでアクセスすることはできないので注意してください。たとえば、RuntimeOptions.UseColor しか選択しないと、後で深度情報が必要になっても使用できません。そのため、ここでは Kinect デバイスのすべての機能を使用するものとして、使用可能な値をすべて渡しています。

ユーザーを追跡する

コードの次の部分を説明する前に、Kinect デバイスで実際に使用できる機能を見ておきます。骨格追跡機能を使用すると、Kinect デバイスではシステムを操作するアクティブなユーザーを最大 2 人まで追跡できます。追跡は、20 個の関節のコレクションを作成し、各関節に ID を関連付けることで実現します。図 2 は、モデル化した関節を示します。

The 20 Joints that Are Modeled in Kinect
図 2 Kinect でモデル化される 20 個の関節

図 3 は、2 人の異なるユーザーから取得する関節の画像です。

Two Active Skeletons
図 3 アクティブな 2 人の骨格

骨格追跡をアクティブにするには、ユーザーの頭部から足先まで Kinect デバイスから見えるようにする必要があります。骨格追跡をアクティブにした後、視野から隠れた関節があると、Kinect デバイスがその部位の補間を試みます。Kinect 対応アプリケーションを作成する場合は、骨格ストリームを監視して Kinect デバイスを操作するだけのシンプルなアプリケーションにすることを強くお勧めします。複数のユーザーが参加できるようにし、ユーザーと Kinect デバイスの間に障害物が入り込むシナリオを設定します。このシナリオは、アプリケーションの配置後に発生する可能性がある状況を模倣します。これで、骨格追跡のしくみ、使用できる機能、および対処しておきたい制限事項について十分に理解していただけたと思います。このテクノロジのすばらしさ、補間機能における創造性についてもすぐにおわかりいただけるようになります。

一部のシナリオ (プロジェクト Lily で表すシナリオなど) では、この補間の速度や補間による頻繁な中断がわずらわしく生産的でないと感じられます。そのため、API は、操作の滑らかさのレベルを制御する機能を公開しています。もう一度、図 1 を見てください。まず、Runtime の SkeletonEngine を使用して TransformSmooth を true に設定しています。これにより、レンダリングされるデータの滑らかさを調整するよう Kinect デバイスに指示します。次に、SmoothParameters を設定します。以下に、TransformSmoothParameters の各要素を簡単に説明します。

  • Correction: 0 ~ 1.0 までの値を使用して、補正の度合いを制御します。既定値は 0.5 です。
  • JitterRadius: ジッター低減の半径を制御します。メートル単位の半径を値として渡します。既定値は 0.05 (5 cm) に設定されています。この半径を超えるジッターは半径に抑えられます。
  • MaxDeviationRadius: 補正位置が元のデータからずれても問題のない、最大半径 (メートル単位) を制御します。既定値は 0.04 です。
  • Prediction: 予測フレーム数を制御します。
  • Smoothing: 滑らかさを 0 ~ 1.0 の範囲で制御します。滑らかさは待機時間に影響するため、特に注意します。滑らかにすればするほど、待機時間が長くなります。既定値は 0.5 です。値を 0 に設定すると、補正していない本来のデータが返されます。

ビデオ ストリームと深度ストリーム

満たすべき要件に応じて、自身のアプリケーションでこれらの設定を試してみることをお勧めします。今回のアプリケーションに必要な最後の手順は、VideoStream と DepthStream を開くことです。これにより、カラー センサー カメラから取り込むビデオ画像と、深度センサー カメラから取り込む深度画像を簡単に表示できます。後で、これをどのように WPF アプリケーションに関連付けるかを示します。

Open メソッドには 4 つのパラメーターが必要です。1 つ目は streamType で、開こうとしているストリームの種類 (Video など) を表します。2 つ目は poolSize で、Runtime でバッファリングするフレームの数を表します。最大値は 4 です。3 つ目は resolution で、目的の画像の解像度を表します。必要に応じて、値として 80x60、640x480、320x240、および 1280x1024 を使用できます。4 つ目のパラメーターは、希望する画像の種類 (Color など) を示します。

Kinect のイベント

Runtime を正常に初期化したら、Runtime から使用できるイベントをアプリケーションに結び付けます。Lily では、ハンドルされる最初の 2 つのイベントを使用して、カラー画像と深度画像を、エンド ユーザーに簡単にグラフィック表示しています。まず、Runtime.VideoFrameReady イベントをハンドルするメソッドを見てみましょう。このイベントは、ImageFrameReadyEventArgs をイベント引数として渡します。Lily でこのイベントをハンドルするのは、nui_VideoFrameReady メソッドです。コードは次のとおりです。

void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
  // Pull out the video frame from the eventargs and
  // load it into our image object.
  PlanarImage image = e.ImageFrame.Image;
  BitmapSource source =
    BitmapSource.Create(image.Width, image.Height, 96, 96,
    PixelFormats.Bgr32, null, image.Bits,
    image.Width * image.BytesPerPixel);
  colorImage.Source = source;
}

Kinect for Windows API が、このメソッドをシンプルにします。ImageFrameReadyEventArgs は ImageFrame.Image を含みます。これを BitmapSource に変換後、この BitmapSource を WPF アプリケーションの Image コントロールに渡します。Kinect デバイスのカラー センサー カメラから取り込むフレームは、このようにしてアプリケーションで表示します (図 3 参照)。

nui_DepthFrameReady でハンドルされる DepthFrameReady イベントも同様ですが、わかりやすく表示するには追加の処理が少し必要です。このメソッドは、先月号と同じ、コードのダウンロード (archive.msdn.microsoft.com/mag201204Kinect、英語) で確認できます。今回このメソッドは作成しませんでしたが、多くの例で使用されているのをオンラインで確認できます。

続いて、興味深いイベント ハンドラーとして、nui_SkeletonFrameReady メソッドについて説明します。このメソッドは、SkeletonFrameReady イベントをハンドルして、SkeletonFrameReadyEventArgs を受け取ります (図 4 参照)。

図 4 nui_SkeletonFrameReady

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
  renderSkeleton(sender, e);
  if (!trackHands)
    return;// If the user doesn't want to use the buttons, simply return.
  if (e.SkeletonFrame.Skeletons.Count() == 0)
    return;// No skeletons, don't bother processing.
  SkeletonFrame skeletonSet = e.SkeletonFrame;
  SkeletonData firstPerson = (from s in skeletonSet.Skeletons
                              where s.TrackingState ==
                              SkeletonTrackingState.Tracked
                              orderby s.UserIndex descending
                              select s).FirstOrDefault();
  if (firstPerson == null)
    return;// If no one is being tracked, no sense in continuing.
  JointsCollection joints = firstPerson.Joints;
  Joint righthand = joints[JointID.HandRight];
  Joint lefthand = joints[JointID.HandLeft];
  // Use the height of the hand to figure out which is being used.
  Joint joinCursorHand = (righthand.Position.Y > lefthand.Position.Y)
    ? righthand
    : lefthand;
  float posX = joinCursorHand.ScaleTo((int)SystemParameters.PrimaryScreenWidth,
    (int)SystemParameters.PrimaryScreenHeight).Position.X;
  float posY = joinCursorHand.ScaleTo((int)SystemParameters.PrimaryScreenWidth,
    (int)SystemParameters.PrimaryScreenHeight).Position.Y;
  Joint scaledCursorJoint = new Joint
  {
    TrackingState = JointTrackingState.Tracked,
    Position = new Microsoft.Research.Kinect.Nui.Vector
    {
      X = posX,
      Y = posY,
      Z = joinCursorHand.Position.Z
    }
  };
  OnButtonLocationChanged(kinectButton, buttons, 
    (int)scaledCursorJoint.Position.X,
    (int)scaledCursorJoint.Position.Y);
}

このアプリケーションに追加する必要があると判断したのは、この図 4 の最初の条件です。アプリケーションで手の動きを追跡することを望まないユーザーには、 trackHands 変数を設定する音声コマンドがあります。この変数は、手の動きを追跡するかどうかを指定します。trackHands を false に設定すると、このメソッドから単純に戻ります。Lily に求める動作ではないのに手の動きを追跡していると、すぐに面倒になって疲れてしまいます。

同様に、骨格追跡を行っていない場合 (ユーザーがいないか、Kinect デバイスの視野外にいる場合)、データの評価を続けても意味がないため、このメソッドから戻ります。ただし、ユーザーを認識し、手の動きを追跡する場合は、引き続き評価を実行します。HoverButton プロジェクト (bit.ly/nUA2RC、英語) にはサンプル コードが付属しています。このメソッドのほとんどは、このサンプル コードの例を基にしています。このメソッドで興味深いのは、ユーザーがどちらの手をより高く上げているかを確認している点です。確認後、高く上げた方の手でボタンを選択する可能性があると想定しています。ボタンがポイントされているかどうかを確認すると、ユーザーの手の位置に応じて、画面上の特定の場所に "手" を表示します。つまり、ユーザーが手を動かすと、グラフィック表示された手も同じように画面上を移動します。これで、ユーザーは自然な方法で操作でき、マウス ケーブルの長さに縛られることはなくなります。ユーザー自身がコントローラーです。

次に、HoverButton の 1 つがクリックされたことを、システムが判断するタイミングに注目します。Lily には合計 8 つのボタンが画面上に存在します。それぞれに on_click イベント ハンドラーが関連付けられています。ここでは、ButtonActionEvaluator、LilyContext、および MultiModalReactions という 3 つの特別なクラスについて説明する必要があります。

ボタンをクリックする操作は、対応するイベントに関連付けられます。ただし、Lily では、この 1 つの操作を受け取ると、高いレベルの意味を持つマルチモーダルなコミュニケーションとして評価するため、対応する音声コマンドに関連付けることができるかどうかを確認します。たとえば、HoverButton の 1 つをクリックすることは、プロジェクトを選択する意図を表します。この情報があれば、システムは、選択されるプロジェクトに関してコンテキストが変化したことのみに対処する必要があります。それ以上の操作は必要ありません。しかし、ユーザーが以前 "プロジェクト計画を開く" 要求を適切に実行できなかった場合、または同じ要求を続けて行う場合、アプリケーションはこの 2 種類の個別のデータをまとめて高いレベルの意味を作成し (2 つの異なる状況下のコミュニケーションによって、このようなマルチモーダルなコミュニケーションが実現されます)、状況に応じて応答する必要があります。これをすべてシームレスに行うため、次の設計を実装しました。

ButtonActionEvaluator クラスをシングルトンとして実装し、INotifyPropertyChanged インターフェイスを実装します。このクラスでは、LilyContext クラス (このクラスもシングルトン) によってハンドルされる PropertyChanged イベントも公開します。次のコードは、一見問題がなさそうなコードですが、少し説明を加える必要があるでしょう。

void buttonActionEvaluator_PropertyChanged(
  object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
  if (MultiModalReactions.ActOnMultiModalInput(
    buttonActionEvaluator.EvaluateCriteria()) == 
    PendingActionResult.ClearPendingAction)
    buttonActionEvaluator.ActionPending = 
      PendingAction.NoneOutstanding;
}

Lily の状態を評価する

まず、上記のコードでは buttonActionEvaluator クラスの EvaluateCriteria メソッドを呼び出しています。このメソッドは単純に、ActionPending プロパティと SelectedButton プロパティによって定義される状態の数値表現を返します。これが、アプリケーションでマルチモーダルなコミュニケーションによる意味を推測する方法の中核部になります。従来のアプリケーションでは、単一のイベントまたはプロパティ (button1.clicked など) の状態を確認することで、必要な操作を評価していました。それに対して、Lily では (マルチモーダルの観点から) 評価する状態が 2 つの異なるプロパティの組み合わせになります。つまり、プロパティにはそれぞれ意味があり、個別の操作を必要としますが、一緒に評価されると、新しく高いレベルの意味を持つようになります。

数値を組み合わせて表現する状態は、MultiModalReactions クラスの ActOnMultiModalInput メソッドに渡されます。このメソッドは、発生する可能性のあるすべての順列をハンドルする、大がかりな switch ステートメントを実装します (上記は説明用に使用した基本的な実装です。今後、Lily を繰り返し使用する場合は、この実装を、全体的なユーザー エクスペリエンスや使いやすさが向上するように、ステート マシンなどより高度な手法に置き換えます)。このメソッドによってユーザーが思いどおりに操作できた場合 (ユーザーがプロジェクト Lily のプロジェクト計画を開くようにシステムに指示するなど)、戻り値の型は PendingActionResult.ClearPendingAction になります。これで、システムのコンテキストはプロジェクト Lily の参照のフレームに保持されますが、キュー内に実行待ちの操作はありません。ユーザーが思いどおりに操作できない場合は、PendingActionResult.LeavePendingActionInPlace を返します。これにより、実行された操作がユーザーが意図したものと異なるため、保留中の操作をクリアしないことがシステムに通知されます。

初回の記事で、指定のドメインまたはコンテキストに固有の文法を作成する方法を説明しました。Kinect ユニットで音声認識エンジンを利用して、文法をユーザーのニーズに合うようにロード/アンロードしながら使用しました。これにより、ユーザーがシナリオどおりの操作にこだわる必要のないアプリケーションを作成しました。ユーザーは希望する方向に移動したり、アプリケーションを再構成する必要なく方向を変えることができます。これにより、自然な方法で、人間とコンピューター アプリケーションとの間の会話が実現されます。

高いレベルの意味

今回は、ユーザーがボタンの上に手をかざすことでボタンを選択するといった物理ジェスチャに、状況に応じた文法に基づく操作を関連付ける方法について説明しました。各イベント (speechDetected および buttonClicked) は、個別に独立してハンドルできます。ただし、これに加えて、この 2 つのイベントはシステムによって相互に関連付けられ、高いレベルの意味を生み出し、それに応じて動作します。

皆さんも、私と同じように Kinect がもたらす機能を楽しんでいただければさいわいです。マイクロソフトは、コンピューターのインターフェイスを人類のために大きな飛躍させた最先端のテクノロジを提供しています。この証拠として、今回 Lily を開発する際、コードの別のコンポーネントやセクションをテストする機会が何度かありました。アプリケーションが完成し、Lily と実際に "会話" できるようになったところで、私は問題に気付き、2 つ目のモニターに切り替えて、ドキュメントなどで原因の追究を始めました。それでも、Lily を引き続き言葉で操作し、タスクの実行だけでなくシャットダウンも指示でききるようになりました。私は、Lily を使用できないと不安になることがわかりました。Lily によって多くの操作を実行できました。簡単な言葉によるコミュニケーションで操作できるため、単純な処理は私の手を離れました。

会話のメカニズムにちょっとした "テクニック" を組み込むことで (たとえば、単語の並びは不規則だが、文脈的および構文的には正しい応答など)、アプリケーションは直感的かつ思いどおりに操作できるようになりました。Kinect では、まさに皆さんの体がコントローラーです。想像力は皆さんをどこにでも連れていってくれます。Kinect の可能性は皆さん次第です。

Leland Holmquest はマイクロソフトに所属するエンタープライズ戦略のコンサルタントです。以前は、Naval Surface Warfare Center Dahlgren に勤務していました。現在は、ジョージ メイソン大学で情報テクノロジの博士号取得に取り組んでいます。

この記事のレビューに協力してくれた技術スタッフの Mark Schwesinger に心より感謝いたします。