LoginSignup
54
40

More than 3 years have passed since last update.

Unityの音声認識を使いやすくしてみた

Last updated at Posted at 2019-02-21

より低遅延かつ柔軟な音声認識システムを開発しましたので、ぜひお使いください。

こんにちは、自称永遠の中二病のZeniZeniです。
普段から心の中でかっこいい詠唱とかして一人興奮してるわけですが、半年くらい前から詠唱して魔法を使うようなVRコンテンツを作ってます。

これはUnityEngine.Windows.SpeechのPhraseRecognizerを使って音声認識を行っています。(他の音声認識の手段もいろいろと試してみたんですが、レスポンスが遅かったり、そもそも設定がたいへんだったりして自分は断念しました。)
PhraseRecognizerにもKeywordRecognizer(特定の単語のみ)と GrammarRecognizer(発声された単語すべて)の二種類があります。
コンテンツ内で魔法の詠唱に用いるような、速いレスポンスが求められるような場面ではKeyeordRecognizerを用いるのがいいと思います。

このKeywordRecognizerの使い方はunityのドキュメントを見ていただければいいんですが、非常に簡単です。
下のコードはドキュメントに書いてあるコードを少し加筆して、設定した単語が認識されたかどうかを、別のメソッド等で使用するために、bool値を用いて切り替えています。

コードの例

KeywordScript
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Windows.Speech;

public class KeywordScript : MonoBehaviour
{
    [SerializeField]
    private string[] m_Keywords;
    [SerializeField]
    private bool hasRecognized;
    private KeywordRecognizer m_Recognizer;

    void Start()
    {
        m_Recognizer = new KeywordRecognizer(m_Keywords);
        m_Recognizer.OnPhraseRecognized += OnPhraseRecognized;//認識された文字列を渡したいメソッドを追加する。
        m_Recognizer.OnPhraseRecognized += Test;
        m_Recognizer.Start();
    }

    void Update () 
    {
         if (hasRecognized)
         {
            Debug.Log("m_keyword[0] was recognized");
            hasRecognized = false;
         }
    }

//認識された単語をコンソールに表示するメソッド
    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());
    }

//認識された単語がm_Keywords[0]だったらhasRecognizedをtrueにする
    private void Test(PhraseRecognizedEventArgs args)
    {
        if(args.text == m_Keywords[0])
        {
           hasRecognized = true;
        }
    }
}

簡単ですね!
しかし冷静になってみてから思ったんですがこれかなり面倒くさい。
音声認識を使うときは毎回これを記述する必要が出てきますし、設定する単語数が増えたり、音声認識を使うメソッドが増えたりするともっと長くなります。
僕なんかは、音声認識の精度を疑似的に向上させるために、一つの単語とそれに近い発音の単語もまとめて設定したりしたので("マジック"と"まじく"みたいな)必要なコードはけっこう長くなりました。

改良しよう

というわけでこの音声認識をもっと簡潔に実装するために、長くなりそうな部分を一つのクラスとしてまとめてあげて、音声認識をする際には、認識したい文字列を記述して渡すだけになるようにしました。
まとめたクラスが下記のコードです。

KeywordController
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Windows.Speech;


public class KeywordController : MonoBehaviour{

    public string[][] keywords;//認識したい単語を二次元配列で記録
    public bool[] hasRecognized;
    public KeywordRecognizer[] m_Recognizer;
    public bool ConsoleKeyword;//認識された単語をコンソールで表示するかしないか

    public KeywordController(string[][] keywords,bool ConsoleKeyword)
    {
        this.ConsoleKeyword = ConsoleKeyword;
        this.keywords = keywords;
    }

    public void SetKeywords()
    {
        hasRecognized = new bool[keywords.Length];
        m_Recognizer = new KeywordRecognizer[keywords.Length];
        for (int i = 0; i < keywords.Length; i++)
        {
            SetRecognizer(i);
        }
    }

    private void SetRecognizer(int i)
    {
        m_Recognizer[i] = new KeywordRecognizer(keywords[i]);
        m_Recognizer[i].OnPhraseRecognized += SetKeywordsBool;
        if (ConsoleKeyword)
        {
            m_Recognizer[i].OnPhraseRecognized += OnPhraseRecognized;
        }
    }

    private void SetKeywordsBool(PhraseRecognizedEventArgs args)
    {
        for (int i = 0; i < keywords.Length; i++)
        {
            for (int j = 0; j < keywords[i].Length; j++)
            {
                if (args.text == keywords[i][j])
                {
                    hasRecognized[i] = true;
                }
            }
        }
    }

    public void StartRecognizing(int i)
    {
        if (!m_Recognizer[i].IsRunning)
        {
            m_Recognizer[i].Start();
        }
        else
        {
            Debug.Log("KeywordRecognizer" + "[" + i + "]" + " is already started.");
        }

    }

    public void StopRecognizing(int i)
    {
        if (m_Recognizer[i].IsRunning)
        {
            m_Recognizer[i].Stop();
        }
        else
        {
            Debug.Log("KeywordRecognizer" + "[" + i + "]" + " is already stopped.");
        }
    }

    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());
    }
}

これをUnityのプロジェクト内に作成してください。
実際に音声認識をする際には下記のようにします。

KeywordTest
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Windows.Speech;

public class KeywordTest : MonoBehaviour {

    private KeywordController keyCon;
    private string[][] keywords;

    // Use this for initialization
    void Start () {
        keywords = new string[2][];
        keywords[0] = new string[] {"りんご","アップル"};//ひらがなでもカタカナでもいい
        keywords[1] = new string[] {"みかん","オレンジ"};

        keyCon = new KeywordController(keywords,true);//keywordControllerのインスタンスを作成
        keyCon.SetKeywords();//KeywordRecognizerにkeywordsを設定する
        keyCon.StartRecognizing(0);//シーン中で音声認識を始めたいときに呼び出す
        keyCon.StartRecognizing(1);
    }

    // Update is called once per frame
    void Update () {
        if (keyCon.hasRecognized[0])//設定したKeywords[0]の単語らが認識されたらtrueになる
        {
            Debug.Log("keyword[0] was recognized");
            keyCon.hasRecognized[0] = false;
        }
        if (keyCon.hasRecognized[1])
        {
            Debug.Log("keyword[1] was recognized");
            keyCon.hasRecognized[1] = false;
        }
    }
}

けっこうすっきりしましたね。
シーン内で音声認識を止めたいと思ったらStopRecognizingを呼んでください。まだ検証してないんですが、KeywordRecognizerを多用して起動しっぱなしにすると動作が重くなりそうな気がするので、使わない場面ではこまめにStopした方がいいと思います。

注意事項

KeywordRecognizer は現在 Windows 10 でのみしか機能しないようです。
また、ネットワーク接続がないと使えないようです。

開発、執筆にあたり、下記のサイト様を参考、引用させていただきました。

Unity スクリプトリファレンス

54
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
40