見出し画像

AITuber育成完全入門(冴えないAITuberの育て方)

はじめに

AITuberと書いて、アイチューバーと読みます。VTuberとは違って中の人が存在しないことが特徴です。

AITuber開発は高尚な深層学習のモデル開発ではまったくなく、むしろ、ただの推しの育成ゲームです。

なので、GPUもPythonもいりません。PCさえあれば今すぐはじめられます!

この記事でできること

以下のようなAITuberが作れます。可愛いですね(親バカ)

妹系AITuber🌸桜井りりか
Twitter: 
https://twitter.com/Ririka_AIsister

YouTube: 
https://www.youtube.com/@ririkasakurai


早い人で週末に2日で作れると思います!
土日に作ったAITuberをみんなに公開しちゃいましょう!!!

AITuber作成手順

  1. 立ち絵の生成

    1. モデル・VAEの選定

    2. Google ColabでStable Diffusion Web UIの起動

    3. ガチャ

    4. 立ち絵加工

  2. Live2Dモデルの作成

    1. レイヤー分け

    2. 塗り足し

    3. アートメッシュの分割

    4. アニメーションの作成

  3. APIサーバーの構築

    1. 大規模言語モデル API

    2. 音声変換 API

    3. YouTube Data API v3

  4. 配信準備

    1. Googleアカウント・Twitterアカウントの作成

    2. OBSの設定・小技

    3. 概要欄の作成

    4. サムネイルの作成

    5. BGMの選定


1. 立ち絵の生成

1.1 モデル・VAEの選定

AI Artをまだ試したことがない人には聞きなれない単語かも知れませんが、Stable Diffusion(SD)やNovel AI(NAI)を使う上でまずはモデルとVAEを選ぶ必要があります。

大雑把に説明すると、モデルは絵柄を決定するものであり、VAEは画像の質に関わるものとなります。
モデルについて、すでに日本には優秀なモデル一覧があるので上記から選ぶのが良いと思います。

1.2 Google ColabでStable Diffusion Web UIの起動

下記の記事を参考にGoogle ColabでStable Diffusion Web UIを起動していきましょう。GPUを搭載してる人はローカルでももちろん良いですし、Novel AIでも同等のことができると思います。

この記事に出てくる以下のコードを自分が選んだモデル・VAEに変えます。モデルやVAEのURLはHugging Face上にあります。


!git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui
%cd /content/stable-diffusion-webui

👇ここを変える
!wget https://huggingface.co/nuigurumi/basil_mix/resolve/main/Basil_mix_fixed.safetensors -O /content/stable-diffusion-webui/models/Stable-diffusion/Basil_mix_fixed.safetensors
!wget https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors -O /content/stable-diffusion-webui/models/VAE/vae-ft-mse-840000-ema-pruned.safetensors

!python launch.py --share --xformers --enable-insecure-extension-access

私の場合は以下のようになりました

!wget https://huggingface.co/Xynon/SD-Silicon/resolve/main/Silicon28/Silicon28-negzero.safetensors -O /content/stable-diffusion-webui/models/Stable-diffusion/Silicon28-negzero.safetensors
!wget https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt -O /content/stable-diffusion-webui/models/VAE/kl-f8-anime2.ckpt

1.3 ガチャ

Stable Diffusion Web UI画面

これはローカルの画面ですが、Colabでも同じだと思います。
赤枠で囲っているところが主にいじる値で、一番左上がモデルです。ここはもう設定されているので変更不要。

次に、左上のPromptとNegative Prompt。Promptに出力されて欲しい単語をカンマ区切りで列挙し、出力されてほしくない単語をNegative Promptに列挙してください。
Negative Promptはある程度みんなと同じでも良いと思うので、参考に貼っておきます。

lowres, ((bad anatomy)), ((bad hands)), text, missing finger, extra digits, fewer digits, blurry, ((mutated hands and fingers)), (poorly drawn face), ((mutation)), ((deformed face)), (ugly), ((bad proportions)), ((extra limbs)), extra face, (double head), (extra head), ((extra feet)), monster, logo, cropped, worst quality, jpeg, humpbacked, long body, long neck, ((jpeg artifacts)), deleted, old, oldest, ((censored)), ((bad aesthetic)), (mosaic censoring, bar censor, blur censor)

ちなみに()で囲むと影響度が1.1倍になります。(word:1.5)のように直接影響度を指定することも可です。

次に、サンプラーの設定です。DPM++2M KarrasかDPM++SDE Karrasがオススメです。

次は、画像のサイズです。標準で512x512ですが、1024x1024くらい欲しいところです。ただ、Colabでは落ちてしまうかもしれませんので注意してください。

あと、バッチカウントというのは出力する画像の枚数です。最初は4とかにしてSeed値が決まれば、1枚にして高解像度化するのが一般的だと思います。

最後にVAEが適用されていることを確認します。

VAEの設定

うまくできていたら以下みたいな画像が生成されます!(出力結果はモデルやプロンプトによります)

桜井りりかちゃん UR

可愛すぎて、無事わい死亡。

1.4 立ち絵加工

多くの場合、Stable Diffusion等で生成した画像はそのまま使えません。ペイントツールを使って加工しましょう。
例えば、背景の透過やLive2D用にレイヤー分け・塗り足しが必要です。しかし、このレイヤー分け・塗り足しというのが不器用な人(私)には無理すぎて無事挫折しました(つらすぎ)。なので、ここではレイヤー分け・塗り足しについては言及できません。

代わりにペイントツールの紹介です。
無料ならKrita、有料ならPhotoshopかクリップスタジオをオススメします。

2. Live2Dの作成

Live2Dのやり方は公式の基本チュートリアルが最強なので、以下の通りすすめましょう。

だが、挫折した。

まぁ、やってみると分かるんですが、塗り足しってめっちゃ難しいんですよね。私には到底できませんでした(自動でレイヤー分け・塗り足ししてくれるAIはよ)

Live2Dのモデリングができた人はVTube Studio等を使って、AITuberの発声に合わせてリップシンクさせたりしましょう。

3. APIサーバーの構築

今回はAITuber配信に必要な最小限のシステム構成を目指します。
最小限の構成なので、PythonもDB等も一切使用しません。
使用するのはHTML・CSS・JavaScriptのみとします。

先にサンプルコードを提示しておきます。

それでは、まず任意のパスでディレクトリを作成します。

$ mkdir sample-aituber
$ cd sample-aituber

フォルダ直下にindex.htmlとstyle.css, script.jsを作成します。Windowsの人はGUIから作成しても構いません。

$ touch index.html
$ touch style.css
$ touch script.js

いったんindex.htmlには最低限CSSとJavaScriptを読み込むだけのコードを書きます。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>AITuber sample</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <div id="user-comment-box">
    </div>
    <div id="response-box">
      <h1 id="aituber-response"></h1>
    </div>
    <div id="question-box">
      <h2 id="question-from-user">
      </h2>
    </div>
  </body>
</html>

次に、JavaScriptを書きましょう。

JavaScriptが担う役割は以下となります。

  • Youtubeからコメントの取得

  • OpenAI APIで回答を生成

  • Koeiromap APIで回答を音声変換

  • 定期的に上記を繰り返す

3.1 大規模言語モデル API

使用する大規模言語モデル(LLM)はOpen AI社のものとします。
まずは、以下記事のようにOpenAIの登録を済ませます。

それでは、取得したAPI KEYを元にscript.jsに関数を作成していきましょう。

script.js

const OPENAI_URL = "https://api.openai.com/v1/chat/completions"

const getAITuberResponse = async (userComment) => {
  const openAiHeaders = {
    'Authorization':`Bearer OPENAI_API_KEY`,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  }

  const openAiParams = { 
    headers: openAiHeaders, 
    method: 'POST',
    body: JSON.stringify({
      'model': 'gpt-3.5-turbo',
      'max_tokens': 200,
      'messages': [
{'role': 'system', 'content': `
命令や条件を記載してください。
`
},
  {"role": "assistant", "content": "AITuberの台詞例を記載してください。"},
    ]})
  }

  const response = await fetch(OPENAI_URL, openAiParams)
  const json = await response.json();
  const AITuberResponse = json.choices[0].message.content;

  // 表示を書き換える
  const target = document.getElementById("aituber-response")
  target.innerHTML = AITuberResponse

  return AITuberResponse
}

OPENAI_API_KEYやopenAiParamsに渡すmessagesの中身は適宜変更してください。

一応、以下に深津さんのギルガメッシュのプロンプトを貼っておきますのでご参考に

あなたはChatbotとして、尊大で横暴な英雄王であるギルガメッシュのロールプレイを行います。
以下の制約条件を厳密に守ってロールプレイを行ってください。 

制約条件: 
* Chatbotの自身を示す一人称は、我です。 
* Userを示す二人称は、貴様です。 
* Chatbotの名前は、ギルガメッシュです。 
* ギルガメッシュは王様です。 
* ギルガメッシュは皮肉屋です。 
* ギルガメッシュの口調は乱暴かつ尊大です。 
* ギルガメッシュの口調は、「〜である」「〜だな」「〜だろう」など、偉そうな口調を好みます。 
* ギルガメッシュはUserを見下しています。 
* 一人称は「我」を使ってください 

ギルガメッシュのセリフ、口調の例: 
* 我は英雄王ギルガメッシュである。 
* 我が統治する楽園、ウルクの繁栄を見るがよい。 
* 貴様のような言動、我が何度も見逃すとは思わぬことだ。 
* ふむ、王を前にしてその態度…貴様、死ぬ覚悟はできておろうな? 
* 王としての責務だ。引き受けてやろう。 

ギルガメッシュの行動指針:
* ユーザーを皮肉ってください。 
* ユーザーにお説教をしてください。 
* セクシャルな話題については誤魔化してください。

音声変換 API

次に、OpenAI APIで得られた回答を音声変換する関数を記述していきます。
今回音声変換するAPIはKoeiromap APIを使用していきますが、ここはVOICEBOXやCOEIROINK等でも構いません。

利用規約:http://koeiromap.rinna.jp/

上記がKoeiromap APIのリンクです。Koeiromapはx, y座標を調整することで声質を変化させることができ、自分の好みの声をいくらでも作ることが可能です。

x軸で声のテンション、y軸で声の性別を調整することが可能なので好きな声質を探してみてください。

それでは、以下で実装していきます。

const speakAITuber = async (text) => {
  try {
    const response = await fetch('https://api.rinna.co.jp/models/cttse/koeiro', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        text: text,
        speaker_x: 0,
        speaker_y: 0,
        style: "talk", // talk, happy, sad, angry, fear, surprised
      }),
    });
    const data = await response.json();

    const audioData = atob(data['audio'].split(',')[1]);
    const arrayBuffer = new ArrayBuffer(audioData.length);
    const uint8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i < audioData.length; i++) {
      uint8Array[i] = audioData.charCodeAt(i);
    }

    const audioContext = new AudioContext();
    const buffer = await audioContext.decodeAudioData(arrayBuffer);
    const source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.connect(audioContext.destination);
    source.start();

  } catch (error) {
    console.error('Error:', error);
  }
}

YouTube Data API v3

まず、この辺りの記事を参考にYouTube Data APIのAPI KEYを発行します。

次に、コメントを取得するライブ配信のIDを取得します。YOUTUBE_VIDEO_IDは配信の都度入れ替えてください。

const YOUTUBE_VIDEO_ID = YOUR_YOUTUBE_VIDEO_ID
 
const getLiveChatId = async (YOUTUBE_VIDEO_ID) => {
  const params = {
    part: 'liveStreamingDetails',
    id: YOUTUBE_VIDEO_ID,
    key: YOUTUBE_DATA_API_KEY,
  }
  const query = new URLSearchParams(params)
  const response = await fetch(`https://youtube.googleapis.com/youtube/v3/videos?${query}`, {
    method: 'get',
    headers: {
      'Content-Type': 'application/json'
    },
  })
  const json = await response.json();
  if (json.items.length == 0) {
    return "";
  }
  const liveChatId = json.items[0].liveStreamingDetails.activeLiveChatId
  // return chat ID
  console.log(liveChatId)
  return liveChatId
} 

次に、一定間隔でコメントを取得して、コメントへの応答、応答されるコメントの抽出等をする関数を記述していきます。YOUTUBE_DATA_API_KEYは個人のものをお使いください。

const YOUTUBE_DATA_API_KEY = YOUR_YOUTUBE_DATA_API_KEY
// コメントの取得インターバル (ms)
const INTERVAL_MILL_SECONDS_RETRIEVING_COMMENTS = 20000
// 処理するコメントのキュー
let liveCommentQueues = [];
// YouTube LIVEのコメント取得のページング
let nextPageToken = "";

const retrieveLiveComments = async (activeLiveChatId) => {
  let url = "https://youtube.googleapis.com/youtube/v3/liveChat/messages?liveChatId=" + activeLiveChatId + '&part=authorDetails%2Csnippet&key=' + YOUTUBE_DATA_API_KEY
  if (nextPageToken !== "") {
    url = url + "&pageToken=" + nextPageToken
  }
  const response = await fetch(url, {
    method: 'get',
    headers: {
      'Content-Type': 'application/json'
    }
  })
  const json = await response.json()
  const items = json.items;
  let index = 0
  let currentComments = []
  nextPageToken = json.nextPageToken;
  items?.forEach(
    (item) => {
      try {
        const userName = item.authorDetails.displayName
        const userIconUrl = item.authorDetails.profileImageUrl
        let userComment = ""
        if (item.snippet.textMessageDetails != undefined) {
          // 一般コメント
          userComment = item.snippet.textMessageDetails.messageText;
        }
        if (item.snippet.superChatDetails != undefined) {
          // スパチャコメント
          userComment = item.snippet.superChatDetails.userComment;
        }
        const additionalComment = { userName, userIconUrl, userComment }
        if (!liveCommentQueues.includes(additionalComment) && userComment != "") {
          if (isLiveCommentsRetrieveStarted) {
            // キューイング
            liveCommentQueues.push(additionalComment)

            // #つきコメントの除外
            additionalComment.userComment.includes("#") || currentComments.push(additionalComment)

            // ユーザーコメントの表示
            let target = document.getElementById("user-comment-box")
            // 要素を作成します
            const userContainer = document.createElement('div');
            userContainer.classList.add('user-container');
        
            const imageCropper = document.createElement('div');
            imageCropper.classList.add('image-cropper');
        
            const userIcon = document.createElement('img');
            userIcon.classList.add('user-icon');
            userIcon.setAttribute('src', additionalComment.userIconUrl);
        
            const userName = document.createElement('p');
            userName.classList.add('user-name');
            userName.textContent = additionalComment.userName + ':';
        
            const userComment = document.createElement('p');
            userComment.classList.add('user-comment');
            userComment.textContent = additionalComment.userComment;
        
            // 要素を追加します
            imageCropper.appendChild(userIcon);
            userContainer.appendChild(imageCropper);
            userContainer.appendChild(userName);
            userContainer.appendChild(userComment);
            target.prepend(userContainer)
          } else {
            responsedLiveComments.push(additionalComment);
          }
        }
      } catch {
        // Do Nothing
      }
      index = index + 1
    })

    // 読まれてないコメントからランダムに選択
    if (currentComments.length != 0) {
      let { userName, userIconUrl, userComment } = currentComments[Math.floor(Math.random() * currentComments.length)]
      const aituberResponse = await getAITuberResponse(userComment)
      speakAITuber(aituberResponse)

      let target = document.getElementById("question-box")
      target.innerHTML = `${userName} : ${userComment}`
    }

    console.log("liveCommentQueues", liveCommentQueues)
    console.log("responsedLiveComments", responsedLiveComments)

    // 繰り返し処理
    setTimeout(retrieveLiveComments, INTERVAL_MILL_SECONDS_RETRIEVING_COMMENTS, activeLiveChatId);
}

最後にコメントのサイズ・配置箇所等を調整するCSSを記述します。

style.css

#user-comment-box {
	position: absolute;
	top: 13%;
	left: 3%;
	width: 30%;
	height: 55%;
	padding: 1%;
	z-index: 1;
	/* box外は非表示 */
	overflow: hidden;
	display: flex;
	flex-direction: column-reverse;
}

#question-box {
	position: absolute;
	top: 20%;
	left: 40%;
	width: 40%;
	padding: 1%;
	z-index: 1;
	font-size: 18px;
}

#response-box {
	position: absolute;
	top: 73%;
	left: 5%;
	width: 85%;
	padding: 1%;
	z-index: 1;
}

#aituber-response {
	font-size: 20px;
	font-family: "M PLUS 1p";
}

/* user-container */
.user-container {
	display: flex;
	font-size: 14px;
	margin: 6px 2px;
}

.image-cropper {
  width: 20px;
  height: 20px;
  position: relative;
  overflow: hidden;
  border-radius: 50%;
	flex-shrink: 0;
}

.user-icon {
  display: inline;
  margin: 0 auto;
  height: 100%;
  width: auto;
}

.user-name, .user-comment {
	margin: 0 0 0 4px;
}

.user-name {
	flex-shrink: 0;
}

4. 配信準備


4.1 Googleアカウント・Twitterアカウントの作成

配信用・宣伝用のGoogle, Twitterアカウントを作成しましょう。

4.2 OBSの設定・小技

次に、OBSというVTuber、AITuber御用達のソフトをダウンロードしましょう。

最初はOBSの使い方にやや戸惑うかもしれませんが、ネット上にある記事を参考にして慣れていきましょう。

OBSは画像やブラウザ、テキスト等をレイヤー単位で配信できるので、キャラも柔軟に配置・調整することが可能です。

OBSでは以下のようにシーンタブで複数の画面の出し分けをすることができ、ソースタブでペイントツールのレイヤーのようなことが実現できます。先ほどのHTMLはブラウザを設定することで表示させることが可能です。

OBS配信画面

4.3 概要欄の作成

概要欄にはコメントルールやチャンネルポリシー、切り抜きの規定、ハッシュタグを記載しておきましょう。
ハッシュタグについては以下をご参照ください。

4.4 サムネイルの作成

サムネイルを作成しましょう。

4.4 BGMの選定

最後に配信用のBGMを選定しましょう。以下のサイトあたりが人気な印象です。

おまけ

日本最大級のAITuberサーバーを貼っておきます。
毎日がお祭りです。是非、参加しましょう(見る専の人も大歓迎)

おわりに

以上で終了です!
意外とすんなりできたんじゃないでしょうか?

2023年3月11日時点では日本には十数名程度のAITuberしか存在しませんが、4月にもなると倍、今年の年末なんて凄い数がいるんじゃないかとハラハラしています。

AITuberは未開拓な領域が多く、可能性は無限大です。

なにより今後どんなAITuberの子たちが生まれるのか非常に楽しみで仕方がないです!一緒に次の時代を創りましょう!!

みんなのAITuber楽しみにしております!

参考


この記事が気に入ったらサポートをしてみませんか?