2022/11/18

Android Speech Translator Plugin for Unity

AndroidのSpeechRecognizerとMLKit Translateを使ってオンデバイスでリアルタイムスピーチ翻訳をUnityのプラグインとして実装してみた

ソースパッケージはGistで公開

概要はGistのREADMEに書いたので、ここでは作ってみて思ったことを書き残しておく

Unityプラグイン実装

よく見る記事ではAndroidネイティブAPIをUnityプラグインにする場合、AndroidStudio使って.aarにしてUnityのPlugins/Androidに置くとか書いてあるのが多いが、ものすごくめんどくさいので直接ネイティブソースを置いてUnityのgradleでビルドするようにした。

ソースファイルだけではだめで、AndroidManifest.xml、gradleTemplate.properties、mainTemplate.gradleファイルが必要。これらのファイルは、Android Player Settings>Publishing SettingsのBuildで、Custom Main Manifest、Custom Main Gradle Template、Custom Gradle Properties Templateにチェックを入れると自動的にPlugins/Androidにファイルが作成される。よくUnityインストールフォルダの奥深くから、これらのファイルを自分でコピーしてくるようなことを書いてある記事を見かけるが、そんな必要はない。

AndroidManifest.xmlに

  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.INTERNET" />

gradleTemplate.propertiesに

android.useAndroidX=true

mainTemplate.gradleのdependenciesに

    implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
    implementation 'com.google.mlkit:language-id:17.0.4'
    implementation 'com.google.mlkit:translate:17.0.1'

を追記。translateバージョンは17.0.1を入れたがその時点の最新を入れればいいと思う。
GistのunityPackageにはこれらのファイルは含まれている。

因みに、Unityのバージョンは2021.3.13f1を使用。2020.3.xxでもビルド可能だけど、Gradleなどのツールチェーンが今のAndroidStudioに比べかなり古いのでできるだけ新しいUnityバージョンを使うのがよさそう。2021.3.13f1で出力したAndroidProjectをAndroidStudioで開いてビルドしても問題なくビルドできた。

API Level

Player Settings>Other Settings:IdentificationのMinimum API LevelとTarget API Levelだけど、Minimumには24、TargetはHighest(30)を指定した。

SpeechRecognizer

品質はどうやらOSバージョンに強く依存するようだ。OS9くらいだとめちゃめちゃで使い物にならない。OS12だとまあ使える。OS13だともっといいかもしれない。

1回認識するとリスナーが終わってしまうので継続させるためには再度リスナー起動する必要がある。これが結構厄介。onResultsとonErrorイベント受けたタイミングでリスナー再起動している。本来ならonEndOfSpeechのタイミングが適切なのでは?と思うが、ここだといまいち安定しない。時々ERROR_CLIENTが出る。

何もしゃべらなかったとき、ERROR_SPEECH_TIMEOUTになるかなと思いきやERROR_NO_MATCHが出る。バグってない?と思ってしまう。

EXTRA_MAX_RESULTSで結果の最大数設定できるので1と設定しているが、結果が1つだけになるのはよっぽど自信があるときだけ。微妙な時は2~4つ候補が返ってくる。どんな仕様?

EXTRA_BIASING_STRINGSの使い方がさっぱり分からない。ドキュメントには

Optional list of strings, towards which the recognizer should bias the recognition results. These are separate from the device context.

と書かれているだけ。探しても具体的な設定方法とか効果とかがさっぱり分からない。
この設定で誤認識を少しは改善できるのでは?と思っているのだが。。。

onRmsChangedイベントって何?って思ったけど、簡単に言うと音量の事。rmsはRoot Mean Squareの略で一定時間内の平均音量ってこと。今音声拾ってますよというレベルメーターに使えるデータをずーっと返してくる。0..4はノイズ成分ぽいので捨ててます。

音声文字変換&音検知通知アプリもそうだけど、、

Pixel6に標準搭載された音声文字変換アプリに単語登録機能があるが、何入れても何の効果も感じられない。この機能がひょっとしてEXTRA_BIASING_STRINGSを使ってる部分かな?知らんけど。

結局EndToEndなんだよなぁ。学習モデルによるIN/OUTなので、しゃべる>直接日本語テキストが出てくる。出力結果に変化を与えたいと思ったら再学習させるしかないのだよね。
しゃべる>ひらがな認識>日本語入力エンジン>日本語テキストなら、途中のIME段階で辞書学習させれば誤認識も改善できそうなものだが。

固有名詞の誤認識を直したいときは、Unity上でonResultsで得られた文字列に対して特定の文字列があったら正しい文字列に直す、みたいな方法しか取れないかな。

2022/11/12

Git for Unityに切り替えた

しばらく GitHub for Unity を使ってまいりましたが、Unity2021.3.xxで不具合発生

https://github.com/github-for-unity/Unity/issues/1163 でも報告されていた。こちらの記事によるとメンテナンスは行われていないらしい。確かに2019年からの更新がない。

ということで、上記の記事でも書かれているように、Git for Unity に切り替えることにした。

インストールはREADMEに記載されている通りにやれば問題なし。GitHub for Unityをフォークしたものなので使い勝手は同じ。ちゃんとメンテされているので2021.3でも大丈夫だ。


2022/07/29

HandIKAppier for Unity

Hand IK apply on Timeline for Unity

UnityでHumanoidアニメーションIKを扱う場合、FootIKはClipにOn/Offがあるんでいいですが、HandIKの場合、普通はAnimationControllerでIKPassを有効にして、コンポーネントのOnAnimatorIK()内で処理コードを書きますよね。
じゃあPlayableDirector(Timeline)の場合は?
DirectorとAnimationControllerは排他関係にありますので、Directorで動かす場合はAnimationControllerは外しますね。OnAnimatorIK()受けるためだけに、無理やり同時使用するのも出来なくはないですが、ワーニング出るし気持ち悪いです。

今回作ったのは、モーキャプしたモーションをDirector/Timelineで再生する際にモーションのHandIKを使えるようにするスクリプトです。
Gist: https://gist.github.com/hiroshi-nishiura/9a696e2f5ba28fc82e410f8c16dc09c9

適応したいAnimatorが付いているオブジェクトもしくはその配下のオブジェクトにつければ機能します。左右それぞれのウエイト調整ができます。なぜウエイトかというと体格によっては手がターゲットに届かなくて腕がピーンってなってしまうのでどのくらい近づけるかというウエイト値になってます。
PlayableDirectorはどこにあってもいいものなので、Directorの場所もコンポーネントに教えてやります。Directorを設定しない場合は普通にOnAnimatorIK()が動きます。両対応ってことです。

Directorで動いているAnimatorでどうやってIK処理するかですが、IAnimationJobを使いました。Directorが作成するPlayableGraphからAnimatorに対応する出力にAnimationScriptPlayableノードを追加します。
このノードからJobにAnimationHumanStreamが渡ってきますので、JobのProcessAnimation()内でIK処理をやってます。
AnimationJobに関しては https://github.com/Unity-Technologies/animation-jobs-samples が参考になります。
DirectorのGraphに関しては情報がほとんどなく、ちょっと難儀しました。
因みに、AnimationControllerのGraphをいじろうとするとReadOnlyでいじれませんでした。DirectorのGraphは編集可能でよかったです。

2022/06/02

OrbitCameraController

OrbitCameraController for Unity

最近Unityでなんだかんだすることがあり、ちょっと便利なの作ったので。。

GameViewでSceneViewのようにカメラ操作が可能なスクリプトを公開
Gist URL
https://gist.github.com/hiroshi-nishiura/f70838083ad818d38911bfb77beb7557

カメラアングルが重要じゃない開発段階で、キャラの動きやポストエフェクトの確認とかでSceneViewとGameViewの切り替えが面倒。GameカメラをSceneViewに合わせてから実行とか面倒。GameView上でSceneView感覚でぐりぐり出来たらいいのに。という思いで作りました。

このスクリプトをGameカメラオブジェクトに加えると、GameView上でSceneViewとほぼ同じ感覚でカメラ操作ができるようになります。
実行時にSceneViewのアングルをGameカメラに反映させたり、終了時にGameカメラのアングルをSceneViewに反映することができるので、SceneViewとGameViewがシームレスな感じになります。

意識的に変えているところは、Altキーを押す必要があるという点と
SceneViewではできない、BallOrbitとRollが可能なところ。
後、SceneViewカメラを初期化するボタンもあります。

複数カメラにも一応対応してます。カメラの区別はTargetDisplayで判断しているので、操作を分けたい場合は、カメラのTargetDisplayを指定して、GameViewのDisplayを切り替えると別々の操作になります。

2022/05/21

fSpy Loader for Unity 2020.3+

fSpy という1枚の写真に数本のガイドラインを引いただけで3Dカメラキャリブレーションができるという秀逸なものがあり、Blender用のインポータプラグインがあるのですが、探してもUnity版が見当たらなかったので、EditorWindow版スクリプト書いてみました。

fSpyアプリからエキスポートしたJSONファイルを読んで、選択したシーンのカメラに反映させるシンプルなものです。
2020.3.26f1で作ったのでそれ以降だったら動くと思います。

Gist: https://gist.github.com/hiroshi-nishiura/294b24834e84e8ffdf69bd9fa612d339

JSON内にUnityのJsonUtilityでは扱えない配列とかがあるので、Json読むために Newtonsoft.Jsonを使用するので、PackageManager左上の+ボタンから、「Add package from git URL...」でcom.unity.nuget.newtonsoft-jsonを追加してインストールする必要があります。

fSpyはBlender向け(Z-up)として作られているらしく、Unityの座標系と相性が良くありませんが、FloorまたはWallに対する何種類かの軸モードに対応してUnity用に変換します。
Blender版を試したことがないので本来の動作じゃないかもしれませんが、自分だったらこういう結果になって欲しいという作りにしました。


2022/02/23

M5StickCplus + Gesture Unit + UIFlow(Blocky)

M5StackC Plusに GestureUnit つなげて UIFlowで遊んでみた。

といっても、どのサイト見てもArduinoIDEとかCとかで各サンプルばかり。最近自分はUIFlowで遊んでいるで、UIFlowでやりたいなと。でもUIFlowのUnitsリストには残念ながらGestureUnitモジュールがない。

ということで、https://docs.m5stack.com/en/unit/gesture とか https://wiki.seeedstudio.com/Grove-Gesture_v1.0 に載っているCサンプルコードを参考にしながらも、最終的にはUIFlowのBlockyで全部書いてみた。

UIFlowにはI2C通信が標準で出来るので基本的には問題ないのだが、一番の問題は初期化のための初期化テーブルをどうやってUIFlowで記述するかってこと。最初はList機能使って初期化データを設定していこうかと思ったが、レジスタアドレスとデータのセットが219もある。これを全部GUIで設定するのはつらすぎる。

ちょっとした工夫程度だけど「文字列からリストを作る」関数を使ってリストを作ることにした。サンプルコードからレジスタとデータ列を抜き取って文字列にした。で、初期化関数イメージは以下のようにできた。


Cで書いたやつに比べると文字列からリストを作る処理に時間かかるが問題なし。

後はサンプルやデータシートを参考にI2C処理を書いていけば出来上がり。

どれくらいのインターバルにすればいいのか? ドキュメントやらサンプルやら見たがいまいちピンとこん。サンプルは1000msが多い。ドキュメントには最低800msとか書かれていたり。自分で実験した限りだと、600~800msくらいが妥当かなと思った。

いつもだと作ったソースコードは記事内にインラインで入れることが多いが、UIFlowコードは載せにくい。MicroPythonコードで入れてもUIFlowのBlockyにならないし。。。
ということで今回のUIFlowソースは
https://gist.github.com/hiroshi-nishiura/707c8ba9849f6771bb698d0fdf482f33
に置いた。

えー、子供の学習用にちょうどいいかなと思って ATOM Matrix 買ってみたものの、表示がLEDMatrixしかないのがつまらないらしく放置されてしまった。シクシク。これにバッテリーが内蔵されてれば楽しそうなのにな。転がしてJyroで電子サイコロとか。