はじめに
前回、リップシンクするプラグインを作り直してみた記事を書きました。
色々とご反響を頂いたのですが、しかしながら母音推定の性能がいまいちでした。第 1 、第 2 フォルマントという周波数スペクトルの 1 つ目、2 つ目のピークを検出する方法を採用していたのですが、このピークの検出が周波数スペクトル包絡を得るために使っている LPC 解析のパラメタに大きく依存したり、またノイズや声の高さによって容易にピークが別の場所に移ってしまったりといったことが原因でした。
そこで手法を変え、MFCC(メル周波数ケプストラム係数)を求める方式へと変更しました。
これはディープラーニング以前の音声認識に使われていたもののようで、母音だけでなく子音も含む音素の検出が可能な特徴量になります。以前の手法と異なりピッチ変化に対する耐性が強くなっています(ピッチ特性は除去されて声道特性を抽出している)。より詳細な違いは以下のスライドをご参照ください:
www.slideshare.net
これにより使い方も大きく変わったので、本記事では新しい設定方法について説明していきます。
追記
新しいバージョンの解説を書きました。
デモ
できることは前回と同じです。ただ、あいうえお以外の音素(ん、や鼻息など)も登録できるようになりました。
環境
前回と同じ Unity のバージョンおよび環境で調査しています。Mac / iOS でも調整すれば動作するといったご報告もいただきましたので、他のプラットフォームについても追って調査していきます。
- Unity 2020.1.17f1 (Windows)
ダウンロード
Releases ページから最新のパッケージを自身のプロジェクトへインポートしてください(執筆時は 1.0.1 です)。
また、Unity.Burst
および Unity.Mathematics
を必要とするので、プロジェクトにインストールされていない場合は Package Manager から追加してください。
使い方
uLipSync のセットアップ
まず音声解析のコンポーネントをセットアップします。uLipSync
コンポーネントを声を喋らせる AudioSource
を持つゲームオブジェクトにアタッチしてください。次のような UI が現れます。
次にプロファイルを指定します。作成の仕方は後で説明しますのでとりあえず「Male」または「Female」ボタンをクリックしてサンプルのプロファイルをセットしてください。UI が次のように変化して、あいうえお(A, I, U, E, O)音素の特徴量が表示されます。
ブレンドシェイプの紐付け
次に口パクさせるコンポーネントのセットアップです。 キャラクタのルートに uLipSyncBlendShape
コンポーネントをアタッチしてください。その上で Skinned Mesh Renderer
のプルダウンから口パクに対応する SkinnedMeshRenderer
を指定します(Find From Children
のチェックボックスを外し、直接 Skinnd Mesh Renderer をオブジェクトのフィールドに指定すればキャラクタのルートにアタッチしなくても大丈夫です)。
プロファイルでセットアップされている音素に対応するブレンドシェイプを指定します。Add New BlendShape
ボタンを 5 回押下して 5 個のブレンドシェイプ設定を追加します。その上で下の画像のように音素と対応するブレンドシェイプの紐付けを行ってください。
uLipSync への登録
この uLipSyncBlendShape
を uLipSync
コンポーネントに登録します。uLipSync
は解析結果をイベントを通じて通知する仕組みになっています。Callback セクションを開き、On Lip Snyc Update (LipSyncInfo)
に uLipSyncBlendShape.OnLipSyncUpdate
を登録してください。
マイク
もし AudioSource
で声を喋らせるのでなくマイクで入力した声を喋らせたい場合は uLipSyncMicrophone
を uLipSync
と同じゲームオブジェクト(= AudioSource
を持つオブジェクト)にアタッチし、適当なマイクデバイスを選んでください。
実行
これでプレイすれば動くようになるはずです!
各コンポーネントについて
コンポーネントについての外観は以下のような感じです。
uLipSync
リップシンクを計算するためのコアとなるコンポーネントです。 uLipSync
は MonoBehaviour.OnAudioFilterRead()
からオーディオバッファを取得するので、AudioSource
でオーディオを再生するのと同じ GameObject にアタッチする必要があります。計算はバックグラウンドスレッドで行われ、JobSystem と Burst Compiler によって最適化されており、大体のケースで計算は 1 ~ 2 ms 程度で完了します。
uLipSyncBlendShape
SkinndeMeshRenderer
のブレンドシェイプを制御するためのコンポーネントです。uLipSync
のプロファイルに登録した音素に対応するブレンドシェイプを登録することで、音声分析の結果を口の形に反映させることができます。
uLipSyncMicrophone
このコンポーネントはマイク入力を再生するための AudioClip
を作成し、AudioSource
に設定します。そのため uLipSync
を持つ GameObject にアタッチしていください。録音の開始/停止はエディタ上からだけでなくスクリプトからも StartRecord()
/ StopRecord()
を呼び出すことで行うことができます。また、index
を変更することで入力ソースを変更することもでき、使いたい入力を探すには、uLipSync.MicUtil.GetDeviceList()
を使うと便利です。
プロファイルについて
概要
uLipSync
は、冒頭で述べたように現在再生中の音声から MFCC と呼ばれる音声特徴量をリアルタイムに抽出します。現在再生中の音声がどの音素のどの MFCC に近いかを推定し、その情報をコールバックとして発行します。uLipSyncBlendShape
は、このコールバックからやってきた情報(どの音素が認識されているか、どれくらいのボリュームかなど)を使って SkinnedMeshRenderer
のブレンドシェイプをスムーズに移動させます。そして MFCC と関連する計算パラメータの登録には、Profile
というアセットを使用しています。
- MFCC
Add Phoneme
を押すと、新しい音素が登録されます。音素名(A, I, E, O, Uなど)と対応する MFCC を後述する方法で登録することで、登録した音素をuLipSync
が推定できるようになります。
- Parameters
- Mfcc Data Count。
- キャリブレーションのために登録する MFCC のデータ数です。
- Mel Filter Bank Channels。
- MFCC を計算する際に必要なメルフィルタバンクのチャンネル数
- Target Sample Rate。
- 入力されたデータをダウンサンプリングして計算を軽くする際の周波数を指定します。例えば、PC 環境では 48000 Hz のデータが
OnAudioFilterRead()
に入力されていますが、デフォルトでは 1/3 の16000Hz にダウンサンプリングされています。
- 入力されたデータをダウンサンプリングして計算を軽くする際の周波数を指定します。例えば、PC 環境では 48000 Hz のデータが
- Sample Count。
- MFCC の計算に必要な音のバッファの数。デフォルトは 16000 Hz で 1024 サンプルなので、約 0.064 秒(〜4フレーム)のデータが使用されます(計算自体は毎フレームオーバーラップして行われます)。
- Min Volume
- 入力ボリュームの最小値(Log10 を適用するので 0.001 は -3 になります)。
- Max Volume
- 入力された音量の最大値。
Min Volume
と組み合わせた計算する正規化されたボリュームが、コールバック(OnLipSyncUpdate()
)のボリュームとして出力されます。
- 入力された音量の最大値。
- Mfcc Data Count。
- Import / Export JSON。
Profile
を JSON に出力したり、逆にインポートしたりすることができます。詳細は後述します。
いじるのは MFCC
、Min Volume
、Max Volume
あたりになると思います。
キャリブレーション
プロファイルの作成
Profile の右下にある「Create」ボタンをクリックしてください。新しい Profile
アセットが作成され割り当てられます。
音素の追加
次に Add Phoneme
を押してあいうえお(A, I, U, E, O)のような音素を追加します(アルファベットでもひらがなでも大丈夫です)。
キャリブレーション(マイク)
マイクを使う場合はゲームを実行して、「あーーー」と喋りながら登録した「あ」に対応する音素の Calib ボタンを押し続けます。同様に、いうえお、の音素に対してもキャリブレーションを行います。
他にも「ん」や「息」といったものも登録してもある程度識別できます。
キャリブレーション(データ)
事前録音の AudioClip
を再生する場合はちょっと面倒ですが「あ」や「い」に対応する音のみを既存の録音データからトリミングしてそれを AudioSource
に割当ててループ再生し、対応する音の音素の Calib ボタンをマイクの説明と同じように押下してキャリブレーションを行います。
パラメタ調整
あとはマイクの特性や入力ボリュームに応じて、Min Volume
と Max Volume
を調整してください。
コールバック
リップシンクの解析が終了していたら Update()
のタイミングで登録されたコールバックが呼び出されます(終了していない場合はフレームをまたがって計算が行われ、終わり次第呼び出されます)。引数として渡ってくる LipSyncInfo
は次のような構造体になっています。
public struct LipSyncInfo { public int index; // MFCC のインデックス番号 public string phenome; // 対応する音素名 public float volume; // 正規化されたボリューム public float rawVolume; // 正規化されていないボリューム public float distance; // キャリブレーション値との誤差(小さいほど信頼性が高い) }
たとえば認識結果をデバッグ出力したい場合は次のようなスクリプトを書きます。
using UnityEngine; using uLipSync; public class DebugPrintLipSyncInfo : MonoBehaviour { public void OnLipSyncUpdate(LipSyncInfo info) { Debug.LogFormat( $"PHENOME: {info.phenome}, " + $"VOL: {info.volume}, " + $"DIST: {info.distance} "); } }
パラメタ
Parameters には Output Sound Gain という項目があります。音声は別撮りで Unity からは音声を出力したくない場合などはこれを 0 にしてください。AudioSource
の方を 0 にしてしまうと、OnAudioFilterRead()
にやってくるバッファも 0 になってしまうため、この仕組を用意しています。
リアルタイムの解析情報
Runtime Information というセクションでは現在の解析状況をリアルタイムに見ることが出来ます。
- Volume
- Current Volume
- 現在のボリューム(生値)
- Min Volume
- 認識された中での最小ボリューム、これをもとに Profile の最小ボリュームを調整してください
- Max Volume
- 認識された中での最大ボリューム
- Normalized Volume
Min Volume
とMax Volume
で正規化されたボリューム
- Current Volume
- MFCC
- 現在の MFCC
なお、このセクションを表示している間は毎フレームエディタの再描画が行われるようになるため、ゲームのパフォーマンスが低下します。確認したい際のみ開くようにしてください。
その他
前回は [BurstCompile]
アトリビュートをつけているのにルールを守っていなくて Burst の恩恵を受けていない(エラーを吐いている)関数がいくつかありました。。今回は指定したものは全部対応できていると思います。
おわりに
Job + Burst で処理するため外部ライブラリが使えず数式を全部 C# のサブセットで書かないとならないのに苦労しました...。まず C++ で書いて既存ライブラリと比較したりしながら数値検証しつつ、また、たまたま身近に先生がいたためアルゴリズムなどの調整については相談させてもらいながらなんとか完成出来ました。まだバグなどもあると思いますので、以降バージョンを重ねて色々改善していきたいと思います。