VRアプリで使える音声コマンドを簡単に実装してみる（Oculus Rift CV1 + Windows10)

この記事は Tech-Circle Hands on Advent Calendar 2016 の 10 日目の記事です。
昨日は、@negokaz さんによる [Akka Camel で外部システムとアクターを手軽につなぐ]
(http://qiita.com/negokaz/items/f4dc4229889bacc29b9e) でした。
* * *
#はじめに
アプリケーション技術週間も今日で最後ですね。
VR元年の2016年ももうすぐ終わりますが、みなさんいかがだったでしょうか。
私としては今年は[Tech-Circle Unity VRハンズオン](https://techcircle.connpass.com/event/40052/)を開催できたことが非常に大きかったですね。参加して頂いた皆さんありがとうございました。初めましての方で興味がある方は事前準備と当日の資料はリンク先のイベントページに公開しておりますので、是非御覧ください。
さて、今日はTech-CircleのVR担当としてVRアプリで使える音声コマンドの実装について書きます。
#開発環境
- Windows 10 64bit
- Unity 5.4.2（5.5出ましたね）
- Oculus Rift CV1 

#Unityの音声認識API
Unityの音声認識はググってみるとJuliusを使う例が多いですが、今回はOculus Rift CV1を使うので、Unity5.4から追加された音声認識 APIの[UnityEngine.Windows.Speech](https://docs.unity3d.com/540/Documentation/ScriptReference/Windows.Speech.DictationRecognizer.html)を使います。MacでOculus CV1を使うのは厳しいので、プラットフォームは限定されますが問題ないと思います。

#UnityEngine.Windows.Speechの体系

UnityEngine.Windows.Speechの体系についてはスクリプトリファレンスではなく[マイクロソフトの開発者ページ](https://developer.microsoft.com/en-us/windows/holographic/voice_input_in_unity)にまとまった資料があります。
偉大なるGoogle先生の力を借りながらまとめると以下のような感じです。

まず機能としては以下の大きく２つに分類されます。

- Phrase Recognition(語句認識）
- Dictation（聞き取り）

Dictationはしゃべっている内容を文字に変換してくれる機能です。Phrase Recognitionは登録した語句を認識した際にイベントが実行される機能です。この２つは同時に実行することができないので、アプリで両方とも使う場合は切り替えが必要です。

次に実装についてはDictationはそのままDictationRecognizerで実装されています（今回は音声コマンドの実装なので詳細は割愛します）。Phrase Recognitionについては以下の２つのクラスに分かれます。


- KeywordRecognizer（キーワード認識）
- GrammarRecognizer（文法認識）

KeywordRecognizerは配列で指定したキーワードとの一致をGrammarRecognizerはSRGSというXMLで書かれた音声認識文法との一致を認識した際にOnPhraseRecognizedメソッドが呼びだされます。今回は「簡単に」なのでKeywordRecognizerを使います。

#使ってみた
VRで音声コマンドということでユニティちゃんに旗揚げをやってもらいました。（音声コマンドより旗を作るのに時間がかかるという。。。）
[音声コマンドデモ - YouTube （※音声があります）
![](http://img.youtube.com/vi/fDcTgB-KcAA/0.jpg)
](http://www.youtube.com/watch?v=fDcTgB-KcAA "http://www.youtube.com/watch?v=fDcTgB-KcAA")

#実装
```HataAgeGame.cs
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Windows.Speech;

public class HataAgeGame : MonoBehaviour
{

    //音声コマンドのキーワード
    private string[] m_Keywords = { "あかあげて", "しろあげて", "あかさげて", "しろさげて" };

    private KeywordRecognizer m_Recognizer;

    //右手の位置ターゲット
    private GameObject RightHandTarget;
    //左手の位置ターゲット
    private GameObject LeftHandTarget;

    //右手と左手の上下それぞれの座標
    private Vector3 RightHandUpPos = new Vector3(-0.51f, 2.159f, 0f);
    private Vector3 LeftHandUpPos = new Vector3(0.588f, 2.108f, 0f);
    private Vector3 RightHandDownPos = new Vector3(-0.3f, 0.838f, 0f);
    private Vector3 LeftHandDownPos = new Vector3(0.341f, 0.838f, 0f);

    void Start()
    {
        LeftHandTarget = GameObject.FindWithTag("LeftHandTarget");
        RightHandTarget = GameObject.FindWithTag("RightHandTarget");
        m_Recognizer = new KeywordRecognizer(m_Keywords);
        m_Recognizer.OnPhraseRecognized += OnPhraseRecognized;
        m_Recognizer.Start();
    }

    private void OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {
        //ログ出力
        StringBuilder builder = new StringBuilder();
        builder.AppendFormat("{0} ({1}){2}", args.text, args.confidence, Environment.NewLine);
        builder.AppendFormat("\tTimestamp: {0}{1}", args.phraseStartTime, Environment.NewLine);
        builder.AppendFormat("\tDuration: {0} seconds{1}", args.phraseDuration.TotalSeconds, Environment.NewLine);
        Debug.Log(builder.ToString());

        //認識したキーワードで処理判定
        switch (args.text)
        {
            case "あかあげて":
                AkaAgete();
                break;
            case "しろあげて":
                ShiroAgete();
                break;
            case "あかさげて":
                AkaSagete();
                break;
            case "しろさげて":
                ShiroSagete();
                break;
        }
    }

    private void AkaAgete()
    {
        RightHandTarget.transform.position = RightHandUpPos;
    }
    private void ShiroAgete()
    {
        LeftHandTarget.transform.position = LeftHandUpPos;
    }

    private void AkaSagete()
    {
        RightHandTarget.transform.position = RightHandDownPos;
    }

    private void ShiroSagete()
    {
        LeftHandTarget.transform.position = LeftHandDownPos;
    }

}
```
OnPhraseRecognizedの引数のキーワードで判定するだけなので簡単ですね！
Oculus RiftのマイクはVirtual Reality SupportedでOculusを指定しておけば特に設定なしで使えます。

#最後に
正直普通にPCを使っている間にCortanaさんにマイクを使って話かけることはあまりないと思います。しかしOculus等のHMDを使用している間は入力手段が現状かなり制限されるので、音声コマンドは有効なUIの一つです。是非活用してみてください。※HMD被って音声コマンドやってるとかなり怪しい人に見えるので、周りに人がいないか注意が必要ですね。

#次回
Tech-Circle Hands on Advent Calendar 2016は来週からはインフラ技術週間です。
トップバッターは@papa_dacchiさんです。お楽しみに。