Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9739

【Unity(C#)】Watson API × OculusQuestで音声認識

$
0
0

はじめに

VRヘビーユーザーからすれば当たり前のことかもしれませんが、
OculusQuestにマイクが搭載されているのはご存じでしたでしょうか。

ボイスチャットに利用されることがほとんどで、
その他の用途で使われている事例をあまり見たことがありませんでした。
(たぶん世の中にはたくさんある)

しかし、最近目にした記事にボイスチャット以外の用途でマイクを使った事例がありました。
【参考リンク】:Synamon、ロゼッタと「リアルタイム多言語翻訳システム装備のVRオフィス」を共同開発

一言で説明すると、翻訳VRアプリです。
マイクを音声認識の受け口として利用しています。

そこで、私も勉強がてら"OculusQuestのマイクを利用したVRアプリ作りに挑戦してみたい"と思い、実際に作りました。

Watson API

先ほどのアプリで利用した音声認識の部分はWatson APIを利用しています。

利用にはアカウントの登録とリソースの作成が必要です。
こちらからログイン後、検索欄からSpeech To Textを選んでリソースを作成します。

Watson3.PNG

フリー版だと500分/月分が無料で30日で使用できなくなるようです。

リソースの作成を終えたら下記画面からAPIキーを取得します。

Watson4.PNG

プロジェクトの下準備

続いてプロジェクトの下準備を行います。
まずは、プラットフォームをAndroidに変更しておきます。これは後からでも構いませんが私は最初にしました。

そして、Api Compatibility Level.NET 4.xに変更します。
これをしないと後述のSDKの導入でエラーを吐きます。

Watson1.PNG

続いて、公式のGitHubからSDKを取得します。
下記リンク先から2つZipファイルをダウンロードします。
watson-developer-cloud/unity-sdk
IBM/unity-sdk-core

Zipを解凍したらそれぞれ下記のように名前を変更し、プロジェクトのAssets配下に移動します。

Watson2.PNG

成功すれば、初回だけIBMのログイン画面に遷移するポップアップダイアログが出現します。
出現しなかったり、ダイアログを消してしまったりしても、
先ほど作成したリソースの画面でAPIキーを確認すればいいだけなので問題ないです。

音声認識ではマイクを使用するのでパーミッションの追記が必要です。
Oculus Integrationを導入後、Unityのツールバーに出現するOculus/Tools/Create strore -compatible AndroidManifest.xmlを実行し、AndroidManifestを作成します。
そしてマイクのパーミッションを追加します。categoryLAUNCHERが無いと怒られたのでそちらも追加しました。

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"android:installLocation="auto"><applicationandroid:label="@string/app_name"android:icon="@mipmap/app_icon"android:allowBackup="false"><activityandroid:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"android:configChanges="locale|fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"android:launchMode="singleTask"android:name="com.unity3d.player.UnityPlayerActivity"android:excludeFromRecents="true"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/> //追記
        <categoryandroid:name="android.intent.category.INFO"/><categoryandroid:name="com.oculus.intent.category.VR"/></intent-filter><meta-dataandroid:name="com.oculus.vr.focusaware"android:value="true"/></activity><meta-dataandroid:name="unityplayer.SkipPermissionsDialog"android:value="false"/><meta-dataandroid:name="com.samsung.android.vr.application.mode"android:value="vr_only"/><meta-dataandroid:name="com.oculus.supportedDevices"android:value="quest|quest2"/></application><uses-featureandroid:name="android.hardware.vr.headtracking"android:version="1"android:required="true"/><uses-permissionandroid:name="android.permission.RECORD_AUDIO"/> //追記
</manifest>

これでビルド環境が整いました。

バージョン

Unity 2019.4.8f1
Oculus Integration 25.0
watson-developer-cloud/unity-sdk v5.0.2
IBM/unity-sdk-core v1.2.2

サンプルデモ

音が無いですが、ボタンを押しながらしゃべると音声認識が実行されるサンプルです。
SpeechSample.gif

コード

SDK内のExampleStreamingというサンプルシーン内のコードを改変しました。

usingUnityEngine;usingSystem.Collections;usingUnityEngine.UI;usingIBM.Watson.SpeechToText.V1;usingIBM.Cloud.SDK;usingIBM.Cloud.SDK.Authentication.Iam;usingIBM.Cloud.SDK.Utilities;usingIBM.Cloud.SDK.DataTypes;publicclassCustomExampleStreaming:MonoBehaviour{[Space(10)][Tooltip("The service URL (optional). This defaults to \"https://api.us-south.speech-to-text.watson.cloud.ibm.com\"")][SerializeField]privatestring_serviceUrl;[Tooltip("Text field to display the results of streaming.")]publicTextResultsField;[Header("IAM Authentication")][Tooltip("The IAM apikey.")][SerializeField]privatestring_iamApikey;[Header("Parameters")][Tooltip("The Model to use. This defaults to en-US_BroadbandModel")][SerializeField]privatestring_recognizeModel;privateint_recordingRoutine=0;privatestring_microphoneID=null;privateAudioClip_recording=null;privateint_recordingBufferSize=1;privateint_recordingHZ=22050;privateSpeechToTextService_service;voidStart(){LogSystem.InstallDefaultReactors();Runnable.Run(CreateService());}privatevoidUpdate(){if(OVRInput.GetDown(OVRInput.RawButton.A)){StartRecording();}if(OVRInput.GetUp(OVRInput.RawButton.A)){StopRecording();}}privateIEnumeratorCreateService(){if(string.IsNullOrEmpty(_iamApikey)){thrownewIBMException("Plesae provide IAM ApiKey for the service.");}IamAuthenticatorauthenticator=newIamAuthenticator(apikey:_iamApikey);while(!authenticator.CanAuthenticate())yieldreturnnull;_service=newSpeechToTextService(authenticator);if(!string.IsNullOrEmpty(_serviceUrl)){_service.SetServiceUrl(_serviceUrl);}_service.StreamMultipart=true;Active=true;}privateboolActive{get=>_service.IsListening;set{if(value&&!_service.IsListening){_service.RecognizeModel=(string.IsNullOrEmpty(_recognizeModel)?"en-US_BroadbandModel":_recognizeModel);_service.DetectSilence=true;_service.EnableWordConfidence=true;_service.EnableTimestamps=true;_service.SilenceThreshold=0.01f;_service.MaxAlternatives=1;_service.EnableInterimResults=true;_service.OnError=OnError;_service.InactivityTimeout=-1;_service.ProfanityFilter=false;_service.SmartFormatting=true;_service.SpeakerLabels=false;_service.WordAlternativesThreshold=null;_service.EndOfPhraseSilenceTime=null;_service.StartListening(OnRecognize,OnRecognizeSpeaker);}elseif(!value&&_service.IsListening){_service.StopListening();}}}privatevoidStartRecording(){if(_recordingRoutine==0){UnityObjectUtil.StartDestroyQueue();_recordingRoutine=Runnable.Run(RecordingHandler());}}privatevoidStopRecording(){if(_recordingRoutine!=0){Microphone.End(_microphoneID);Runnable.Stop(_recordingRoutine);_recordingRoutine=0;}}privatevoidOnError(stringerror){Active=false;Debug.Log(error);}privateIEnumeratorRecordingHandler(){_recording=Microphone.Start(_microphoneID,true,_recordingBufferSize,_recordingHZ);yieldreturnnull;if(_recording==null){StopRecording();yieldbreak;}varbFirstBlock=true;varmidPoint=_recording.samples/2;float[]samples=null;while(_recordingRoutine!=0&&_recording!=null){intwritePos=Microphone.GetPosition(_microphoneID);if(writePos>_recording.samples||!Microphone.IsRecording(_microphoneID)){Debug.Log("Microphone disconnected.");StopRecording();yieldbreak;}if((bFirstBlock&&writePos>=midPoint)||(!bFirstBlock&&writePos<midPoint)){samples=newfloat[midPoint];_recording.GetData(samples,bFirstBlock?0:midPoint);varrecord=newAudioData();record.MaxLevel=Mathf.Max(Mathf.Abs(Mathf.Min(samples)),Mathf.Max(samples));record.Clip=AudioClip.Create("Recording",midPoint,_recording.channels,_recordingHZ,false);record.Clip.SetData(samples,0);_service.OnListen(record);bFirstBlock=!bFirstBlock;}else{varremaining=bFirstBlock?(midPoint-writePos):(_recording.samples-writePos);vartimeRemaining=(float)remaining/(float)_recordingHZ;yieldreturnnewWaitForSeconds(timeRemaining);}}}privatevoidOnRecognize(SpeechRecognitionEventresult){if(result!=null&&result.results.Length>0){foreach(varresinresult.results){foreach(varaltinres.alternatives){ResultsField.text=alt.transcript;}}}}privatevoidOnRecognizeSpeaker(SpeakerRecognitionEventresult){}}

音声認識のコールバックに登録してあるOnRecognizeの内部で認識した音声をテキストに反映しています。

privatevoidOnRecognize(SpeechRecognitionEventresult){if(result!=null&&result.results.Length>0){foreach(varresinresult.results){foreach(varaltinres.alternatives){ResultsField.text=alt.transcript;}}}}

下記リンクの手順でエディター上でも音声認識を行うことができます。
【参考リンク】:【Unity】Oculus Link使ってEditor上でデバッグ
しかし、まれにQuestのマイクが反応しなくなります。原因不明です。
その際はLinkの接続をいったん切る、Unityを再起動するなどすればだいたい直ります。

認識言語の設定はSpeechToTextService.RecognizeModelを変更することで切り替えることが可能です。
下記に一覧があります。
【参考リンク】:BM Cloud API Docs/Speech to Text

おわりに

翻訳VRは最終的に多人数翻訳コミュニケーションアプリに仕立てたいので、Watsonを導入する以前からPUN2Photon Voiceを導入していました。

しかし、ライブラリ間で同名のDllが存在する?とかなんとかでエラーを取り除くことができず、それに気づくまで動作しないことをWatsonのせいにしてました。(ごめんよWatson)

引き続き多人数対応をしていきたいので、どうすればライブラリ間の干渉によるエラーを取り除けるかも含めて調査します。

(Watson導入の前に、Androidのネイティブの音声認識機能がQuestでも動かないか検証して四苦八苦してました。結果動かずWatsonに助けてもらいました。その記録も供養の意を込めてそのうちメモしようと思っています。)


2021/04/05 追記

供養しました→【Unity(C#)】OculusQuestでAndroidネイティブの音声認識機能を呼び出せるのか検証

参考リンク

watson-developer-cloud/unity-sdk
UnityからIBM Watson APIを使う
UnityでWatsonと雑談APIを利用したChatBotを作る


Viewing all articles
Browse latest Browse all 9739

Trending Articles