はじめに
VRヘビーユーザーからすれば当たり前のことかもしれませんが、
OculusQuestにマイクが搭載されているのはご存じでしたでしょうか。
ボイスチャットに利用されることがほとんどで、
その他の用途で使われている事例をあまり見たことがありませんでした。
(たぶん世の中にはたくさんある)
しかし、最近目にした記事にボイスチャット以外の用途でマイクを使った事例がありました。
【参考リンク】:Synamon、ロゼッタと「リアルタイム多言語翻訳システム装備のVRオフィス」を共同開発
一言で説明すると、翻訳VRアプリです。
マイクを音声認識の受け口として利用しています。
そこで、私も勉強がてら"OculusQuestのマイクを利用したVRアプリ作りに挑戦してみたい"と思い、実際に作りました。
勉強がてら作成していた翻訳VRできました!😎
— KENTO⚽️XRエンジニア😎Zenn100記事マラソン挑戦中29/100 (@okprogramming) April 3, 2021
OculusQuestのマイクで拾った音声を音声認識APIに渡して、認識結果を翻訳APIに渡す、、、というやり方です💪
次はマルチ対応していきます😆#OculusQuest#Unitypic.twitter.com/k95D73gEnh
Microsoft Translator
先ほどのアプリで利用した翻訳の部分はMicrosoft Translatorを利用しています。
登録の手順を覚えている範囲でメモします。
この手順に関してですが、2021/04/03時点の情報となります。
私も使い方がわからなかったため、過去に執筆された記事等手掛かりに進めてましたが、UIや手順に変更があり、そこそこ苦労しました。
この記事もそうなる可能性が高いのでご注意ください。
まずは下記からMicrosoft AzureのHome画面を開きます。
ログインしたらHome画面上部からTranslatorを検索し、
Marketplaceの欄のTranslatorを選び、登録に進みます。
登録画面で必要な項目を選択します。ここでPricing tierの欄をfreeにしておけば無料で使えるはずです。(たぶん)
設定完了したらAPIを利用する際に必要な値を下記画面から確認できます。
翻訳デモ
翻訳VRアプリ内のコードは他のAPIに置換できるよう、モジュール化しているためわかりにくいかなと思い、シンプルな翻訳デモを作りました。
英語→日本語、日本語→英語が翻訳可能です。
もちろんMicrosoft Translatorが対応している言語であれば他の言語でも翻訳可能です。
【参考リンク】:Language and region support for text and speech translation
バージョン
念のため使ったライブラリ等のバージョンも書いときます。
Unity 2019.4.8f1
UniTask.2.2.4
UniRx 7.1.0
コード
翻訳デモのコードです。
usingSystem;usingSystem.Linq;usingSystem.Text;usingSystem.Threading;usingCysharp.Threading.Tasks;usingUniRx;usingUnityEngine;usingUnityEngine.Networking;usingUnityEngine.UI;/// <summary>/// MSTranslatorの最小構成サンプル/// </summary>publicclassSimpleTranslation:MonoBehaviour{[SerializeField]privateDropdownfromLanguageDd;[SerializeField]privateDropdowntoLanguageDd;[SerializeField]privateButtontranslateButton;[SerializeField]privateInputFieldinputField;[SerializeField]privateTexttranslationText;/// <summary>/// レスポンスを格納する構造体/// </summary>[Serializable]publicstructTranslateData{publicTranslations[]translations;[Serializable]publicstructTranslations{publicstringtext;publicstringto;}}/// <summary>/// リクエストを格納する構造体/// </summary>[Serializable]publicstructSpeechData{publicstringText;}privateconststringSUBSCRIPTION_KEY="登録キー";privateconststringENDPOINT="https://api.cognitive.microsofttranslator.com/";privateconststringLOCATION="登録時に設定したLocation";/// <summary>/// 設定言語/// </summary>privateenumLanguage{ja,en}privateLanguagefromLanguage=Language.ja;privateLanguagetoLanguage=Language.en;privatevoidStart(){vartoken=this.GetCancellationTokenOnDestroy();//ドロップダウンメニュー作成varlanguages=Enum.GetNames(typeof(Language));fromLanguageDd.ClearOptions();fromLanguageDd.AddOptions(languages.ToList());toLanguageDd.ClearOptions();toLanguageDd.AddOptions(languages.ToList());fromLanguageDd.value=(int)fromLanguage;toLanguageDd.value=(int)toLanguage;//翻訳元言語fromLanguageDd.OnValueChangedAsObservable().Subscribe(value=>{fromLanguage=(Language)value;}).AddTo(this);//翻訳後言語toLanguageDd.OnValueChangedAsObservable().Subscribe(value=>{toLanguage=(Language)value;}).AddTo(this);//翻訳ボタン押下translateButton.OnClickAsObservable().Subscribe(async_=>{//結果が送られてくるまで待ってから表示varresult=GetTranslation(fromLanguage,toLanguage,inputField.text,token);translationText.text=awaitresult;}).AddTo(this);}/// <summary>/// 翻訳結果を返す/// </summary>/// <param name="from">翻訳前の言語設定</param>/// <param name="to">翻訳語の言語設定</param>/// <param name="speechText">翻訳したい文字列</param>/// <param name="ct">CancellationToken</param>/// <returns>翻訳結果</returns>privateasyncUniTask<string>GetTranslation(Languagefrom,Languageto,stringspeechText,CancellationTokenct){//POSTメソッドのリクエストを作成varrequestInfo="translate?api-version=3.0";requestInfo+=$"&from={from}&to={to}";varrequest=UnityWebRequest.Post(ENDPOINT+requestInfo,"Post");//リクエストに使用するJSON作成varspeechData=newSpeechData{Text=speechText};varjsonData="["+JsonUtility.ToJson(speechData)+"]";varbodyRaw=Encoding.UTF8.GetBytes(jsonData);request.uploadHandler=newUploadHandlerRaw(bodyRaw);request.downloadHandler=newDownloadHandlerBuffer();request.SetRequestHeader("Content-Type","application/json");//ヘッダーに必要な情報を追加request.SetRequestHeader("Ocp-Apim-Subscription-Region",LOCATION);request.SetRequestHeader("Ocp-Apim-Subscription-Key",SUBSCRIPTION_KEY);//結果受け取りvarsecond=TimeSpan.FromSeconds(3);varresult=awaitrequest.SendWebRequest().ToUniTask(cancellationToken:ct).Timeout(second);varrawJson=result.downloadHandler.text;varjson=rawJson.Substring(1,rawJson.Length-2);vardata=JsonUtility.FromJson<TranslateData>(json);returndata.translations[0].text;}}
APIのリクエスト用のJson、APIのレスポンス用のJsonのそれぞれをシリアライズ、デシリアライズするための構造体を用意する必要があります。
リクエスト用のJsonはルートが配列でないとAPIの都合上だめだったのでごり押ししています。var jsonData = "[" + JsonUtility.ToJson(speechData) + "]";
【参考リンク】:【Unity(C#)】WebAPIで返ってきたJSONデータの扱いでつまったところ
EnumをDropDownに反映
全然関係ない内容ですが、知らなかったのでメモします。(下記参考リンクのまんまですが...)
【参考リンク】:UnityでDropDownのOptionリストに、enumの定義値をラベルとしてスクリプトからセットする
/// <summary>/// 設定言語/// </summary>privateenumLanguage{ja,en}privateLanguagefromLanguage=Language.ja;privateLanguagetoLanguage=Language.en;privatevoidStart(){//ドロップダウンメニュー作成varlanguages=Enum.GetNames(typeof(Language));fromLanguageDd.ClearOptions();fromLanguageDd.AddOptions(languages.ToList());toLanguageDd.ClearOptions();toLanguageDd.AddOptions(languages.ToList());fromLanguageDd.value=(int)fromLanguage;toLanguageDd.value=(int)toLanguage;//翻訳元言語fromLanguageDd.OnValueChangedAsObservable().Subscribe(value=>{fromLanguage=(Language)value;}).AddTo(this);//翻訳後言語toLanguageDd.OnValueChangedAsObservable().Subscribe(value=>{toLanguage=(Language)value;}).AddTo(this);}
Enumの値をvar languages = Enum.GetNames(typeof(Language));で配列化して
DropDownの値に追加します。
あとはDropDownの値変更を監視して、変更時にenumに値を設定してあげればOKです。
おわりに
記事内にも書いた通り、翻訳APIの箇所はモジュール化しているので他の無料で使えるAPIと比較して精度など試してみようと思います。
次は音声認識機能について書きます。
参考リンク
Microsoft Translator テキスト API で、日本語を英語に翻訳するサンプル
UniTaskの使い方2020 / UniTask2020
Quickstart: Get started with Translator