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

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

音声認識処理

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

01Private Sub speechEngine_SpeechRecognized(sender As Object, e As SpeechRecognizedEventArgs)
02    messageTextBlock.Text = String.Empty
03    Rnd = New Random
04    RandomNumber = Rnd.Next(0, 2)   (1)
05    Const myConfidence As Double = 0.4   (2)
06    If e.Result.Confidence >= myConfidence Then
07        Select Case e.Result.Text
08            Case “じゃんけんぽん”
09                computerJankenImage.Source = Nothing
10 
11                JyankenImage = xmldoc.Descendants(“画像名”)(RandomNumber).Value   (3)
12                computerJankenImage.Source = New BitmapImage(New Uri(“Images/” & JyankenImage, UriKind.Relative))
13                If JyankenImage = “グー.png” AndAlso flagImage = “グー.png” Then   (4)
14                    messageTextBlock.Text = “あいこです”
15                ElseIf JyankenImage = “パー.png” AndAlso flagImage = “パー.png” Then
16                    messageTextBlock.Text = “あいこです”
17                ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “チョキ.png” Then
18                    messageTextBlock.Text = “あいこです”
19                ElseIf JyankenImage = “グー.png” AndAlso flagImage = “パー.png” Then
20                    messageTextBlock.Text = “あなたの勝です”
21                    player_Score += 1
22                ElseIf JyankenImage = “パー.png” AndAlso flagImage = “グー.png” Then
23                    messageTextBlock.Text = “コンピューターの勝です”
24                    computer_Score += 1
25                ElseIf JyankenImage = “パー.png” AndAlso flagImage = “チョキ.png” Then
26                    messageTextBlock.Text = “あなたの勝です”
27                    player_Score += 1
28                ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “パー.png” Then
29                    messageTextBlock.Text = “コンピューターの勝です”
30                    computer_Score += 1
31                ElseIf JyankenImage = “グー.png” AndAlso flagImage = “チョキ.png” Then
32                    messageTextBlock.Text = “コンピューターの勝です”
33                    computer_Score += 1
34                ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “グー.png” Then
35                    messageTextBlock.Text = “あなたの勝です”
36                    player_Score += 1
37                End If
38                playerScoreTextBlock.Text = “あなたの得点=” & player_Score
39                computerScoreTextBlock.Text = “コンピューターの得点=” & computer_Score
40        End Select
41    End If
42End 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の続き)

01Private Sub myBodyFrameReader_FrameArrived(sender As Object, e As BodyFrameArrivedEventArgs)
02    Using frame = e.FrameReference.AcquireFrame   (1)
03        If frame Is Nothing = False Then
04            Using myDataContext = myBodyDrawingGroup.Open
05                myDataContext.DrawRectangle(Brushes.Black, Nothing, myRect)
06                frame.GetAndRefreshBodyData(myBodies)   (2)
07                For i As Integer = 0 To Me.myBodies.Length - 1   (3)
08                    If myBodies(i).IsTracked = True Then
09                        Dim joints As IReadOnlyDictionary(Of JointType, Joint) = myBodies(i).Joints   (4)
10                        Dim points As New Dictionary(Of JointType, Point) (5)
11                        For Each joint In joints.Keys  (6)
12                            Dim pos = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(joints(joint).Position)   (7)
13                            points(joint) = New Point(pos.X, pos.Y)   (8)
14                            If joint = JointType.HandLeft Then   (9)
15                                DrawHandState(myBodies(i).Joints(JointType.HandLeft), myBodies(i).HandLeftState, myBodies(i).HandLeftConfidence)
16                            ElseIf joint = JointType.HandRight Then
17                                DrawHandState(myBodies(i).Joints(JointType.HandRight), myBodies(i).HandRightState, myBodies(i).HandRightConfidence)
18                            End If
19                        Next
20                    End If
21                Next
22                Me.myBodyDrawingGroup.ClipGeometry = New RectangleGeometry(myRect)   (10)
23            End Using
24        End If
25    End Using
26End 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の続き)

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

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

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

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

01Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
02    Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame (1)
03        If myColorFrame Is Nothing = True Then
04            Return
05        End If
06        myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)   (2)
07    End Using
08    colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)   (3)
09    Image1.Source = colorBitmap   (4)
10End 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の続き)

01    Private Sub DrawHandState(joint As Joint, state As HandState, myTrackingConfidence As TrackingConfidence)
02 
03        If myTrackingConfidence <> TrackingConfidence.High Then
04            Return
05        End If
06 
07        If state = HandState.Open Then
08            jankenImage.Source = New BitmapImage(New Uri("Images/パー.png", UriKind.Relative))
09            flagImage = "パー.png"
10        ElseIf state = HandState.Closed Then
11            jankenImage.Source = New BitmapImage(New Uri("Images/グー.png", UriKind.Relative))
12            flagImage = "グー.png"
13        ElseIf state = HandState.Lasso Then
14            jankenImage.Source = New BitmapImage(New Uri("Images/チョキ.png", UriKind.Relative))
15            flagImage = "チョキ.png"
16        Else
17            jankenImage.Source = Nothing
18            Exit Sub
19        End If
20    End Sub
21End 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メルマガ会員のサービス内容を見る

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