Kinect

Kinect を使った状況に応じた会話

Leland Holmquest

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

こちらは、私のオフィス アシスタントの Lily です。私たちはよく意見を交わします。Lily は、私の指示で、情報の検索や Microsoft Office ドキュメントの操作といったビジネス上の一般的な仕事を行います。しかしもっと重要なことは、Lily が "仮想" オフィス アシスタントであることです。Lily は、Microsoft Kinect 対応の Windows Presentation Foundation (WPF) アプリケーションで、状況に応じた会話とマルチモーダルなコミュニケーションの手段を進化させるプロジェクトの一部です。

Lily は、ジョージ メイソン大学の大学院での研究の一部として開発しました。このアプリケーションの基本的なコードについて説明する前に、状況に応じた会話とマルチモーダルなコミュニケーションの意味を説明します。

状況に応じた会話とマルチモーダルなコミュニケーション

我々人間は、多様で複雑なコミュニケーション手段を持っています。たとえば、次のようなシナリオで考えてみましょう。赤ちゃんが泣き始めます。母親が見ていることがわかると、床に落ちているクッキーを指差します。母親は、同情の笑みを浮かべ、前かがみになってクッキーを拾い、赤ちゃんに返します。大事な物を返してもらったことに喜び、赤ちゃんは歓喜の声を上げて手をたたき、クッキーをわしづかみにします。

このシーンは、いくつかの事象の簡単な流れを表しています。しかし、この流れを詳しく見て、ここで行われたコミュニケーションを調べてみてください。赤ちゃんも母親もいない 1 つのソフトウェア システムを実装し、システムがこのコミュニケーションを行うと考えます。すると、2 人がどれほど複雑で込み入ったコミュニケーションを行っていたかがすぐにわかります。赤ちゃんの泣き声、歓びの声、手をたたく音を認識する音声処理が必要です。クッキーを指差す赤ちゃんのジェスチャーと、同情の笑み浮かべながらやんわりと諭す母親のジェスチャーを理解する視覚的分析が必要です。このようにごくありふれた動作でも、コンピューターを使って同レベルのエクスペリエンスを実装しなければならなくなると、いかに洗練された動作を行っているかがよくわかります。

ここで、コミュニケーションを少し複雑にしてみましょう。たとえば、次のようなシナリオを考えます。部屋に入ると、数人が会話している最中です。そこで、「cool」(クール) というひと言が耳に入ります。部屋の中の他の人々は、同意を求めるようにあなたを見ています。あなたは何と答えますか。「cool」には多くの意味があります。たとえば、その人は部屋の温度について話していたかもしれません。また、話し手は何かの感想を話していたのかもしれません (「that car is cool (その車はかっこいいね)」)。あるいは、国家間の関係について話していたことも考えられます (「negotiations are beginning to cool (交渉は落ち着き始めている)」)。その 1 つの単語を取り巻く状況がわからなければ、声に出された時点でその単語の意味を理解できる可能性はほとんどありません。意図された意味を把握するには、なんらかのレベルで語義を理解しなくてはなりません。このような考え方が、この記事の中核になっています。

プロジェクト Lily

私は、ジョージ メイソン大学で João Pedro Sousa 博士の指導を受け、"CS895: Software for Context-Aware Multiuser Systems (コンテキスト対応のマルチユーザー システム用ソフトウェア)" プロジェクトの仕上げとして、プロジェクト Lily を作成しました。前述のとおり、Lily は典型的なビジネス オフィスという設定が設けられた仮想アシスタントです。このプロジェクトでは、Kinect デバイスと Kinect for Windows SDK beta 2 を使用しました。Kinect は、カラー センサー カメラ、深度センサー カメラ、4 本のマイクアレイ、および自然な UI を作成するのに使用できる便利な API を提供します。また、Microsoft Kinect for Windows サイト (microsoft.com/en-us/kinectforwindows、英語) と Channel 9 (bit.ly/zD15UR、英語) には、大量の便利な関連サンプルが用意されています。Kinect は、(比較的) 安価なパッケージで、すばらしい機能を開発者にもたらします。これは、Kinect がギネス世界記録の「最も速いペースで売れているコンシューマー デバイス」の記録を破ったことによって実証されています (on.mash.to/hVbZOA、英語)。Kinect の技術仕様 (bit.ly/zZ1PN7、英語) を以下に示します。

  • カラー VGA モーション カメラ: 640 x 480 ピクセル解像度: 30 フレーム/秒 (fps)
  • 深度カメラ: 640 x 480 ピクセル解像度: 30 fps
  • 4 本のマイクアレイ
  • 視野角
    • 水平視野: 57 度
    • 垂直視野: 43 度
    • 物理的なチルト稼働範囲: ± 27 度
    • 深度センサーの範囲: 1.2 ~ 3.5 m
  • 人体トラッキング システム
    • 2 人のアクティブプレイヤーを含む 6 人までの人体を認識
    • アクティブ プレイヤー 1 人に対し 20 の関節をトラッキング
  • 音声入力の認識率を高めるエコー キャンセル システム
  • 音声認識

Kinect のアーキテクチャ

マイクロソフトは、Kinect の基盤となるアーキテクチャに関する情報も公開しています (図 1 参照)。

Kinect for Windows Architecture
図 1 Kinect for Windows アーキテクチャ

図 1 の丸数字の意味は、以下のとおりです。

  1. Kinect ハードウェア: Kinect センサーや USB ハブなどのハードウェア コンポーネント。センサーは USB ハブを通じてコンピューターに接続されます。
  2. Microsoft Kinect ドライバー: Kinect センサー用の Windows 7 ドライバーは、ベータ版の SDK をセットアップするプロセスの一環としてインストールされます。これについては、この後説明します。Microsoft Kinect ドライバー サポート:
    • カーネル モードのオーディオ デバイスとしての Kinect センサーのマイクアレイ。これは、Windows の標準オーディオ API を通じてアクセスできます。
    • イメージ データと深度データのストリーミング。
    • デバイス列挙機能。これにより、コンピューターに接続された複数の Kinect センサーをアプリケーションから使用できます。
  3. NUI API: イメージ センサーからデータを取得し、Kinect デバイスを制御する一連の API。
  4. KinectAudio DMO: Kinect DMO は、Windows 7 のマイクアレイ サポートを拡張して、ビーム形成と音源方向特定の機能を公開します。
  5. Windows 7 の標準 API: Windows 7 のオーディオ、音声、およびメディアの API。これについては Windows 7 SDK および Microsoft Speech SDK (Kinect for Windows SDK beta のプログラミング ガイド) の説明を参照してください。

この記事では、マイクアレイと音声認識エンジン (SRE) を使用して、状況に応じてボキャブラリを作成する方法について説明します。つまり、Kinect は、ユーザーが作り出す状況に応じてボキャブラリを聞き取るようになります。ここでは、アプリケーションがユーザーの動作を監視して、(状況に応じて) 文法を SRE 内外に切り替えるフレームワークを示します。このフレームワークにより、使用する特定のコマンドやパターンを覚えることなく、自然で直感的な方法で Lily と対話する手段がユーザーに提供されます。

SpeechTracker クラス

プロジェクト Lily では、SpeechTracker という独立したクラスを使用して、すべての音声処理に対処しています。SpeechTracker は UI に関係しないスレッドで実行するよう設計しており、応答性が大幅に向上しています。応答性は、このアプリケーションにとって重要な側面です。人の話を聞かないアシスタントでは意味がありません。

SpeechTracker を詳しく説明する前に、いくつか事前に考慮が必要な点があります。まず、アプリケーションが聞き取る必要がある状況 (コンテキスト) を決定します。たとえば、Lily には、データや情報の検索に関連する操作を処理する "Research" コンテキスト、Word 文書や PowerPoint プレゼンテーションを開く操作と、オフィスの設定に関連するその他のタスクを処理する "Office" コンテキスト、Kinect デバイスをユーザーが調整できるようにする "Operations" コンテキスト、およびすべての現実的な状況 (コンテキスト) とは無関係に発生する、すべてのこまごまとした物事 ("今は何時ですか"、"シャットダウン"、またはシステムへのフィードバックなど) を処理する "General" コンテキストがあります。今回の例では、ほかにもいくつかコンテキストを作成していますが、もう 1 つ重要なのは "Context" コンテキストです。このコンテキストを使用して、Lily が属している枠組みを伝えます。つまり、Context 列挙子を使用して、システムが聞き取るコンテキストを決定します。これについては後で詳しく説明します。

その後、各コンテキストを列挙値を使用してモデル化します (図 2 参照)。

Example of Context
図 2 コンテキストの例

モデル化したコンテキストと共に、次に必要になるのは、特定のコンテキスト内でのユーザーの意図を伝達する方法です。これを実現するため、コンテキストごとのホルダーとブール値を含む構造体を使用します。

struct Intention
{
  public Context context;
  public General general;
  public Research research;
  public Office office;
  public Operations kinnyops;
  public Meeting meeting;
  public Translate translate;
  public Entertain entertain;
  public bool contextOnly;
}

このブール値は重要です。伝達する意図が単にコンテキストの変更を行うことだけであれば、contextOnly を true にし、それ以外は false にします。この有用性は後で明らかにしますが、現時点では必要なフラグであると考えておいてください。

ユーザーの意図とは

これでプロジェクト Lily には意図 (Intention) を伝達する機能が備わったので、実行時にどの意図をどのようなタイミングで使うかを知る必要があります。これを行うために、System.Collections.Generic.Dictionary<TKey, TValue> ディクショナリを作成しました。ここで、キーは話される単語やフレーズであり、値はそれに関連付けられた意図です。Microsoft .NET Framework 3.0 からは、オブジェクトを作成してプロパティを初期化するための簡潔な方法があります (図 3 参照)。

The ContextPhrases Dictionary
図 3 ContextPhrases ディクショナリ

このディクショナリは、Context フレーズを定義します。キーはエンド ユーザーが話す単語やフレーズで、意図に関連付けます。1 行で各意図を宣言し、そのプロパティを設定しています。1 つの意図 (たとえば、Research) を複数の単語やフレーズにマップすることができます。これにより、ドメイン固有の言語や目前の課題の語彙をモデル化する、豊富なボキャブラリを作成する機能を実現します (ContextPhrases について 1 つの重要なことがわかります。contextOnly プロパティを true に設定すると、アクティブなコンテキストを変更するためだけにこの操作を使用することを、システムに通知します。これにより、システムは、話された事象の処理をサポートするロジックのサイクルを短くすることができます)。

このことをもっとわかりやすく表すために、図 4 の GeneralPhrases ディクショナリの一部をご覧ください。

The GeneralPhrases Dictionary
図 4 GeneralPhrases ディクショナリ

いくつかの場合において、同じ意図をさまざまなフレーズでモデル化し、豊富で人間的な方法でユーザーとの対話を処理する機能がシステムに提供されているのがわかります。注意が必要な 1 つの制限として、SRE が使用するディクショナリには 300 までしかエントリを含めることができません。したがって、ボキャブラリと文法は注意深くモデル化する必要があります。この制限がなくても、最良のパフォーマンスを得るためには、ディクショナリを可能な限り軽量にしておくことがベスト プラクティスです。

これで、ボキャブラリと意図をマップしたので、ここからがお待ちかねの部分です。まず、システムは SpeechRecognitionEngine へのハンドルを取得する必要があります。

ri = SpeechRecognitionEngine.InstalledRecognizers()
    .Where(r => r.Id ==  RecognizerId).FirstOrDefault();
if (ri == null)
{
  // No RecognizerInfo => bail
  return;
}
sre = new SpeechRecognitionEngine(ri.Id);

ここで、上記のフレーズを取得し、選択肢 (Choices) にします。

// Build our categories of Choices
var contextPhrases = new Choices();
foreach (var phrase in ContextPhrases)
  contextPhrases.Add(phrase.Key);

この操作を、先ほど作成したすべてのフレーズに対して実行します。次に、Choices を GrammarBuilder の Append メソッドに渡します。最後に、Grammar オブジェクトを作成して、SRE にロードします。これを実現するために、新しい Grammar を作成して GrammarBuilder に渡し、目的の文法を表します (図 5 参照)。

図 5 文法を作成して音声認識エンジンをロードする

// And finally create our Grammars
gContext = new Grammar(gbContext);
gGeneral = new Grammar(gbGeneral);
gResearch = new Grammar(gbResearch);
gOffice = new Grammar(gbOffice);
gOperations = new Grammar(gbOperations);
gMeeting = new Grammar(gbMeeting);
gTranslation = new Grammar(gbTranslation);
gEntertain = new Grammar(gbEntertain);
// We're only going to load the Context and General grammars at this point
sre.LoadGrammar(gContext);
sre.LoadGrammar(gGeneral);
allDicts = new List<Dictionary<string, 
    Intention>>() { ContextPhrases,
                    GeneralPhrases,
                    ResearchPhrases,
                    OfficePhrases,
                    OperationsPhrases,
                    MeetingPhrases,
                    TranslationPhrases,
                    EntertainPhrases };

SRE にロードされる Grammar は 2 つ (gContext と gGeneral) のみですが、すべての文法をフレーズの List に追加しています。これが聞き取りのコンテキスト対応の部分を作用させる方法です。General フレーズと Context フレーズは、あらかじめ決められたパターンはなく、時を選ばず話されるため、SRE に継続的に示す必要があります。ただし、Context フレーズを特定すると、追加の文法を 1 つロードします。アプリケーションのこの部分を完成させるには、SRE で SpeechRecognized イベントに対処するだけです。SpeechRecognizedEventArgs 引数を使用して、話された内容を評価します。図 4 を見直すと、引数には contextOnly としてフラグが設定された意図が含まれ、システムが SRE から 3 つ目の Grammar を (これが存在する場合は) アンロードして、新しく特定した Grammar をロードする必要があります。これにより、現在スコープ内にあるコンテキストに基づいて、アプリケーションはさまざまなボキャブラリや語彙を聞き取ることができます。

ディクショナリ (図 4 参照) には、話されるフレーズを表す文字列型のキーと、Intention 型の値 (ユーザーのニーズを満たすためにシステムが使用する、意図している操作を表します) を含めています。ディクショナリの各エントリに追加する実体は、Intention のコンストラクターで、一般的に、コンテキストの関連付け、コンテキストの変化イベントかどうか、および (コンテキストの変化イベントでない場合に) どの操作が意図されているか、という 3 つのコンポーネントから構成されます。

サポートするフレーズのすべてのディクショナリを定義したら、この情報を Kinect ユニットによって提供される SRE に追加します (図 6 参照)。

Speech Recognition Engine
図 6 音声認識エンジン

これにより、聞き取る内容と、情報を聞いたときにその情報をタグ付けする方法が、事実上 SRE に指示されます。ただし、システムをよりインテリジェントで有益にするために、特定の時点に SRE にロードされた 3 つのコンテキストとそれぞれのフレーズ ディクショナリのみをシステムが使用するように制限しています。Context フレーズと General フレーズは、常に、それぞれの基本的な性質に応じてロードされます。3 つ目にロードされるコンテキストとフレーズは、エンド ユーザーとの対話によって決まります。Lily がその環境を聞き取ると、"彼女" はキーワードとフレーズに反応して、フレーズの 1 つのセットを削除して、SRE の別のフレーズに置き換えます。例を示すことで、このことがわかりやすくなります。

動作のしくみ

Lily を最初に起動すると、ContextPhrases と GeneralPhrases が SRE にロードされます。これにより、システムのコンテキストを変化させる、または一般的な操作を機能させるコマンドを、システムが聞き取ることができます。たとえば、初期化完了後に、ユーザーが「What time is it? (今何時ですか)」と聞くと、Lily はこれを "理解" し (これは GeneralPhrases の一部)、現在時刻を応答します。同様に、ユーザーが「I need some information (情報が必要です)」と言うと、Lily は、これが ResearchPhrases を SRE にロードして、Research コンテキストにマップされた意図の聞き取りを開始するためのフラグと理解します。これにより、Lily は次の 3 つの重要な目的を果たすことができます。

  1. 関連する可能性のあるフレーズの最小限のセットのみを聞き取ることで、Lily のパフォーマンスが最大限に高められます。
  2. 特定のコンテキストの語彙のみを許可することにより、さまざまなコンテキストで意味が異なり、あいまいになり得る言語を使用できます (「cool」の例を思い出してください)。
  3. システムはさまざまな異なるフレーズを聞き取り、複数のフレーズを同じ操作にマップできます (たとえば、「Lily, what time is it? (Lily、今は何時ですか)」、「What time is it? (今は何時ですか)」、および「Do you know the time? (今何時かわかりますか)」はすべて、ユーザーに時刻を告げるという同一の操作にマップできます)。これにより、システムとのコンテキスト固有の対話で、豊富な語彙を使用できる可能性が高まります。キーワードやフレーズを 1 対 1 の形式でユーザーに覚えさせ、意図する操作をコマンドにマップするのではなく、デザイナーは、さまざまな一般的な方法をモデル化して、同一のものを示すことができます。これにより、ユーザーは通常のリラックスした状態で言葉を発する柔軟性が得られます。ユビキタス コンピューティングの目標の 1 つは、デバイスを背景に溶け込ませることです。Lily のような状況に応じた会話システムを作成することで、ユーザーはコンピューターを意識しなくなります (つまり、アプリケーションではなく、アシスタントになります)。

必要な知識をすべて備えることで、Lily は聞き取り、状況に応じた操作で応答できるようになります。あとは、KinectAudioSource のインスタンスを作成して、そのパラメーターを指定するだけです。プロジェクト Lily の場合は、すべてのオーディオ処理を SpeechTracker クラス内に追加しています。その後、新しいスレッドで BeginListening メソッドを呼び出して、UI スレッドと分離します。図 7 にそのメソッドを示します。

図 7 KinectAudioSource

private void BeginListening()
{
  kinectSource = new KinectAudioSource());
  kinectSource.SystemMode = SystemMode.OptibeamArrayOnly;
  kinectSource.FeatureMode = true;
  kinectSource.AutomaticGainControl = true;
  kinectSource.MicArrayMode = MicArrayMode.MicArrayAdaptiveBeam;
  var kinectStream = kinectSource.Start();
  sre.SetInputToAudioStream(kinectStream,
       new SpeechAudioFormatInfo(EncodingFormat.Pcm,
       16000,
       16,
       1,
       32000,
       2,
       null));
sre.RecognizeAsync(RecognizeMode.Multiple);
}

組み込んでいる操作に応じて、複数のパラメーターを設定できます。これらのオプションの詳細については、「Kinect for Windows SDK プログラミング ガイド」 (bit.ly/Avrfkd、英語) のドキュメントを確認してください。次に、この WPF アプリケーションを SpeechTracker SpeechDetected イベントに登録します。これは実際には SRE の SpeechRecognized イベントのパススルーですが、意図をイベント引数の一部として使用しています。SRE が、ロードしたコンテキスト フレーズのいずれかと一致していることを検出すると、SpeechRecognized イベントが発生します。SpeechTracker はそのイベントを処理して、意図がコンテキストの変化を表しているかどうかを評価します。コンテキストの変化を表している場合、SpeechTracker は Grammar を適切にロード/アンロードして、SpeechContextChanged イベントを発生させます。それ以外の場合は、SpeechDetected イベントを発生させて、そのイベントを監視しているすべてのインスタンスがこのイベントを処理できるようにします。

Confidence プロパティ

注意しておく点が 1 つあります。私がオンラインで見つけた例では、SpeechRecognizedEventArgs の Confidence プロパティは信頼性が低いので、使用しないように記載されていました (SDK ドキュメントとは正反対です)。Confidence プロパティを使用しないと、何も話していないのに SpeechRecognized イベントがほぼひっきりなしに発生しました。したがって、SpeechRecognized イベント ハンドラーでは、まず、Confidence を確認しています。少なくとも 95% の確信がなければ、結果を無視します (95 という数値は、単純に試行錯誤の結果導き出しました。有効な値を分析したわけではありません。95% なら、期待していたレベルの結果がもたらされるように思われます。実際に、SDK では、この値をケース バイ ケースでテストおよび評価するよう推奨しています)。Confidence を確認する場合、誤検知は常に 0 になりました。そのため、オンラインで見つけたステートメントやソリューションは注意深くテストすることをお勧めします。私の経験では、SDK ドキュメント (および Kinect for Windows サイトでマイクロソフトが提供しているサンプル) は、非常に有益で正確です。

よく、Kinect では音声認識トレーニングがどのくらい必要かとたずねられます。経験では、それほどトレーニングは必要ありません。Confidence 値を設定したら、トレーニングや調整などを行わなくても、Kinect は完璧に機能しました。私は自分を主なテスト対象にしましたが、7 歳と 8 歳の娘たちもテスト対象に含めました (Kenzy と Shahrazad に感謝します)。娘たちは、父親のコンピューターに何をするかを話し、コンピューターがそれを認識して動作することに興奮していました。娘たちはかなり自信を持ち、父親としてはかなり報われました。

人間のアシスタントのように錯覚させる

システムが環境から観察できる内容に基づいて状況を切り替えられれば、ユーザー (人間) と Lily (コンピューター) の間に豊富な対話が生み出されます。アシスタントがいるという錯覚をさらに強めるために、ちょっとした機能をたくさん追加しました。たとえば、我々人間が互いに話すときは、同じフレーズを何度も繰り返す必要はありません。したがって、"pulse" (繰り返し) の意図を処理する場合、システムはランダムなフレーズを選択してユーザーに答えます。つまり、ユーザーが「Lily?」と問いかけると、Lily は「はい」、「ここにいます」、「何でしょう」などといくつかのフレーズをランダムに返します。

今回はここからさらに 1 歩踏み込みました。いくつかのフレーズに、ユーザーの名前や性別固有代名詞のためのホルダーを含めました。これらのフレーズのうちの 1 つがランダムに選択されると、名前と代名詞のどちらを使用するかをランダムに決定します。これにより、まったく同一にならない会話が生み出されます。このようなちょっとした工夫は取るに足らない、ほとんど努力する価値もないように思えますが、システムと対話するときに人間が経験する応答に注目すると、我々人間がコミュニケーションを取る際のユニークさは、このようなちょっとした工夫によって成り立っていることがわかります。私は、Lily を他のプログラムとは違うと考えていることに気付きました。テストしてデバッグするときは、仕様やコードを確認する必要があります。そのため、これらの確認を始めたときに、Lily に絶えず話しかけて、"彼女" にシャットダウンするように指示したりしていました。しばらくして、Lily が停止しているときに、"人間的対話" がないことを寂しく思っていることに気が付きました。これは、Kinect が自然な UI というまったく新しい時代をもたらしたことを最も良く表す証拠と考えられます。

これを確認するために、図 8 にユーザーの言葉によるコマンドを聞き取って動作するシステムを作成するために必要なオブジェクトの流れを示します。

Creating a System that Listens and Acts on Verbal Commands
図 8 言葉によるコマンドを聞き取って動作するシステムを作成する

次回は、Kinect の深度トラッキングと人体トラッキングの機能と、音声認識機能を連結して、マルチモーダルなコミュニケーションを実現する方法について説明します。マルチモーダルなコミュニケーション コンポーネント内では、コンテキストの重要性がさらに明らかになります。体の動きを音声コマンドにリンクして、人間のコミュニケーション範囲全体をシステムに評価させる方法についても説明します。

Leland Holmquest はマイクロソフトの社員です。以前は、Naval Surface Warfare Center Dahlgren に勤務していました。現在は、ジョージ メイソン大学で情報テクノロジの博士号取得に取り組んでいます。

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