Kinect v2を使った「じゃんけんゲーム」を作る

2014年12月16日(火)
薬師寺 国安

音声認識処理

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

    Private Sub speechEngine_SpeechRecognized(sender As Object, e As SpeechRecognizedEventArgs)
        messageTextBlock.Text = String.Empty
        Rnd = New Random
        RandomNumber = Rnd.Next(0, 2)   (1)
        Const myConfidence As Double = 0.4   (2)
        If e.Result.Confidence >= myConfidence Then
            Select Case e.Result.Text
                Case “じゃんけんぽん”
                    computerJankenImage.Source = Nothing

                    JyankenImage = xmldoc.Descendants(“画像名”)(RandomNumber).Value   (3)
                    computerJankenImage.Source = New BitmapImage(New Uri(“Images/” & JyankenImage, UriKind.Relative))
                    If JyankenImage = “グー.png” AndAlso flagImage = “グー.png” Then   (4)
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “グー.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “グー.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “グー.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “グー.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    End If
                    playerScoreTextBlock.Text = “あなたの得点=” & player_Score
                    computerScoreTextBlock.Text = “コンピューターの得点=” & computer_Score
            End Select
        End If
    End Sub
  1. 0~2までの3つの乱数を発生します。
  2. Confidenceプロパティで音声認識の信頼度を設定します。-1~1の値で指定し、数字が大きいほど信頼度が高くなります。標準は「0」です。「-1」を指定すると、どんな言葉でも反応してしまう可能生があり、「1」を指定すると、なかなか認識されません。今回は信頼度が「0.4」より大きい場合に言葉を認識するよう指定しています。
    以下、プレイヤーが「じゃんけんぽん」とセンサーに向かって発生した場合の処理です。
  3. 乱数に該当する位置にある「画像名」要素の値を取得します。
  4. コンピューターが表示する「グー」「チョキ」「パー」のイメージ画像によって条件分岐を行います。JyanlenImageとflagImageの画像を比較して勝ち負けを表示しています。結果をmessageTextBlockに表示し、プレイヤーが勝ったら変数player_Scoreを、コンピューターが勝ったらcomputer_Scoreを1ずつ加算して表示します。

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

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

    Private Sub myBodyFrameReader_FrameArrived(sender As Object, e As BodyFrameArrivedEventArgs)
        Using frame = e.FrameReference.AcquireFrame   (1)
            If frame Is Nothing = False Then
                Using myDataContext = myBodyDrawingGroup.Open
                    myDataContext.DrawRectangle(Brushes.Black, Nothing, myRect)
                    frame.GetAndRefreshBodyData(myBodies)   (2)
                    For i As Integer = 0 To Me.myBodies.Length - 1   (3)
                        If myBodies(i).IsTracked = True Then
                            Dim joints As IReadOnlyDictionary(Of JointType, Joint) = myBodies(i).Joints   (4)
                            Dim points As New Dictionary(Of JointType, Point) (5)
                            For Each joint In joints.Keys  (6)
                                Dim pos = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(joints(joint).Position)   (7)
                                points(joint) = New Point(pos.X, pos.Y)   (8)
                                If joint = JointType.HandLeft Then   (9)
                                    DrawHandState(myBodies(i).Joints(JointType.HandLeft), myBodies(i).HandLeftState, myBodies(i).HandLeftConfidence)
                                ElseIf joint = JointType.HandRight Then
                                    DrawHandState(myBodies(i).Joints(JointType.HandRight), myBodies(i).HandRightState, myBodies(i).HandRightConfidence)
                                End If
                            Next
                        End If
                    Next
                    Me.myBodyDrawingGroup.ClipGeometry = New RectangleGeometry(myRect)   (10)
                End Using
            End If
        End Using
    End Sub
  1. e.FrameReference.AcquireFrameメソッドで、ボディフレームを取得し、Frameで参照します。
  2. GetAndRefreshBodyDataメソッドで、更新されたボディデータを取得します。
  3. ボディデータの中を、反復処理を行います。
  4. ボディデータが追跡されている場合は、ボディデータの関節を取得し、変数jointsで参照します。
  5. JointTypeのキーとPointの値で初期化された新しい、Dictionaryのインスタンスである、pointsオブジェクトを作成します。
  6. 各関節をjoint変数で取得していきます。
  7. CoordinateMapper.MapCameraPointToDepthSpaceメソッドで、関節の位置を、カメラ空間から距離空間へのポイントにマップし、変数posで参照します。CoordinateMapper.MapCameraPointToDepthSpaceメソッドの書式は下記の通りです。
    CoordinateMapper.MapCameraPointToDepthSpace(cameraPoint)
    「cameraPoint」にはカメラ空間からマップするポイントCameraSpacePointを指定します。この場合はカメラ空間における関節の位置を指定しています。
  8. 距離空間にマップされた関節のXとY座標で初期化された、新しいPointのインスタンスpoints(joint)オブジェクトを作成します。
  9. 関節のタイプが「左手」や「右手」であった場合は、DrawHandStateを実行します。
  10. DrawingGroup のクリップ領域にmyRectで初期化された2次元の四角形を設定します。

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

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

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

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

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

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

    Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
        Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame (1)
            If myColorFrame Is Nothing = True Then
                Return
            End If
            myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)   (2)
        End Using
        colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)   (3)
        Image1.Source = colorBitmap   (4)
    End Sub
  1. e.FrameReference.AcquireFrameメソッドでカラーフレームを取得し、myColorFrameで参照します。
  2. CopyConvertedFrameDataToArrayメソッドで、フレームから「BGRA形式」のバイト列に変換し、バイト配列に格納します。書式は下記の通りです。
    CopyConvertedFrameDataToArray(frameData, colorFomat)
    「frameData」には格納する配列、この場合はバイト配列のColorImagePixelDataを指定しています。「colorFormat」には「色の形式」を指定します。ここでは、「Bgra形式」となります。
  3. colorBitmap(WriteableBitmapクラス)のWritePixelsメソッドで、ビットマップの指定した領域内のピクセルを更新します。書式は下記の通りです。
    WritePixels(sourecRect, pixels, stride, offset)
    「sourceRect」には「Int32Rect型」を指定します。更新するWriteableBitmapの四角形です。「pixels」にはビットマップの更新に使用する「ピクセル配列」を指定します。この場合は、ColorImagePixelDataのバイト列を指定しています。「stride」にはpixels内の更新領域の「ストライド」を指定します。今回はcolorBitmapのPixelWidthに「4」を乗算した値を指定しています。「ストライド」とは、画像データのX座標横一列あたりに用いられるバイト数を示す値です。例えば、横幅が512ピクセルで、RGBA各色8ビット(32ビットカラー)で表現される画像データのストライドは、
    512(ピクセル)×4(バイト)=2048
    となります。今回の場合はRGBカラーの解像度が1920×1080(ピクセル)であるため
    1920×4=7680
    の値が「stride」に適用されることになります。「offset」には入力バッファのオフセットを指定します。今回は「0」を指定しています。
  4. 「Image1」のSourceプロパティにcolorBitmapオブジェクトを指定します。これでRGBカメラからの画像が表示されます。

プレイヤーの手の形によって条件分岐を行う処理

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

    Private Sub DrawHandState(joint As Joint, state As HandState, myTrackingConfidence As TrackingConfidence)

        If myTrackingConfidence <> TrackingConfidence.High Then
            Return
        End If

        If state = HandState.Open Then
            jankenImage.Source = New BitmapImage(New Uri("Images/パー.png", UriKind.Relative))
            flagImage = "パー.png"
        ElseIf state = HandState.Closed Then
            jankenImage.Source = New BitmapImage(New Uri("Images/グー.png", UriKind.Relative))
            flagImage = "グー.png"
        ElseIf state = HandState.Lasso Then
            jankenImage.Source = New BitmapImage(New Uri("Images/チョキ.png", UriKind.Relative))
            flagImage = "チョキ.png"
        Else
            jankenImage.Source = Nothing
            Exit Sub
        End If
    End Sub
End Class

手の形がOpen、つまり「パー」の場合は、ソリューションエクスプローラー内のImagesフォルダーから、「パー.png」をjankenImageのSourceプロパティに指定します。flagImageには「パー.png」を代入します。「グー」、「チョキ」パーについても同じ処理を行います。ここでの値が、speechEngine_SpeechRecognizedイベントの音声認識で使用され、「じゃんけんぽん」と言いながら「グー」「チョキ」「パー」のいずれかをセンサーに向けると、コンピューターとの勝負結果が表示されるようになります。

実行すると動画1のようになります。実際には「じゃんけんぽん」と喋っていますが、動画には音声が入っていませんので、ご了承ください。

動画1:Kinect v2センサーが手の形を認識して、じゃんけんができる

  • Kinect v2を使った「じゃんけんゲーム」を作るサンプル

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

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

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