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

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

音声認識処理

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

01Private Sub speechEngine_SpeechRecognized(sender As Object, e As SpeechRecognizedEventArgs)
02    Const myConfidence As Double = 0.6
03    If e.Result.Confidence >= myConfidence Then   (1)
04        Select Case e.Result.Text
05            Case "うえ"   (2)
06                If Index <= 0 Then
07                    Index = 0
08                    Exit Sub
09                Else
10                    Index -= 1
11                End If
12                ListBox1.SelectedIndex = Index   (4)
13                ListBox1.ScrollIntoView(ListBox1.SelectedItem)    (5)
14            Case "した"   (3)
15                If Index >= ListBox1.Items.Count - 1 Then
16                    Index = ListBox1.Items.Count - 1
17                    Exit Sub
18                Else
19                    Index += 1
20                End If
21                ListBox1.SelectedIndex = Index   (4)
22                ListBox1.ScrollIntoView(ListBox1.SelectedItem)    (5)
23        End Select
24    End If
25End 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の続き

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

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

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

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

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

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

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

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

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

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

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

01Private Sub DrawEllipse(myJoint As Joint, R As Integer, myBrush As Brush)
02    Dim myEllipse = New Ellipse
03    With myEllipse
04        .Width = R
05        .Height = R
06        .Fill = myBrush
07    End With
08 
09    point = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(myJoint.Position) (1)
10    If point.X < 0 OrElse point.Y < 0 Then
11        Return
12    End If
13    Canvas.SetLeft(myEllipse, point.X - (R / 2))   (2)
14    Canvas.SetTop(myEllipse, point.Y - (R / 2))
15    If myJoint.JointType = JointType.Head AndAlso myMask <> String.Empty Then
16        myImage = New Image   (3)
17        With myImage   (4)
18            .Width = 60
19            .Height = 75
20            .Source = New BitmapImage(New Uri(myMask, UriKind.Relative))
21        End With
22        Canvas.SetLeft(myImage, point.X - 30)   (5)
23        Canvas.SetTop(myImage, point.Y - 30)
24        CanvasBody2.Children.Add(myImage)
25    End If
26    CanvasBody.Children.Add(myEllipse)   (6)
27End 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の続き

01Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
02    Dim colorFrameProcessed As Boolean = False
03    Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame  (1)
04        If myColorFrame Is Nothing = False Then
05            Dim myColorFrameDescription As FrameDescription = myColorFrame.FrameDescription   (2)
06            If myColorFrameDescription.Width = colorBitmap.PixelWidth AndAlso myColorFrameDescription.Height = colorBitmap.PixelHeight Then   (3)
07                If myColorFrame.RawColorImageFormat = ColorImageFormat.Bgra Then   (4)
08                    myColorFrame.CopyRawFrameDataToArray(ColorImagePixelData)
09                Else
10                    myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)
11                End If
12                colorFrameProcessed = True   (5)
13            End If
14        End If
15    End Using
16    If colorFrameProcessed = True Then
17        colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)   (6)
18        roomImage.Source = colorBitmap   (7)
19    End If
20End Sub
21End 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メルマガ会員のサービス内容を見る

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