Kinect v2の音声認識で「仮面」を選んで変身してみる

2015年1月6日(火)
薬師寺 国安

音声認識処理

リスト6:MainWindow.xaml.vbの一部、リスト5の続き

Private Sub speechEngine_SpeechRecognized(sender As Object, e As SpeechRecognizedEventArgs)
    Const myConfidence As Double = 0.6
    If e.Result.Confidence >= myConfidence Then   (1)
        Select Case e.Result.Text
            Case "うえ"   (2)
                If Index <= 0 Then
                    Index = 0
                    Exit Sub
                Else
                    Index -= 1
                End If
                ListBox1.SelectedIndex = Index   (4)
                ListBox1.ScrollIntoView(ListBox1.SelectedItem)    (5)
            Case "した"   (3)
                If Index >= ListBox1.Items.Count - 1 Then
                    Index = ListBox1.Items.Count - 1
                    Exit Sub
                Else
                    Index += 1
                End If
                ListBox1.SelectedIndex = Index   (4)
                ListBox1.ScrollIntoView(ListBox1.SelectedItem)    (5)
        End Select
    End If
End Sub
  1. Confidenceプロパティで音声認識の信頼度を設定します。低信頼度の「-1」から高信頼度の「1」の間の値をとり、標準は「0」となります。-1を指定すると、どんな言葉でも反応する恐れがあります。一方1を指定すると、なかなか認識されません。今回は、信頼度が0.6より大きい場合に言葉を認識するよう指定しています。
  2. プレイヤーが「うえ」とセンサーに向かって発生した場合の処理です。メンバー変数Indexの値が、1ずつ減少します。
  3. プレイヤーが「した」とセンサーに向かって発生した場合の処理です。メンバー変数Indexの値が、1ずつ加算されます。
  4. ListBox1の選択されたインデックスにIndex変数の値を指定することで、Indexに該当するマスクが選択されます。
  5. ScrollIntoViewメソッドで選択された画像位置まで移動しますが、今回はListBoxに3個全ての画像が表示されているので、移動は確認できません。例えばListBoxに2個のマスクだけが表示されていて、残り1個が下に隠れて見えない場合は、3個目のマスクが指定された場合は、その位置までスクロールします。

新しいボディフレームの準備ができているときに発生するイベント処理

リスト7:MainWindow.xaml.vbの一部、リスト6の続き

Private Sub myBodyFrameReader_FrameArrived(sender As Object, e As BodyFrameArrivedEventArgs)
    CanvasBody.Children.Clear()   (1)
    CanvasBody2.Children.Clear()
    Using myBodyFrame = e.FrameReference.AcquireFrame   (2)
        If myBodyFrame Is Nothing = True Then
            Return
        End If
        myBodyFrame.GetAndRefreshBodyData(myBodies)    (3)
    End Using
    For Each body In myBodies
        For Each joint In body.Joints   (4)
            If joint.Value.TrackingState = TrackingState.Tracked Then
                DrawEllipse(joint.Value, 10, Brushes.Transparent)   (5)
            End If
        Next
    Next
End Sub
  1. CanvasBodyとCanvasBody2内を一度クリアしておきます。
  2. e.FrameReference.AcquireFrameメソッドで、ボディフレームを取得し、myBodyFrameで参照します。myBodyFrameに何も設定されていなかった場合は、何も行いません。
  3. GetAndRefreshBodyDataメソッドで、更新されたボディデータを取得します。
  4. ボディデータの中を、反復処理を行いながら、繰り返し変数jointで、関節の位置を反復処理しながら、関節位置を取得していきます。
  5. 関節が追跡されている場合は、関節の位置、関節の位置を描く円の大きさ(10を指定)と円の色(透明)を指定して、DrawEllipseを実行します。

ウインドウが閉じられる場合の処理

リスト8:MainWindow.xaml.vbの一部、リスト7の続き

Private Sub MainWindow_Closing(sender As Object, e As ComponentModel.CancelEventArgs) Handles Me.Closing
    If myKinect Is Nothing = False Then
        myKinect.Close()
        myKinect = Nothing
    End If
End Sub

Kinectが動作している場合は、Kinectを閉じ、全ての関連付けから解放します。

ListBoxからマスクが選択された時の処理

リスト9:MainWindow.xaml.vbの一部、リスト8の続き

Private Sub ListBox1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
    myMask = DirectCast(ListBox1.SelectedItem, Mask).画像名
End Sub

メンバー変数myMaskに、ListBox1から選択された項目を、Maskクラスにキャストして、その「画像名」プロパティの値を取得して格納します。

関節の位置に透明の円を表示する処理

リスト10:MainWindow.xaml.vbの一部、リスト9の続き

Private Sub DrawEllipse(myJoint As Joint, R As Integer, myBrush As Brush)
    Dim myEllipse = New Ellipse
    With myEllipse
        .Width = R
        .Height = R
        .Fill = myBrush
    End With

    point = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(myJoint.Position) (1)
    If point.X < 0 OrElse point.Y < 0 Then
        Return
    End If
    Canvas.SetLeft(myEllipse, point.X - (R / 2))   (2)
    Canvas.SetTop(myEllipse, point.Y - (R / 2))
    If myJoint.JointType = JointType.Head AndAlso myMask <> String.Empty Then
        myImage = New Image   (3)
        With myImage   (4)
            .Width = 60
            .Height = 75
            .Source = New BitmapImage(New Uri(myMask, UriKind.Relative))
        End With
        Canvas.SetLeft(myImage, point.X - 30)   (5)
        Canvas.SetTop(myImage, point.Y - 30)
        CanvasBody2.Children.Add(myImage)
    End If
    CanvasBody.Children.Add(myEllipse)   (6)
End Sub

透明な直径10ピクセルの円を作成します。直径はDrawEllipseを呼ぶ際に指定しています(リスト7)。

  1. CoordinateMapper.MapCameraPointToDepthSpaceメソッドで、関節の位置を、カメラ空間から距離空間へのポイントにマップし、メンバー変数pointで参照します。
  2. SetLeftとSetTopメソッドで透明な円を、指定したXとY軸に描きます。
    関節が頭部であり、かつメンバー変数myMaskの値が空ではなかった場合には、以下の処理を行います。
  3. 新しいImageクラスのインスタンスmyImageオブジェクトを生成します。
  4. myImageオブジェクトのWidthとHeightを指定し、Sourceプロパティに、メンバー変数myMaskの値を指定します。
  5. SetLeftとSetTopメソッドで、選択されたマスクを頭部に配置します。「-30」は位置を調整している値です。
  6. CanvasBodyには、透明化された円がプレイヤーの関節部分に描画されます。目には見えません。

カラーフレーム到着時のイベント

リスト11:MainWindow.xaml.vbの一部、リスト10の続き

Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
    Dim colorFrameProcessed As Boolean = False
    Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame  (1)
        If myColorFrame Is Nothing = False Then
            Dim myColorFrameDescription As FrameDescription = myColorFrame.FrameDescription   (2)
            If myColorFrameDescription.Width = colorBitmap.PixelWidth AndAlso myColorFrameDescription.Height = colorBitmap.PixelHeight Then   (3)
                If myColorFrame.RawColorImageFormat = ColorImageFormat.Bgra Then   (4)
                    myColorFrame.CopyRawFrameDataToArray(ColorImagePixelData)
                Else
                    myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)
                End If
                colorFrameProcessed = True   (5)
            End If
        End If
    End Using
    If colorFrameProcessed = True Then
        colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)   (6)
        roomImage.Source = colorBitmap   (7)
    End If
End Sub
End Class
  1. e.FrameReference.AcquireFrameメソッドでカラーフレームを取得し、myColorFrameで参照します。
  2. myColorFrameにデータがあった場合、KinectSensor からイメージフレームのプロパティを取得し、変数myColorFrameDescriptionで参照します。
  3. 取得したプロパティの幅と高さがcolorBitmapのPixelWidth、PixelHeightと同じであった場合、以下の処理を行います。
  4. カラーフレームデータがBgra形式であった場合は、CopyRawFrameDataToArrayメソッドで、Raw フレームのデータ指定された配列(ColorImagePixelData)にコピーします。そうでない場合は、CopyConvertedFrameDataToArrayメソッドでBgra形式に変換してから、バイト配列に格納します。
  5. Boolean型で宣言していた変数colorFrameProcessedをTrueで初期化します。
  6. 変数colorFrameProcessed変数がTrueであった場合は、colorBitmap(WriteableBitmapクラス)のWritePixelsメソッドで、ビットマップの指定した領域内のピクセルを更新します。

CopyConvertedFrameDataToArrayメソッドやWritePixelsメソッドの書式については、連載の第3回目を参照してください。7.roomImageのSourceプロパティにcolorBitmapオブジェクトを指定します。これでRGBカメラからの画像が表示されます。

実際に動作させると動画1のようになります。実際には「うえ」、「した」と喋ってマスクを選択していますが、音声は録音されておりません。ご了承ください。

  • 音声認識で仮面を装着するKinect v2プログラム

    『作りながら学ぶKinect v2プログラミング開発』 第4回のサンプルプログラムです。
薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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