Kinectの音声認識を使って、プレイヤーを分離、結合させるデモを試してみる

2012年9月14日(金)
薬師寺 国安

※前ページからの続きです。

ウィンドウが読み込まれた時の処理

最初は1人が表示されるようにspeechNoを1で初期化しています。

ピクセル深度の値を2000(2m)で初期化しておきます。よって、Kinectセンサーより2mの距離を取ってお試しください。Kinectセンサーを取得し、ビットマップデータを初期化し、プレイヤーの画層を初期化するinit_kinectプロシージャを実行します。

構成ツリーのオブジェクトがレンダリングされる直前に発生する、CompositionTarget.Renderingイベントにイベントハンドラを指定します。イベントハンドラ内では以下の処理を行います。

OpenNextFrame(100)メソッドで、KinectからRGBデータの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。同様に、Kinectから深度データの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。スケルトンフレームに関しても同じ処理を行います。
分身の数を保持する変数maxPlayerにspeechNoの値を代入します。
背景を描画し距離データを取得するRenderScreenプロシージャを実行します。引数として、Kinectのストリーミング用RGBデータのバッファと深度データのバッファを含んでいる、colorFrameとdepthFrameを渡しています。

RGBデータのバッファ、深度データのバッファ、スケルトンデータのバッファを引数に、SaveBufferプロシージャを実行します。

DataContextプロパティにMainWindow自身のインスタンスを指定します。この処理を行わないとプレイヤーが表示されませんので注意してください。

Choicesクラスは、要素を構成するための代替項目の一覧を表すクラスで、GrammarBuilder オブジェクトからのみ直接使用されます。認識させる言葉をAddメソッドで登録します。

GrammarBuilderクラスは、単純な入力から複雑な Grammar(構文情報を取得管理するクラス)を構築するためのメカニズムを提供するクラスで、登録された言葉の構文(文法)設定を行い、SpeechRecognitionEngineへと設定します。Appendメソッドで、登録した言葉を GrammarBuilder オブジェクトとして現在の GrammarBuilder に追加します。

文法のチェックされた言葉(builder)で初期化された、新しいGrammerクラスのインスタンス、myGrammerオブジェクトを作成します。Grammerクラスは、構文情報を取得および管理するためにランタイムをサポートするクラスです。

次に、SpeechRecognitionEngineクラスの新しいインスタンスengineオブジェクトを作成します。

SpeechRecognitionEngineクラスのLoadGrammerメソッドで、Grammar によって指定された通りに、特定の構文を同期的に読み込みます。

Kinectの音声インターフェースは、Kinect.AudioSourceで提供されます。Startメソッドで音声入力を開始します。入力ストリームを取得し、SpeechRecognitionEngine クラスのSetInputToDefaultAudioDeviceメソッドで、SpeechRecognitionEngine の現在のインスタンスに、システム既定のオーディオ入力を割り当てます。

認識操作の後に、RecognizeAsync によって開始された認識を終了しないよう、RecognizeMode.Multipleを指定して、RecognizeAsyncメソッドで非同期音声認識を開始します。

言葉が認識された際には、AddHandlerステートメントで言葉を認識した際に発生するSpeechRecognizedイベントに、イベントハンドラを指定します。認識された音声(speechArgs.Result.Text)を変数wordsに格納します。
Confidenceプロパティで音声認識の信頼度を設定します。-1が低、0が標準、1が高信頼度となります。-1を指定するとどんな言葉でも反応する恐れがあります。1を指定するとなかなか認識してくれません。今回は信頼度が0.3より大きい場合に設定しています。

wordsの内容で条件分岐を行いますwordsの値が「おわり」の場合はKinectセンサーの動作を停止し、音声認識も停止し、Environment.Exit(0)でプログラムを終了します。

「分身」の場合は、OpenNextFrame(100)メソッドで、KinectからRGBデータの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。同様に、Kinectから深度データの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。
スケルトンフレームに関しても同じ処理を行います。speechNoの値が6より大きい場合、分身が6名になった場合は警告メッセージを表示します。それ以外は、speechNoの値を1ずつ加算し、背景を描画し距離データを取得するRenderScreenプロシージャを実行します。
引数として、Kinectのストリーミング用RGBデータのバッファと深度データのバッファを含んでいる、colorFrameとdepthFrameを渡しています。RGBデータのバッファ、深度データのバッファ、スケルトンデータのバッファを引数に、SaveBufferプロシージャを実行します。

「統合」の場合は、OpenNextFrame(100)メソッドで、KinectからRGBデータの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。同様に、Kinectから深度データの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。スケルトンフレームに関しても同じ処理を行います。
speechNoを1で初期化し、プレイヤーの表示を1人だけとします。背景を描画し距離データを取得するRenderScreenプロシージャを実行します。引数として、Kinectのストリーミング用RGBデータのバッファと深度データのバッファを含んでいる、colorFrameとdepthFrameを渡しています。RGBデータのバッファ、深度データのバッファ、スケルトンデータのバッファを引数に、SaveBufferプロシージャを実行します。

  Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    speechNo = 1
    targetDepth = 2000
    init_kinect()
 
    AddHandler CompositionTarget.Rendering, Sub(renderSender As Object, renderArgs As EventArgs)
      Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100)
        Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100)
          Using mySkeletonFrame As SkeletonFrame = Kinect.SkeletonStream.OpenNextFrame(100)
            maxPlayer = speechNo
            RenderScreen(colorFrame, depthFrame)
            SaveBuffer(colorFrame, depthFrame, mySkeletonFrame)
          End Using
 
        End Using
        End Using
          End Sub
    DataContext = Me
 
    Dim sentence As Choices = New Choices
  
    With sentence
      .Add("分身")
      .Add("統合")
      .Add("おわり")
    End With
 
    Dim builder As GrammarBuilder = New GrammarBuilder
    builder.Append(sentence)
    Dim myGrammer As Grammar = New Grammar(builder)
    engine = New SpeechRecognitionEngine()
    engine.LoadGrammar(myGrammer)

 
      AddHandler engine.SpeechRecognized, Sub(speechSender As Object, speechArgs As SpeechRecognizedEventArgs)
        Dim myConfidence As Single = speechArgs.Result.Confidence
        If myConfidence > 0.3 Then
          words = speechArgs.Result.Text
            Select Case words
              Case "おわり"
                If Kinect Is Nothing = False Then
                  If Kinect.IsRunning = True Then
                    Kinect.Stop()
                    Kinect.AudioSource.Stop()
                    engine.RecognizeAsyncStop()
                  End If
                    Environment.Exit(0)
                End If
              Case "分身"
                Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100)
                  Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100)
                    Using mySkeletonFrame As SkeletonFrame = Kinect.SkeletonStream.OpenNextFrame(100)
                      If speechNo >= 6 Then
                        TextBlock1.Text = "これ以上は分身できません。"
                        Exit Sub
                      Else
                        speechNo = speechNo + 1
                      End If
                        RenderScreen(colorFrame, depthFrame)
                        SaveBuffer(colorFrame, depthFrame, mySkeletonFrame)
                    End Using
                  End Using
                End Using
              Case "統合"
                Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100)
                  Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100)
                    Using mySkeletonFrame As SkeletonFrame = Kinect.SkeletonStream.OpenNextFrame(100)
                      TextBlock1.Text = String.Empty
                      speechNo = 1
                      RenderScreen(colorFrame, depthFrame)
                      SaveBuffer(colorFrame, depthFrame, mySkeletonFrame)
                    End Using
                  End Using
                End Using
              Case Else
                Exit Select
              End Select
            End If
          End Sub
    Dim audio As KinectAudioSource = Kinect.AudioSource
    
    Using s As Stream = audio.Start()
      engine.SetInputToDefaultAudioDevice()
      engine.RecognizeAsync(RecognizeMode.Multiple)
    End Using
  End Sub

Kinectセンサーを取得し、ビットマップデータを初期化し、プレイヤーの画層を初期化する処理

Kinectセンサーを取得します。DepthStream.Enableメソッドで距離カメラの表示サイズを320×240、1秒あたり30フレームで、動作を開始します。表示サイズを640×480に設定すると描画が重くて動かなくなるので注意してください。同様にColorStream.Enableメソッドで、RGBカメラを、RGBフォーマット、表示サイズ640×480、1秒あたり30フレームで、動作を開始します。プレイヤーおよびスケルトンの認識も開始します。

骨格生成を行う場合は、深度センサー(距離カメラ)を有効にする必要があります。Dim depthStream As DepthImageStream = Kinect.DepthStreamで、距離カメラの処理を行うクラスのインスタンスを取得します。Short配列変数myDepthPixelDataに距離カメラのピクセルデータのバイト長分の配列を作成します。320×240の表示サイズの場合は、myDepthPixelData=New Short(76800-1){}分の配列を確保することになります。-1しているのは配列のインデックスは0から始まるためです。バイト配列変数myColorPixelDataにRGBカメラのピクセルデータのバイト長分の配列を作成します。

WriteableBitmapクラス型のmyOverrayBitmapを下記の書式で初期化します。

New WriteableBitmap(距離カメラのフレーム幅,距離カメラのフレーム高さ, ビットマップの水平値, ビットマップの垂直値,ビットマップのピクセルフォーマット,ビットマップのビットマップパレット)

「ビットマップのピクセルフォーマット」に指定する、PixelFormats.Bgra32は、Bgra32 ピクセル形式を取得します。ビットマップのピクセル形式には、PixelFormats.Bgra32を指定します。Bgra32 は、bits per pixel (BPP) が 32 の sRGB 形式です。各カラー チャネル(青、緑、赤、およびアルファ)に割り当てられるbits per pixel (BPP) は 8 です。ここに、Brg32を指定すると背景が透明化されず真っ黒になってしまいますので注意してください。

ここは、myOverrayBitmap = New WriteableBitmap(320 240, 96, 96, PixelFormats.Bgra32, Nothing)
と指定しても同じです。

IntRect32構造体のmyScreenImageRectを下記の書式で初期化し、領域を指定します。

New IntRect32(新しいInt32Rect のインスタンスの X座標,新しいInt32Rect のインスタンスの Y座標, 四角形の幅を指定した新しい Int32Rect のインスタンスの幅, 四角形の高さを指定した新しい Int32Rect のインスタンスの高さ)

「新しいInt32Rect のインスタンスの X座標」と「新しいInt32Rect のインスタンスの Y座標」には0を指定します。「四角形の幅を指定した新しい Int32Rect のインスタンスの幅」には、overrayBitmapオブジェクトの幅を指定し、「Int32Rect のインスタンスの高さ」には、myOverrayBitmapオブジェクトの高さを指定します。

結局は、
myScreenImageRect = New Int32Rect(0, 0, 320, 240)
と指定しても同じことです。

リングバッファを初期化します。RingBuffer構造体のinit_ringbuffer関数を使用します。書式は下記の通りです。

init_ringbuffer(秒数分のバッファ領域(この場合は3), RGBストリームの画素数, 深度ストリームの画素数) As Boolean

WriteableBitmapクラス型のRoom_BitmapをmyOverrayBitmapと同じ値で初期化し、myScreenImageRect2をRoom_Bitmap.WidthとRoom_Bitmap.Heightで初期化します。

myScreenImageRect2 = New Int32Rect(0, 0, 320, 240)と同じです。

Kinectセンサーを動作します。

Int32Rect構造体のmyImageSizeのWidthプロパティにRGBストリームのフレームの幅(640)を指定します。同じくHeightプロパティにはRGBストリームのフレームの高さ(480)を指定します。画像のサイズを取得します。

  Private Sub init_kinect()
    Try
      If KinectSensor.KinectSensors.Count = 0 Then
        MessageBox.Show("KINECTが接続されておりません。")
        Exit Sub
      Else
        Kinect = KinectSensor.KinectSensors(0)
      End If
  
      Kinect.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30) '640×480では重くて動かない。
      Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30)
      Kinect.SkeletonStream.Enable()

      Dim depthStream = Kinect.DepthStream

      myDepthPixelData = New Short(Kinect.DepthStream.FramePixelDataLength - 1) {}
      myColorPixelData = New Byte(Kinect.ColorStream.FramePixelDataLength - 1) {}
      myOverrayBitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing)
      myScreenImageRect = New Int32Rect(0, 0, CInt(Math.Ceiling(myOverrayBitmap.Width)), CInt(Math.Ceiling(myOverrayBitmap.Height)))
      myRingBuffer.init_ringbuffer(bufferSecond, Kinect.ColorStream.FramePixelDataLength, Kinect.DepthStream.FramePixelDataLength)
    
      Room_Bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing)
      myScreenImageRect2 = New Int32Rect(0, 0, CInt(Math.Ceiling(Room_Bitmap.Width)), CInt(Math.Ceiling(Room_Bitmap.Height)))

      Kinect.Start()
 
      myImageSize.Width = Kinect.ColorStream.FrameWidth
      myImageSize.Height = Kinect.ColorStream.FrameHeight
    Catch ex As Exception
      MessageBox.Show(ex.Message)
      Exit Sub
    End Try
  End Sub
  • Kinectの音声認識でプレイヤーを分離、結合するサンプル

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています