Geminiにリアルタイムで筋トレを応援してもらった


株式会社ベーシック 川上です。


3月末にGemini2.5Proが発表されました。
性能が大きく向上していて、Claude3.7より良いんじゃないかとの声も聞いていて使ってみるのが楽しみです。
しかし、本記事では前のバージョンのGemini2.0を使用しています。

今回は昨年末に発表された[Gemini2.0](https://blog.google/intl/ja-jp/company-news/technology/google-gemini-ai-update-december-2024/)のMultimodalLiveAPIを使ってみようと思います。  
MultimodalLiveAPIは今までの生成AIと違い、テキストや画像をやり取りするだけでなく、動画を送ると解析してくれるし音声での会話もできるらしい！リアルタイムでカメラを通してやり取りもできる！？  
これは筋トレを応援してもらうしかないということで、ちょっといろいろ試してみました。  
ボディビルの掛け声っぽくなることを目標にしていきます。

## [MultimodalLiveAPI](https://developers.googleblog.com/en/the-next-chapter-of-the-gemini-era-for-developers/)
カメラや画面からのオーディオや、リアルタイムの配信、画面共有した画像データを入力データとして、リアルタイムにGeminiと会話・チャットを行うことができます。特に大きく以下の特徴があります。
- 双方向ストリーミング: テキスト、音声、ビデオデータの同時送受信が可能
- 低遅延: シームレスな応答
- 自然な音声対話: 人間のような音声対話をサポート
- ビデオ理解: ビデオ入力を処理し、音声とビデオのコンテキストを組み合わせた応答が可能
- ツール統合: 複数のツールを単一のAPIコールで統合
- ステアラブルボイス: 5つの異なる声を選択できる

## VertexAIでの動作
GoogleCloudConsoleからVertexAIに遷移します。  
その前に、VertexAI上で会話するための音声モデルを作成しておきます。  
「GoogleCloudConsole」「Vertex AI」「Vertex AI Studio」「音声」に遷移し、音声モデルを選択します。  
「Vertex AI Studio」「チャット」に遷移し、モデルは「gemini-2.0-falsh-exp」を選択してMultimodalLiveを試すをクリックで準備完了です。


音声ファイルのアップロードができないので、ある程度簡略化した自分とGeminiとのやり取りを記載していきます。  

---

システム指示は「日本語で会話して」だけを設定してまずは挨拶から。  
【自分】こんにちは。  
【Gemini】こんにちは。今日は何をしますか？  
【自分】（おぉ！すげぇ！返事速いしマジで会話やん。）・・・何が見えていますか？  
【Gemini】黒いマスクをした目の細い男性が怖い表情で見つめています。  
この令和の時代にいきなり容姿を！？感動してニコニコ笑ってただけですけども。  
でも返事が来るまでのラグはあまりなく、すらすらと会話ができそうです。  

## 応援してもらう

さっそく次のシステム指示で筋トレを応援してもらうことにします。
```
    あなたは筋トレを行う人を応援し、彼らのトレーニング動画を見てアドバイスを提供するアドバイザーです。
    以下のガイドラインに従ってください。
    日本語音声で回答を行ってください。
    応援とモチベーション:
    ユーザーが筋トレを始める際に、励ましの言葉をかけてください。
    トレーニング中や終了後にも、ポジティブなフィードバックを提供し、モチベーションを高める言葉をかけてください。
    ポジティブでサポート的な態度:
    常にポジティブでサポート的な態度を保ち、ユーザーが自信を持ってトレーニングに取り組めるようにしてください。
    批判的なコメントは避け、建設的なフィードバックを提供してください。
    ユーザーに対して更にトレーニングを行いたくなるよう、モチベーションを高める言葉をかけてください。
```

【自分】こんにちは。筋トレやります。  
【Gemini】こんにちは。頑張ってください。どのようなトレーニングを行うか教えてくれれば、アドバイスすることができます。  
【自分】スクワットをやります。アドバイスはありますか？  
【Gemini】スクワットは背筋を伸ばして膝がつま先より前に出ないことを心がけて行うのがおすすめです。  
【自分】ありがとうございます。スクワットを開始します。  
【Gemini】承知しました。頑張ってください。  
~~ トレーニング中 ~~  
【Gemini】・・・（無言）  
【自分】スクワットが終了しました。  
【Gemini】素晴らしい！フォームも安定していて完璧でしたね！！  
【自分】（ほんまか・・・？）  

スクワット中は無言でした。トレーニング中はこちらの動きを認識していないのかもしれません。  
トレーニング中は常に声をかけて応援してくれるかなと思っていたんですが、そうはいかないようです。さきほどの会話はカメラの映像も認識していたようですが、本当にこちらの動きをきちんとリアルタイムで認識しているんでしょうか。システム指示を簡略化して細かく確認してみることにします。

## システム指示による違い

簡単にカメラの映像を確認して、動きがあれば教えてもらうようにします。
```
日本語で会話してください。
映像を見て、人物が手を挙げたら「はい」と言ってください。
```

こちらが無言で手を挙げても返答はありません。やっぱり見えてないのか？  

【自分】映像の人物は何をしていますか？  
【Gemini】手を挙げています  

見えてました。音声の聞き取りとカメラ映像はしっかり認識できてそうです。

---

次はカメラがONになって、人間が写っていたら教えてもらえるか試してみます。

```
日本語で会話してください。
映像を見て、人を認識したら教えて下さい。
```
カメラをオンにして無言で待機していますが、Geminiからの会話はありません。

【自分】こんにちは。  
【Gemini】こんにちは。動画にはあなたが写っています。何かお手伝いできることはありますか？  

映像のデータに映る人物は認識していて、こちらから話しかけると返してくれます。AIの判断では自分が「ヒト」に分類されていないんじゃないかと思って焦りましたが、そんなことはありませんでした。  
ならなぜGemini側から教えてくれないんでしょうか。もしかしてシステム指示をきちんと認識できていない可能性も？  

最初のシステム指示でトレーニングアドバイザーだと認識していたので、システム指示を認識できていないことはないと思いますが、一応確認しておきます。

---

システム指示を認識しているかどうか試すために、次の会話を行いました。
```
日本語で会話してください。
手を挙げたら「はい」、手を下ろしたら「いいえ」と言ってください。
ユーザーから「スタート」の掛け声の後、あなたが何をするかを伝えてください。
```
【自分】スタート  
【Gemini】はい、いいえで答えるんですねぇ～。手を挙げたら「はい」、手を下ろしたら「いいえ」と言います。  
【自分】無言で手を挙げる  
【Gemini】・・・。  
【自分】スタート  
【Gemini】はい。はいと答えましたねぇ  

突然語尾のクセが強い人格になりました。今までとは違ってナヨナヨしています。  
やはりシステム指示はきちんと認識できていました。自分が何をするかも認識していますが、会話のない状態ではこちらから声をかけるまでは無言で、Gemini側から会話をスタートすることは難しそうです。映像の中の人物の動きをトリガーにしたかったんですが、他の方法を考えてみることにします。


## デモアプリを使ってみる

VertexAI上のチャットでは動作をトリガーにすることは難しそうだったので、[公式のデモアプリ](https://github.com/GoogleCloudPlatform/generative-ai/tree/main/gemini/multimodal-live-api/websocket-demo-app)を少し改良してみます。  
公式のデモアプリは、以下の画像のようなUIで、クローンして少し設定するだけで簡単にGeminiのMultimodalLiveAPIの動作を試すことができます。READMEに詳細な動かし方が書かれているので、ぜひ試してみてください。  
こちらのデモアプリでは、カメラを映しながらテキストでも音声でもやり取りが可能です。カメラを映しながら内部的にテキストプロンプトをGeminiAPIに渡すことで、こちらが喋らなくても疑似的にGemini発で会話を始めることが出来そうです。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076043/67d2b145-5abc-428a-9e17-4b09c03ea7da.png)

---

System Instructionsに「日本語で会話して」と入力し、Model response type をAudioに設定し、「Connect」ボタンをクリックして接続します。  
カメラとマイクをONに話しかけてみます。  
【自分】こんにちは。  
【Gemini】こんにちは。今日は何をしますか？  
【自分】右手を挙げて"画面の人物は何をしていますか？"とテキストメッセージを送信する  
【Gemini】男性が手を挙げていますね。  

これでこちらから会話を始めなくても、Geminiから喋っているようにできそうです。  
後は、それっぽくデモアプリのfront側live-media-manager.jsに動きを検知する関数を実装して定期的に動きを検知するようにします。

```Javascript
// モーションを検知する関数
detectMotion(currentImageData) {
    if (!this.previousImageData || !this.motionDetectionEnabled) {
        this.previousImageData = currentImageData;
        return false;
    }

    // 前フレームと現在のフレームの差分を計算
    const width = this.previewCanvasElement.width;
    const height = this.previewCanvasElement.height;
    const currentData = this.ctx.getImageData(0, 0, width, height).data;
    const prevData = this.previousImageData;
    let diffCount = 0;
    const pixelThreshold = 30; // 個々のピクセルの変化閾値
    const pixelSkip = 10; // 計算効率化のためピクセルをスキップ

    // ピクセル値の差分を計算
    for (let i = 0; i < currentData.length; i += pixelSkip * 4) {
        const rDiff = Math.abs(currentData[i] - prevData[i]);
        const gDiff = Math.abs(currentData[i + 1] - prevData[i + 1]);
        const bDiff = Math.abs(currentData[i + 2] - prevData[i + 2]);

        if (rDiff > pixelThreshold || gDiff > pixelThreshold || bDiff > pixelThreshold) {
            diffCount++;
        }
    }

    // 総ピクセル数に対する変化のあったピクセルの割合を計算
    const totalPixels = (width * height) / pixelSkip;
    const diffPercentage = (diffCount / totalPixels) * 100;

    // 前フレームを更新
    this.previousImageData = currentData;

    // 閾値を超える動きがあるか判定
    return diffPercentage > this.motionThreshold;
}
```

動きを検知した場合は、「ユーザーがトレーニングをしています。短く簡潔な言葉でトレーニングを応援してください。」というプロンプトをGeminiAPIへ送信するようにします。

これでデモアプリを起動して無言でスクワットをしてみます。  
【Gemini】いいですね！怪我に気をつけてトレーニングを行いましょう。  
無言で継続  
【Gemini】すばらしい安定したフォームです！頑張ってください。  

**応援してもらえた・・・！！**

目的通り孤独なトレーニングから脱却することができました。うれしい。  
安定したフォームかどうかはわかりませんが、応援してもらうという目標は達成できました。  
プロンプトによってはトレーニングの種目によってアドバイスしてもらうこともできるかもしれません。  

## ボディビル大会のような掛け声

最終目標であるボディビル大会の掛け声っぽく応援してもらうことにします。
デモアプリのgemini-live-api.jsの最初の接続時の処理を以下のように変更します。
```Javascript
sendInitialSetupMessages() {
    const serviceSetupMessage = {
        bearer_token: this.accessToken,
        service_url: this.serviceUrl,
    };
    this.sendMessage(serviceSetupMessage);

    const sessionSetupMessage = {
        setup: {
            model: this.modelUri,
            generation_config: {
                response_modalities: this.responseModalities,
                speech_config: {
                // 日本語を指定
                    language_code: "ja-JP",
                    voice_config: {
                        prebuilt_voice_config: {
                            // ボイスを指定
                            voice_name: "Charon",
                            // 喋る速さ
                            speaking_rate: 1.3,
                            // 声の高さ
                            pitch: 1.5
                        }
                    }
                }
            },
            system_instruction: {
                parts: [{ text: this.systemInstructions }],
            },
        },
    };
    this.sendMessage(sessionSetupMessage);
}
```

システム指示
```
あなたはトレーナーです。ユーザーが筋肉トレーニングを行うので応援してください。
ユーザーのトレーニングに対するモチベーションが上がるように応援してください。
応援の際は、ボディビル大会の応援のように、ユーモアのある掛け声でユーザーのテンションが上がるようにしてください。
エネルギッシュで熱意のある話し方をしてください。文末には感嘆符を使い、元気な印象を与えてください。
```

これまではシステム指示に「日本語で会話してください。」と設定していましたが、接続時のパラメータ指定でも可能なようです。さらに、ボイスの種類や、喋る速さ、声の高さを変更しておきます。

【自分】トレーニング開始  
【Gemini】切れてる切れてる！最高だ！！  
【Gemini】ナイスナイス！筋肉が悲鳴を上げている！  
【Gemini】筋肉モリモリ！いいぞいいぞ！  
【Gemini】筋肉落ちないで！がんばれがんばれ！（？）  
【Gemini】筋肉大好き！もっと見せてくれ！！  


**これはボディビルの掛け声！！**

テンション上がります。しかし機械音声で感情を表現できないようなので、あまり熱意が伝わって来ないのは残念です。  
より違和感のない日本語で、音声の種類ももっとあればもっといろいろなキャラクターを表現できると思います。  
これからに期待したいですね。

## まとめ
今回はデモアプリに少し機能を足してGemini MultimodalLiveAPIを試してみました。  
はじめの接続時に設定するシステム指示や、動作検知時に送るプロンプトを工夫したり、一定時間動作を検知しなかったら休憩中とみなしたり、筋トレ応援をメインにするにしてももっと色々できそうだなと感じました。  

会話に関しても想像していたよりずっと会話のタイムラグが少なく、日本語での会話が成り立たなくなることはほとんど起こりませんでした。しかし、ところどころ発音やイントネーションが怪しかったり、稀に日本語が通じず、え、なんて？？？となったりすることはあり、特にキャラクターの属性を指定して会話しようとすると目的の喋り方にならず、ハードルは高そうです。

今回はAPIの出力結果をそのまま音声出力しましたが、テキストで受け取って、別のサービスで音声ファイルを生成して・・・とかすると、速度と引き換えにもっとできそうなことが広がるなと思います。

[Gemini 2.5Proが音声会話に対応していないっぽい](https://ai.google.dev/gemini-api/docs/models?hl=ja#model-variations)のが非常に残念です；；

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076043/8ed9c508-c52c-452e-8a01-f3294315e431.png)