LoginSignup
8
5

Live2DリアルアバターでChatGPTにて会話させてみた。

Last updated at Posted at 2023-04-16

ChatGPTを使って自分のリアルアバターにしゃべらせました。
この動画はSNS用に編集してるので、こんなにレスポンスは早くないです。

実際は、1度回答をオーディオファイル化・セーブするといった処理をしているので、返答まで5秒から10秒くらいと結構遅いです。
もっといい方法はあると思いますが、今回はここまで。


雑ですが、備忘録を兼ねてQiitaに残します。

環境・使用ソフト

※OSやバージョンは動作確認済みのもので、これに限りません。

アバター作成

自分の写真をフォトショップで開きます。

Image from Gyazo

レイヤーを複製して残しておきます。
Image from Gyazo

一番下のレイヤー(背景)をのっぺらぼうにします。

編集 > コンテンツに応じた塗りつぶし 
を使えばスムーズなのっぺらぼうができます。
怖い・・・。

Image from Gyazo

複製した元の写真から、まゆげ、目、鼻、口を切り取り、レイヤーにします。(アニメーションで動かすため)

Image from Gyazo

このまま福笑いアプリを作れそうですねw


PSDで保存します。

Live2D Cubism Editor

Live2D Cubism Editor を立ち上げ、
さっき作ったPSD ファイルを開きます。

眉、目、口を選択し、
「メッシュの自動生成」(下の画像参考)

Image from Gyazo

「変化度合小」で
Image from Gyazo

あとは、Live2Dのチュートリアルを参考に、まばたきや口の開閉などの動きを作ってください。
https://docs.live2d.com/cubism-editor-tutorials/import/

Unity

詳細はこちら
https://docs.live2d.com/cubism-sdk-tutorials/getting-started/

Unity の Projects > Assets に
Live2Dで生成した組み込みータをフォルダごと入れます。
LIve2Dで生成したフォルダにあるプレファブをシーンにドラッグ&ドロップします。

なぜか出てきません。

よく見ると、Inspectoar のMeshが反映されていません。

このLive2Dのフォーラムに「1度実行したら反映される」と書いてあります。

そのとおり1度実行(Run)すると、Unityに反映されました。

Image from Gyazo

リップシンクの設定をします。
やり方はここを見てください。
https://docs.live2d.com/cubism-sdk-tutorials/lipsync/

UI

テキスト入力し、ボタンを押したら、後述するスクリプト answer.cs が動く(アバターがChatGPTの回答を話す)ようにします。

Image from Gyazo

アバターのInspector

このとおり必要なコンポーネントを追加してください。

Image from Gyazo

Audio ファイル

アバターが回答する用のオーディオファイルを作成します。

空のオブジェクトを作成し、
「Audio Source」をアタッチしてください。
オーディオファイルはアタッチしなくてもいいです。
(プログラムでオーディオファイルを生成するので)
Image from Gyazo

VOICEVOX

アバターの声は、OSSのテキスト読み上げソフトウェアVOICEVOXを使います。

VOICEVOX をインストール
https://voicevox.hiroshiba.jp/

VOICEVOXのローカルサーバーを立てます。

ここを参考にしました。
https://qiita.com/yamanohappa/items/b75d069e3cb0708d8709

コマンドプロンプトでVOICEVOX のフォルダへ移動

自分のPCのIPアドレスを確認

$ ipconfig

Voicevox をコマンドプロンプトから起動 (ポートは50021)

$ run.exe --host XXX.XXX.X.XX --port 50021

サーバー起動
Image from Gyazo

サーバーが立ち上がってるか確認
http://XXX.XXX.X.XX:50021/docs

こういう画面が出たらOK
Image from Gyazo

コード

まず、JSON Object アセットをインストールします。
https://assetstore.unity.com/packages/tools/input-management/json-object-710?locale=ja-JP

以下のGithubから非同期処理のライブラリ 「Unitask」 をインポートします。
https://github.com/Cysharp/UniTask/releases

以下のGithubから、録音・再生するプログラム WavUtility.cs をUnityのProjects > Assetsへ コピペすします。
https://github.com/deadlyfingers/UnityWav

AIの回答をVoiceBoxにあげてリップシンクで回答するコードはこちらのブログの
クラス VoiceVoxConnection をそのまま使いました。
https://note.com/negipoyoc/n/n081e25f5ee9e

メインのプログラムは以下です。

テキスト入力された言葉をOpenAIのAPIに投げて、その回答を録音し、アバターオブジェクトの Cubism Audio Mouth Input のAudio Inputにアタッチし、VoiceVoxConnectionクラスで読み上げています。

1度録音、アタッチしてるので処理は遅いです。

なお、OpenAIのAPIキーの取得方法はこちらをご参照ください。
https://qiita.com/tatsuya1970/items/c04cb7b6d07c4cf51007#3-openai

answer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System;
using Defective.JSON;
using TMPro;
using System.Threading.Tasks;
using System.IO;
using System.Text;

public class answer : MonoBehaviour
{
    public Button button;          // OKボタン
    public GameObject inputField;  //ユーザー入力するフィールド
    AudioSource audioSource;
    public GameObject audioObject;

    [SerializeField]
    TextMeshProUGUI responseText;
    VoiceVoxConnection _voiceVoxConnection;

    //OpenAIのAPIにリクエストするとき必要
    [Serializable]
    public class body
    {
        public string model;
        public Transfers[] messages;
    }

    [Serializable]
    public class Transfers
    {
        public string role;
        public string content;
    }

    public async void OnClick()
    {
        animator = GameObject.Find("man2").GetComponent<Animator>();

        //ユーザーが投稿した内容を取得
        string mes = inputField.GetComponent<Text>().text;

        var url = "https://api.openai.com/v1/chat/completions";

        // ボットの性格を設定
        string chara = "ボットの性格を入れる";

        string itemJson
            = "{ \"model\": \"gpt-3.5-turbo\"," +
            "\"messages\": [" +
            "{ \"role\": \"system\", \"content\": \"" + chara + "\"}," +
            "{ \"role\": \"user\", \"content\": \"" + mes + "\"}]}";

        body item = JsonUtility.FromJson<body>(itemJson);
        string serialisedItemJson = JsonUtility.ToJson(item);
        UnityWebRequest request = new UnityWebRequest(url, "POST");
        byte[] postData = System.Text.Encoding.UTF8.GetBytes(serialisedItemJson);
        request.uploadHandler = new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        
        request.SetRequestHeader("Authorization", "Bearer OpenAIのAPIキー");

        var operation = request.SendWebRequest();

        while (!operation.isDone)
        {
            await Task.Delay(10);
        }

        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.Log(request.error);
        }
        else
        {
            Debug.Log(request.responseCode);

            if (request.responseCode == 200 || request.responseCode == 201)
            {
                string text = request.downloadHandler.text;
                Debug.Log(text);
                JSONObject json = new JSONObject(text);
                JSONObject choices = json.GetField("choices");
                JSONObject message = choices[0].GetField("message");
                string content = message.GetField("content").ToString();
                //Debug.Log(content);
                responseText.text = "考え中・・・";
                string content2 = content.Replace("\"", "");
                _voiceVoxConnection = new VoiceVoxConnection(13);  //声はここの整数
                // 1-10:女性、11以降は男性 13:渋い男 14:女の子っぽい男の子? 15:女性 
                
                //録音
                var clip = await _voiceVoxConnection.TranslateTextToAudioClip(content2);
                string filename = "Assets/Audio/sound.wav";
                SaveWav(filename, clip);
      
                // WAVファイルを読み込む
                byte[] fileData = File.ReadAllBytes(filename);
                // WAVファイルのデータ部分をバッファにコピー
                float[] samples = ConvertBytesToFloat(fileData);
                // AudioClipを生成           
                AudioClip clip2 = AudioClip.Create("audio", samples.Length, 1, 44100, false);
                responseText.text = "TatsuyaGPT";
                GameObject.Find("Audio").GetComponent<AudioSource>().clip = clip;
                clip2.SetData(samples, 0);
                audioSource = GameObject.Find("Audio").GetComponent<AudioSource>();
                audioSource.Play();
                await Task.Delay(3000);
            }
            else
            {
                responseText.text = request.responseCode + "エラーだよ!!\nどこかが間違ってるよ。";
                Debug.Log("エラー");
                await Task.Delay(3000);
            }
        }
    }

    //ここから下は全部ChatGPTに作ってもらった
    private void SaveWav(string filename, AudioClip clip)
    {
        // WAVファイルのデータ部分を作成
        byte[] data = CreateWavData(clip);

        // WAVファイルの先頭部分を作成
        byte[] header = CreateWavHeader(clip.frequency, clip.channels, data.Length / 2);

        // WAVファイルの先頭部分とデータ部分を合体
        byte[] fileData = new byte[header.Length + data.Length];
        header.CopyTo(fileData, 0);
        data.CopyTo(fileData, header.Length);

        // WAVファイルを保存
        File.WriteAllBytes(filename, fileData);
    }


    private byte[] CreateWavData(AudioClip clip)
    {
        // AudioClipのデータをバッファにコピー
        float[] samples = new float[clip.samples];
        clip.GetData(samples, 0);

        // ビット深度に応じてデータを変換
        byte[] data;

        // WAVファイルのヘッダーを作成
        byte[] header = CreateWavHeader(clip.channels, clip.frequency, samples.Length);

        // WAVデータを作成
        byte[] wavData = ConvertFloatTo16BitWav(samples);

        // ヘッダーとデータを結合
        data = new byte[header.Length + wavData.Length];
        header.CopyTo(data, 0);
        wavData.CopyTo(data, header.Length);

        return data;
    }


    private byte[] ConvertFloatTo16BitWav(float[] samples)
    {
        int sampleCount = samples.Length;
        byte[] data = new byte[sampleCount * 2];

        int i = 0;
        while (i < sampleCount)
        {
            short value = (short)(samples[i] * 32767f);
            data[i * 2] = (byte)value;
            data[i * 2 + 1] = (byte)(value >> 8);
            i++;
        }

        return data;
    }

    private byte[] CreateWavHeader(int sampleRate, int channelCount, int sampleCount)
    {
        // フォーマットID
        ushort formatId = 1;

        // ブロックサイズ (byte/sample * channel)
        ushort blockSize = (ushort)(2 * channelCount);

        // データレート (byte/second)
        int dataRate = sampleRate * blockSize;

        // サイズ
        int size = 36 + sampleCount * blockSize;

        // ヘッダーを作成
        byte[] header = new byte[44];
        Encoding.ASCII.GetBytes("RIFF").CopyTo(header, 0);
        BitConverter.GetBytes(size).CopyTo(header, 4);
        Encoding.ASCII.GetBytes("WAVE").CopyTo(header, 8);
        Encoding.ASCII.GetBytes("fmt ").CopyTo(header, 12);
        BitConverter.GetBytes(16).CopyTo(header, 16);
        BitConverter.GetBytes(formatId).CopyTo(header, 20);
        BitConverter.GetBytes(channelCount).CopyTo(header, 22);
        BitConverter.GetBytes(sampleRate).CopyTo(header, 24);
        BitConverter.GetBytes(dataRate).CopyTo(header, 28);
        BitConverter.GetBytes(blockSize).CopyTo(header, 32);
        BitConverter.GetBytes(16).CopyTo(header, 34);
        Encoding.ASCII.GetBytes("data").CopyTo(header, 36);
        BitConverter.GetBytes(sampleCount * blockSize).CopyTo(header, 40);

        return header;
    }

    private static float[] ConvertBytesToFloat(byte[] bytes)
    {
        float[] samples = new float[bytes.Length / 2];

        for (int i = 0; i < samples.Length; i++)
        {
            samples[i] = BitConverter.ToInt16(bytes, i * 2) / 32768.0f;
        }

        return samples;
    }

}

VOICEVOXのローカルサーバーを立ち上げて、UnityをRUNすれば、リアルアバターと会話できます。


(参考)ChatGPTとの対話によるコーディング

余談になりますが、
このプログラムは以前作成した CatGPT のプログラムをもとに、ChatGPTと対話を重ねながら作りました。

こんな感じです。
まず、CatGPT はコルーティンを使ってたので、まずは、 async/await 形式に直してもらいました。
Image from Gyazo

エラーが出るたびに
対話を続けていきます。
AIも一発で完璧なコードは作れません。
Image from Gyazo

Image from Gyazo

Image from Gyazo

このようなやりとりを4回繰り返すと、async/await 形式 のプログラムに変換できました。

これから、さらに13回やりとりして、VOICEVOX で返答するコードが完成しました。

ChatGPTがなければできませんでした。

ですので、本コードはChatGPTにいわれたとおりに作ったので、細かいところの意味はよく分かってません。

参考サイト

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5