Gratias合同会社

トップ › 技術ブログ › 社内の AI ビデオチャットの開発事例

話して、口を動かし、表情を変える 3D アバター — 社内の AI ビデオチャットの開発事例

開発事例(AI・3DCG)Gratias 開発チーム

この記事は、当社が社内向けに開発し、社内で使っている AI ビデオチャットについて、開発チームが仕組みを解説する開発事例です。販売している製品ではありません。

ブラウザの画面に 3D のキャラクターがいて、マイクで話しかけると、声で返事をしながら口を動かし、話の中身に合わせて表情を変える。当社では、こうした AI ビデオチャットを社内で開発し、使っています。

難しかったのは「画面の中の相手が、人と話しているように見える」ことでした。この記事では、全体の流れに続いて、口の動き、表情、まばたきの作り方と、表情を出す時機で起きた問題とその直し方を紹介します。

全体の流れ — 聞く・考える・話す

1 回のやり取りは、次の順に進みます。

  1. 区切る:ブラウザがマイクの音の大きさを見て、話し始めと話し終わりを見分けます。
  2. 文字にする:話している途中は仮の文字を出し、話し終わったら精度の高い音声認識で文字にします。
  3. 考える:社内のサーバで動かしている大規模言語モデルに、キャラクターの人物像と会話の流れを添えて返事を作らせます。
  4. 話す:返事を 1 文ずつ音声合成で声にし、ブラウザで流しながら、声に合わせて口を動かします。

話し始めは、音の大きさが周りの雑音の 3.2 倍を超えた状態が 90 ミリ秒続いたとき、話し終わりは静かな状態が 0.7 秒続いたときです。話している間は 0.9 秒ごとに仮の文字を出すので、聞いてもらえていることが画面で分かります。

音声認識は 2 つを使い分けています。途中の仮の文字には、速さを重視して SenseVoice-Small を、話し終わりには Whisper large-v3-turbo を使います。カタカナ語や英語まじりの言葉が聞き取りにくいという声があり、音声合成で読み上げた 200 本の文で比べたところ、ねらいのカタカナ語・英語の言葉が正しく書けた割合は 90.9% から 98.1% に上がりました。ただし、これは合成した声での測定で、人の声ではまだ同じ形で測っていません。

返事は、大規模言語モデルが書き終わるのを待たずに、文の切れ目が来た所から順に声にします。こうすると、長い返事でも最初の声が早く出ます。

// 文の切れ目(読み上げを 1 文ずつ先に始める)。短すぎる切れは次とつなぐ
const SENT_END = /[。!?!?\n]/;
// …切り出した文 s が 6 文字未満なら、前の文の後ろに足す
if (done.length && s.length < 6) done[done.length - 1] += s;
else done.push(s);

アバターは VRM、描画は three.js と three-vrm

キャラクターには、人型の 3D アバターのための形式である VRM を使っています。VRM には骨組みのほかに、表情の形を名前付きで持つ決まりがあり、「まばたき(blink)」「あ・い・う・え・お(aa・ih・ou・ee・oh)」「喜び・怒り・悲しみ・くつろぎ・驚き(happy・angry・sad・relaxed・surprised)」といった名前で、0〜1 の強さを指定できます。アバターは当社の 3D 制作の仕組みで作ったもので、表情の形もその中で作っています。

描画には three.js(WebGPU で描く版)と、VRM を three.js で扱うためのライブラリ three-vrm を使い、口・表情・まばたきは、どれも毎コマこの強さを書き換えて動かしています。

口の動き — 声の響きから「あいうえお」を混ぜる

口の動きは、流している声をブラウザの中で分析して決めています。開き具合は声の大きさから決めます。口の形は、声の 2 つの響きの山(第 1・第 2 フォルマント)の位置を探し、日本語の 5 つの母音それぞれの代表的な位置にどれだけ近いかで決めています。

// 母音ごとの [第 1 の響き Hz, 第 2 の響き Hz, 開きの係数]。い・えは口を横に引くので開きを小さめに
const VOWELS = { aa: [780, 1250, 1], ih: [320, 2300, 0.55], ou: [360, 1350, 0.6],
                 ee: [500, 1950, 0.75], oh: [520, 900, 0.85] };
// 近いほど重く(1 つに決め打ちせず混ぜる)。最後に利用者の「口の動き」の倍率を掛け、1 を超えないようにする
w[k] = Math.exp(-d / 0.05);
target[k] = Math.min(1, (w[k] / sum) * open * VOWELS[k][2] * mouthScale);

母音を 1 つに決め打ちせず近さに応じて混ぜるのは、決め打ちすると形が切り替わる瞬間に口がパタパタと動いて見えるためです。また、話し手ごとの声の高さの違いを話しているうちに覚えて補正しています。女性の声は響きの位置が全体に高く、補正しないと「い」が「え」や「う」になってしまいました。口は開くときは速く、閉じるときは少しゆっくり動かします。

人によって、話すときの口の開き方は違います。そこで設定に「口の動き」を設け、40% から 140% の間で選べるようにしました(既定は 100%)。

表情 — 会話の中身から気持ちを選ぶ

表情は、会話の言葉から決めています。「嬉しい」「ありがとう」などがあれば喜び、「残念」「心配」などがあれば悲しみ、というように、キャラクターが話す文と相手の言葉の両方から、手がかりになる言葉で気持ちを選びます。

表情の強さは設定で変えられ、既定は 30% と控えめにしています。社内で使ってみて、日本人の会話では表情が抑えめなほうが自然だという意見が出たためです。ただし笑い声(「あはは」など)のときだけは、強さの設定にかかわらず、はっきりした笑顔にしています。30% のままだと、口が開くだけで笑顔に見えなかったからです。

相手が怒っているときに怒った顔を返すと、けんかをしているように見えます。相手の怒りには、困ったような顔(悲しみを弱く出す)で応えるようにしました。

表情を出す時機の問題と、その直し方

最初の版では、話す文の気持ちを、その文を話している間に顔に出していました。すると、「あいうえお」の口の動きと喜怒哀楽の表情が同時に顔に乗り、不自然に見えました。使った人からも「話すときはふつうの顔で、こちらの話を聞いているときに表情を変えてほしい」という声がありました。

そこで、次のように時機を分けました。

// 表情を出す割合: 話している間は 0 へ速く(毎秒 10)、それ以外は 1 へゆっくり(毎秒 3)
emoGate += ((speaking ? 0 : 1) - emoGate) * Math.min(1, dt * (speaking ? 10 : 3));
// 喜怒哀楽の強さ = 気持ちの量 × 表情の強さの設定 × emoGate
setExpr(k, mood[k] * s * emoGate);

話し終えた所で気持ちが複数重なっていたときは、1 つに絞ります。優先する順は「悲しみ > 怒り > 喜び > 驚き」です。たとえば「ええっ、大変だったね」と返したとき、驚きの顔が残るより、相手を気遣う顔のほうが会話として自然です。相手のつらさや不満に触れた返事の後で笑顔や驚き顔が出ると、話を聞いていないように見えてしまうため、気遣いにつながる気持ちを先にしています。

直す中で、つまずいた所が 2 つありました。

直した後に、表情の値を 0.2 秒ごとに読み取って確かめました(表情の強さ 100% のとき)。

送った言葉返事を考えている間話している間話し終えた後
「合格したよ!」喜び 0.70喜び 0喜び 0.80
「落ち込んでる」悲しみ 0.550悲しみ 0.60

なお、言葉の中の手がかりで気持ちを決めているため、皮肉や否定(「嬉しくない」など)はまだ取り違えます。今後の課題です。

まばたきと、小さな動き

止まっている CG は、すぐに CG だと分かります。そこで、まばたきや視線、呼吸の動きも付けています。

まばたきは、閉じるのに 70 ミリ秒、閉じたまま 40 ミリ秒、開くのに 130 ミリ秒かけます。次のまばたきまでの間隔は 3.8 秒に、ばらつき(平均 2.8 秒の指数分布、上限 7 秒)を足して決め、6% の確率で 2 回続けます。考え始めたときなどにも、ときどきまばたきを足します。

最初は 1 分に 24 回まばたきする決まりになっていて、使った人から「多い」と言われました。今は 1 分に約 10 回です(どちらも決まりから数えた値です)。

アバターによっては、まばたきを付けないこともあります。画像から生成した頭では、目に掛かる前髪がまぶたの肌の模様として描き込まれることがあり、まばたきをすると前髪までまぶたと一緒に下がって見えてしまいます。そうした頭では、まばたきの形をあえて作らないようにしています(作る・作らないを自動で判定し、手で選ぶこともできます)。

視線はときどき横や斜め下へ少しそらしてから相手に戻し、頭も 3 割ほど付いていくようにしています。相手が話している間は 2〜4.5 秒に 1 回小さくうなずき、約 4 秒の周期で胸と肩がわずかに動く呼吸も付けています。

まとめ

音声認識・大規模言語モデル・音声合成をつなぐだけでは、「人と話している」感じは出ませんでした。声の響きから母音を混ぜて口を動かし、話している間はふつうの顔で、話し終えた後や聞いている間に表情を見せ、まばたきや視線にほどよいばらつきを持たせる。一つひとつは小さな決まりですが、積み重ねることで画面の中の相手の印象が変わりました。

今後も社内で使いながら、言葉の受け取り方の精度などを改良していきます。

開発のご相談について

当社では、VR・AR・AI・3DCG を使ったシステムの開発や、業務での活用のご相談を承っています。この記事のような音声と 3D キャラクターを組み合わせた仕組みについても、お気軽にお問い合わせください。

お問い合わせ