音声解析リップシンクミドルウェア「CRI ADX LipSync」を使ってみた

本記事は、[サムザップ Advent Calendar 2019 #2](https://qiita.com/advent-calendar/2019/sumzap2) の12/7の記事です。

#CRI ADX LipSyncとは
2019年11月末に深層学習を用いた音声解析によるリップシンクをするためのミドルウェア『[CRI ADX LipSync](https://game.criware.jp/products/adx-lipsync/#shiyou)』がCRI・ミドルウェア社からリリースされました。2019年12月頭現在、対応しているゲームエンジンはUnityのみですが、いずれ他のゲームエンジンにも対応していく予定とのことです。

今回リリースされた音声解析機能には、再生した音声を入力としてリアルタイムに口の形状情報を出力するものと、予め音声から口の形状情報をテキストデータとして出力するものとがあります。

前者は、CRIWARE SDKに含まれており同社が出しているサウンド演出を手掛ける『CRI ADX2』というミドルウェアと組み合わせて手軽に実装することができます。こちらは、CRIWAREユーザーであればタイトルリリース後も無料で使用することができるようです。
後者は、専用のコマンドツール（Windows版のみ）を使うことになり、出力されたデータはADX2のツール上で編集することも可能です。こちらは、CRIWAREユーザーであってもタイトルリリース後は料金が発生するようです。

本記事では、Unityで『CRI ADX2』と『CRI ADX LipSync』を組み合わせて音声データから口の形状情報をリアルタイムに得る方法を紹介したいと思います。


#フォルマントベースの音声解析
音声からリアルタイムに口形状情報を得るために用いられる特徴量は、主に次の２つがあります。

・音声の振幅値（音量）
・音声のフォルマント（周波数のピーク）

振幅値ベースのリップシンクは、実装が割と楽な反面、得られるデータが1次元であるため単純な口の開閉運動しか表現できず不自然さを感じやすくなります。
一方で、フォルマントベースのものでは、2次元データ（F1, F2周波数）から母音（a, i, u, e, o）を推定することができるため、口の上下の開閉だけでなく左右の伸縮にもマッピングすることで、より自然な表現が可能となります。

『CRI ADX LipSync』を使うと、フォルマントベースのリップシンクを手軽に実現することができます。

#唇音の判定もできる
自然なリップシンクを実現する上では、母音が推定できただけでは実はまだ足りないようです。口を閉じるところでちゃんと閉じさせることができないケースがあります。
「ま」や「ば」などの唇音と呼ばれる音です。唇音は、口を一度閉じてから発音します。
例えば、母音の推定だけで「ママ」という単語の発音をリップシンクさせると、「aa」と口が開きっぱなしになってしまうのです。

この唇音の判定まで自前で実装するとなると、結構大変そうです。
『CRI ADX LipSync』では、なんと唇音であるかどうかも判定してくれるようです。

#実際に使ってみる
リアルタイムに再生される音声を解析する方式と、予め解析して口パクデータを作っておくオフライン方式とが用意されているが、
今回は、リアルタイムでの解析を試してみました。

使用したツールのバージョンは、以下の通りです。
Unity： 2019.2.5f1
CRIWARE SDK for Unity： 3.00.00

###リアルタイム解析による口形状情報取得までの流れ

１．アナライザーの初期化
ADX2のAtomライブラリを用いて再生した音声から口形状情報を取得するための解析器であるCriLipsAtomAnalyzerを初期化します。

```
// インスタンス生成
var atomAnalyzer = new CriLipsAtomAnalyzer();
// 無音と判定する最大音量(0以下のdB値)の設定
atomAnalyzer.SetSilenceThreshold(-40);
// 解析対象の音声データのサンプリング周波数を設定(16000Hz以上)
atomAnalyzer.SetSamplingRate(48000);
```
２．アナライザーのプレイヤーへのアタッチ
AtomライブラリのプレイヤーであるCriAtomExPlayerにCriLipsAtomAnalyzerのインスタンスをアタッチします。

```
var player = new CriAtomExPlayer();
atomAnalyzer.AttachToAtomExPlayer(player);
```
３．アナライザーから口形状情報の取得
CriLipsAtomAnalyzerからCriLipMouthクラスの口形状情報を格納する構造体CriLipsMouth.InfoおよびCriLipsMouth.MorphTargetBlendAmountAsJapaneseを取得します。

```
// 縦横形状の組み合わせ情報の取得
var info = new CriLipsMouth.Info();
atomAnalyzer.GetInfo(out info);

// 日本語５母音の組み合わせ情報の取得
var blendAmount = new CriLipsMouth.MorphTargetBlendAmountAsJapanese();
atomAnalyzer.GetMorphTargetBlendAmountAsJapanese(out blendAmount);
```

###取得できる口形状情報の種類
CriLipsAtomAnalyzerからは、次の2種類の口形状情報を取得することができます。

**・縦横形状の組み合わせ情報**
構造体：　CriLipsMouth.Info
構造体のフィールド：

| フィールド名 | 取得できる値 |
|:-----------------|:------------------|
| lipWidth             |              口の幅 (0.0f~1.0f) |
| lipHeight           |            口の高さ (0.0f~1.0f) |
| tonguePosition             |              舌の位置 (0.0f~1.0f) |
| isLipWidthReleased               |                口の幅が閉じ状態に遷移中かどうか |
| isLipHeightReleased             |             口の高さが閉じ状態に遷移中かどうか |
| isLipToungueReleased          |           舌の位置が閉じ状態に遷移中かどうか |

**・日本語５母音の組み合わせ情報**
構造体：　CriLipsMouth.MorphTargetBlendAmountAsJapanese
構造体のフィールド：

| フィールド名 | 取得できる値 |
|:-----------------|:------------------|
| a             |              「あ」のブレンド量 (0.0f~1.0f) |
| i           |            「い」のブレンド量 (0.0f~1.0f) |
| u            |              「う」のブレンド量 (0.0f~1.0f) |
| e              |                「え」のブレンド量 (0.0f~1.0f) |
| o           |             「お」のブレンド量 (0.0f~1.0f) |
※　同時刻においては、5母音の内2母音のブレンド量が0より大きい値として取得できます。

###結果
『CRI ADX LipSync』のサンプルプロジェクトについてくる音声を解析してどのようなデータが得られるか調べてみました。サンプル音声は、女性の声で「私が死んだら、代わりはいるのでしょうか。」と話しているものを使いました。

まずは、分かりやすいので日本語５母音の組み合わせ情報の時間推移を示します。
![日本語五母音の組み合わせ情報.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/71f8727d-6971-8ef6-9d62-a0a37537204e.png)
図を見てみると、「あ」「い」「う」については比較的精度高く推定できていそうですが、この音声では「え」と「お」はほとんど検出されなかったようで、子音や前後の音との組み合わせによっては推定が難しいケースもあるようです。そもそも、フォルマント的にも各母音のフォルマントの分布は完全に分離されているという訳ではなく、一部が重複した状態で分布していることも、推定を誤ってしまう原因として考えられます。

次に、縦横形状の組み合わせ情報の時間推移を見てみます。
![縦横形状の組み合わせ情報（口の高さ）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/4361a985-b529-4857-8f53-2236ee772d21.png)
![縦横形状の組み合わせ情報（口の幅）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/285002f9-e65f-9132-0be2-994ac0f70442.png)
口の高さの推移の図を見てみると、口を縦に大きく開く「a」や「o」の音で極大となっており、全体的に良く追従できているのが分かります。さらに、図の赤丸で示した「だ」と「ら」の間では、一瞬口を閉じている状態のデータを返しており、唇音ではないが間で口を閉じる発音にも対応できていることが伺えます。


最後に、CriLipsAtomAnalyzerクラスのGetRmsメソッドから取得した音圧の強さを表すRMS(Root Means Square)値の時間推移です。
![RMS値（≒音圧の高さ）の推移.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/0642e318-2245-1372-c439-beb4980c13bb.png)
こちらのデータだけを使ってリップシンクをさせるのは難しそうですが、口形状情報と組み合わせて使うと音量による口の開き具合の影響度を大きくしたい場合などに役立つかもしれないです。CriLipsAtomAnalyzerクラスのGetVolumeメソッドでも音量(dB値)が取得できるようなので、今後そちらも検証してみたいと思います。

#まとめ
自然なリップシンクを自前で実装しようとすると、実装も難しく計算負荷も高くなってしまいがちですが、『CRI ADX2』による再生機構と『CRI ADX LipSync』が提供する解析機構を組み合わせて使うことで手軽に品質の高いリップシンクを実現することができそうだということが分かりました。
実機でのプロファイリングで問題がなければ、プロジェクトでの導入も検討していきたいです。

#参考
・[キャラクターをより魅力的に！ゲーム向けリップシンクミドルウェア](https://game.criware.jp/wp-content/uploads/2019/10/20190904_CRIWARE_CEDEC2019_handout.pdf)　CRIWARE公式ページより
・[音声解析リップシンクミドルウェアCRI ADX LipSync](https://game.criware.jp/products/adx-lipsync/#shiyou)

明日は @kojima_akira さんの記事です。