Unity

Unityで音声認識+読み上げ

はじめに

Vtuberののらきゃっとさんっていらっしゃるじゃないですか。
彼女の「声を認識して読み上げソフトで読み上げさせる」っていうのがすごいいいなーって思ったので自分も作ってみました。
琴葉葵ちゃんになれます。

できたもの

マイクの声を拾ってVOICEROID2で葵ちゃんに再生させるプログラム
ついでにUnity内に字幕も生成する

NoName_2018-1-31_6-18-18_No-00_LI.jpg

方法

力技です。

  1. Windows.Speechで音声認識
  2. クリップボードに貼り付け
  3. VOICEROIDのウィンドウをアクティブにする
  4. ペースト&再生
  5. 元のソフトをまたアクティブにする

という感じです。

必要なもの

  • Unity (2017.3)
  • VOICEROID2

実装

VOICEROID再生部分

いきなり苦肉の策なんですが、UnityでのWin32APIの使い方がわからなかった(このままだとプロセスをアクティブにできない)ので、「プロセスのアクティブ化」「コピペ」だけをするプログラムを書き出します。

Program.cs
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace SendTextToVoiceroid
{
    class Program
    {
        string PROCESSNAME = "<ビルドしたUnityのプログラム名>"; // program.exeとビルドしたなら"program"
        string WINDOWTITLE = "<ビルドしたUnityのウィンドウ名>"; // PlayerSettingsのProductName

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        static void Main(string[] args)
        {
            bool isFoundGameProcess = false;
            bool isPlayedVoice = false;
            Process game = null;

            foreach (var p in System.Diagnostics.Process.GetProcesses())
            {
                if (!isFoundGameProcess && p.ProcessName == PROCESSNAME && p.MainWindowTitle == WINDOWTITLE)
                {
                    game = p;
                    isFoundGameProcess = true;
                }

                if (!isPlayedVoice && Regex.IsMatch(p.ProcessName, @"VoiceroidEditor"))
                {
                    SetForegroundWindow(p.MainWindowHandle);
                    SendKeys.SendWait("^a");
                    SendKeys.SendWait("^v");
                    SendKeys.SendWait("{F5}");
                    isPlayedVoice = true;
                }

                if (isFoundGameProcess && isPlayedVoice)
                {
                    SetForegroundWindow(game.MainWindowHandle);
                    break;
                }
            }
        }
    }
}

ちょっとややこしくなりましたが、System.Diagnostics.Process.GetProcesses()で稼働中のプロセスを一覧でもってきて、プロセス名とメインウィンドウ名から目的のウィンドウ(VOICEROIDとUnityのプログラム)を持ってきます。
あとはVOICEROIDの方をアクティブにしてCtrl+A Ctrl+V F5と送信して全選択→貼り付け→再生を実現しています。
あとはまたUnityのプロセスをアクティブにするだけ。
これをこぴぺ.exeとかなんとか適当なexeファイルとして書き出しておきます。

Unity部分

Hoge.cs
using UnityEngine;
using UnityEngine.Windows.Speech;

public class Hoge : MonoBehaviour
{
    const string path = @"<さっきのこぴぺ.exeのパス>";

    private DictationRecognizer dicRecognizer;

    void Start()
    {
        dicRecognizer = new DictationRecognizer();
        dicRecognizer.InitialSilenceTimeoutSeconds = 10;
        // 確定
        dicRecognizer.DictationResult += (text, confidence) =>
        {
            gameObject.GetComponent<UnityEngine.UI.Text>().text = text;
            GUIUtility.systemCopyBuffer = text;
            System.Diagnostics.Process.Start(path);
        };
        // 推測
        dicRecognizer.DictationHypothesis += (text) => {
            // 推測時にする処理
        };
        // 停止時
        dicRecognizer.DictationComplete += (completeCause) =>
        {
            // 要因がタイムアウトなら再び起動
            if (completeCause == DictationCompletionCause.TimeoutExceeded)
                dicRecognizer.Start();
        };
        dicRecognizer.Start();

    }
}

これをGUITextオブジェクトにアタッチしておわりです。

DictationRecognizerオブジェクトで音声認識ができます。
DictationResultイベントにリスナーを登録しておけば認識される度に処理ができるので、GUIText(字幕)の文字を書き換え、クリップボードにコピーし、さっきのプログラムを呼び出しています。

補足

このままだと使いにくいので、実際には推測候補を表示したり認識を再起動させるボタンを追加したりしました。
あとはモーションキャプチャで3Dモデルを動かしたりしたら完璧そうですね。
Unityに組み込める合成音声ソフトとかがあればこんな面倒な事をしなくてもUnityだけで完結できるんですけどね…。

出力をDiscordに繋げば葵ちゃんの声で通話したりできますね!

参考