はかせのラボ

私の頭の中を書いていく雑記ブログです

Unity Unityで音声認識 ~キーワードで取得~

あいさつ

どうも、はかせです。
ここ最近はずっとAlexaの開発をしておりました。
今までやったことのない分野でとても勉強になりました。

ただその中でAlexaに不満を持つことも多かったです。
ウェイクワードの件だったり、
前回のリマインダーの件だったり、
Alexaは何かと融通が利きません。
(おそらく誤動作と誤認識の軽減のため)

そうしていくうちにふとこう思いました。

「AlexaじゃなくてUnityで直接音声認識すればいんじゃね?」

もちろんAlexa×Unityというのが当初の技術的挑戦であり、
検証内容でした。

ただ目的が音声による操作ならばUnityで直接やってもいいはず。
ということで今回はUnityで直接音声認識をしてみる話です。

この記事では
Unityは2019.1.1f1、
マイクはノートパソコンの内臓マイクを使っています。

Unityで音声認識はできるのか

まず第一前提そういう機能やライブラリはあるのかって話です。
(ないならまた色々ごにょるだけですが・・・)

そして調べたらいっぱいありましたw
Alexaみたいにキーワード使うものから自由テキスト、
はては直接AudioCilpまで多種多様なものがありました。

Unityで直接提供されているものもあればWindowsの機能や
WebAPIだったりと叩き方もたくさんありました。
私が見たのは下記サイトですが、おそらく他にもあると思います。
qiita.com


さてこんだけあると何からやろうか迷いますね。
あくまでゲームの中で使いたいというとこなので
キーワードでやりましょう。
(迷って無くないか・・・?)

Unityでキーワードによる音声認識

キーワード認識はUnityが用意してくれている、
KeywordRecognizerというクラスを使えばできるみたいです。
docs.unity3d.com

ではまずさくっと使ってみましょう。

using UnityEngine;
using UnityEngine.Windows.Speech;

public class UnityVoiceTest : MonoBehaviour
{
    public string[] mKeywords;
    private KeywordRecognizer mKeywordRecognizer;

    // Start is called before the first frame update
    void Start()
    {
        mKeywordRecognizer = new KeywordRecognizer(mKeywords);
        mKeywordRecognizer.OnPhraseRecognized += OnPhraseRecognized;
        mKeywordRecognizer.Start();
    }

    private void OnDisable()
    {
        mKeywordRecognizer.Stop();
        mKeywordRecognizer.Dispose();
    }

    private void OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {
        print(args.text);
    }
}

このクラスの使い方は極めて単純です。
・キーワードの配列を用意する
・その配列をコンストラクタに渡しインスタンスを作る
・キーワードが発話された際に呼び出したい処理をイベントに追加する
・Startを呼び出し聞き取りを開始
・終わるときはDispose忘れずに

これだけです。

そして色々登録してみて色々話しかけてみました。
感想としては「内臓マイク使ってる割にはがんばってるな」です。
要はそこそこ精度いいです。

ただキーワードが発話の最初に無きゃ基本認識してくれません。
あとあんまり長いキーワードはうまく認識してくれないっぽい?
(私の滑舌悪い説濃厚)

なんかちょっと使い方めんどくさいな・・・

さっきのコードでは発話されたキーワードを
ただオウム返ししただけなのでそんなでしたけど、
キーワード毎にユニークな処理をしようと思うと地獄を見そうです。

というのも登録できるイベントハンドラーが一つしかないので
キーワードが増えれば増えるほど処理内で分岐が発生します

private void OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
    switch(args.text)
    {
        case"a":
            break;
        case"b":
            break;
        case"c":
            break;
        case"d":
            break;
        //以下増えるだけ増える・・・
    }
}

いやああぁぁ!!!
こんなめんどくさいことは嫌なので
すこしでも楽にするためのオレオレラッパー作りました。
それがこちら

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.Windows.Speech;

public class MyKeywordRecognizer
{
    private Dictionary<string, Action<string>> mKeyAndActionDic;
    private KeywordRecognizer mKeywordRecognizer;

    public MyKeywordRecognizer()
    {
        mKeyAndActionDic = new Dictionary<string, Action<string>>();
    }

    ~MyKeywordRecognizer()
    {
        Dispose();
    }

    /// <summary>
    /// キーワードの聞き取りを開始するタイミングで呼ぶ
    /// </summary>
    public void Start()
    {
        mKeywordRecognizer = new KeywordRecognizer(mKeyAndActionDic.Select(value => value.Key).ToArray());
        mKeywordRecognizer.OnPhraseRecognized += OnPhraseRecognized;
        mKeywordRecognizer.Start();
    }

    /// <summary>
    /// キーと対応する処理を登録する
    /// </summary>
    /// <param name="key">キー</param>
    /// <param name="action">処理</param>
    public void Add(string key, Action<string> action)
    {
        if (mKeyAndActionDic.ContainsKey(key)) throw new ArgumentException("キーが重複しています");
        mKeyAndActionDic.Add(key, action);
    }

    /// <summary>
    /// 終了時に呼び出す
    /// </summary>
    private void Dispose()
    {
        if (mKeywordRecognizer.IsRunning) mKeywordRecognizer.Stop();
        mKeywordRecognizer.Dispose();
    }

    /// <summary>
    /// 受け取ったキーに対応する処理を呼び出す
    /// </summary>
    /// <param name="args"></param>
    private void OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {
        if (!mKeyAndActionDic.ContainsKey(args.text)) return;
        mKeyAndActionDic[args.text](args.text);
    }
}

やってることは単純でDictionaryを使って
キーワードと処理をペアで管理しています。


使い方がこちら

using UnityEngine;

public class UnityVoiceTest : MonoBehaviour
{
    private MyKeywordRecognizer mKeywordRecognizer;

    // Start is called before the first frame update
    void Start()
    {
        mKeywordRecognizer = new MyKeywordRecognizer();
        mKeywordRecognizer.Add("アレクサ", text => print(text));
        mKeywordRecognizer.Add("ボルテッカー", Borutekka);
        mKeywordRecognizer.Start();
    }

    private void Borutekka(string text)
    {
        print("ピカチュウ!ボルテッカーだ!");
    }
}

Startの中でオレオレラッパーのインスタンスを作り
適時メソッドを呼び出して登録、聞き取りを開始しています。

KeywordRecgnizerインスタンスの管理は
ラッパーに任せているので使う側は気にしなくて問題ありません。

処理の登録はラムダでも普通のコールバックでもどちらもでも可です。
とにかく登録してスタートすればあとは良きに計らってくれます。

あとがき

今回はUnityの音声認識でした。
正直ゲームでAIに簡単な指示を出す程度ならば
全然問題なさそうでした。
(音声入力でチャットとかやろうとすると辛そうですが)

もしかしてUnity×Alexaを全然見かけなかったのは
このせいか・・・?

それでは今回はこの辺でノシ

ラッパーはGitHubに上げました。
github.com