PR

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のようになります。実際には「うえ」、「した」と喋ってマスクを選択していますが、音声は録音されておりません。ご了承ください。

Think IT会員限定特典
  • 音声認識で仮面を装着する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のWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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