何の変哲もないアプリで遅くなりましたが公開いたします!
サムネもなんとなくYouTube風。
VTuberって、最近では手軽に参入できるアプリが出来はじめたものの、自分オリジナルのキャラを使いたい!と思うとすごーく手間がかかるってイメージですよね。
まずキャラクターをデザインして…
3Dだったらモデリングして、モーションキャプチャーして…
2Dでもグラフィックを細かくパーツ分けして…
配信を賑やかにしたいけどちょっとそこまでは厳しい…って思いながらひそかにバーチャルアバター配信を見て(最低限どんな動作があればぎりぎりそれっぽく見えるのか)研究しつつ、考えたものがこちら。
『なんちゃってVTuber』基本仕様
・2Dイラストのみ対応
・一定時間ごとにイラストが目パチ(瞬き)する
・マイク入力に合わせて口パクする
これだけ。カメラも使わない音声検知だけ簡単なんちゃってVTuber!
なんか…どっかで見たような絵面だなと思ってたらゆっくりだこれ。
…これでVTuberなんて言ったらファンに怒られますかね?
でもやっぱり、自分の動画内でキャラクターが反応してくれるって面白いね…口パクと目パチだけでだいぶ印象違う。リアルタイム感が出た気がします。
技術的な話。
動作ツールはPython、使用した画像も顔のベース、目と口それぞれ二種、目と口より上に来るパーツの透明情報入り画像たった6枚だけ。
ウィンドウ画面表示に関しては、
OpenCVを使うかPygameにするか悩んだのですが、FPSの調整とかがやりやすそうだなと思いPygameを使用。
将来的に凝った画像加工や認識を使いたいならOpenCVにする方がいいかもしれない。
というか今の仕様なら画像の透明部分重ねて表示できればなんでもいい。
音声認識に関してはこちらを参考にしました。
qiita.com
基本的な流れとしては、Pygameの1ループに一回音声を検知して、入力があったら画像を切り替えるだけの雑な設定。
更新履歴
2021/05/30 初版
2024/05/03 不具合修正版ソースコードを初公開
2024/05/12 全体的に記事を修正。素材用画像差し替え。
※当ソースコードを用いて発生した問題について、管理人は責任を負いかねます。ご了承ください。
使い方
1. Pythonを実行できる環境を作る
2.素材をダウンロードする
ダウンロードリンクはこちら。
https://3ka.me/sozai/nantyatteV.zip
3. コマンドプロンプトを起動し、ファイルを置いたディレクトリにcdで移動し『python main_v02.py』で実行
同梱の画像はブログ筆者がフリー素材として手描きしたものなので、ご自由にお使いください。
前のサンプル画像(記事サムネで使ってるキャラ)は当時適当に描いたもので、今見たら少し恥ずかしかったので改めて新キャラクターで描き直しました。
改変などもご自由にどうぞ(zipファイルの中にクリスタ形式のファイルも同梱しております)。
画像の構造は、
base.png
kuti.png
kuti2.png
me.png
me2.png
me3.png
add.png(※全て透明色の画像)
こんな感じで重ね合わせる前提でパーツ分けされてますが、ちょっとソースいじったら通常ととじ目、口開けの3枚だけでも
何とかなると思います。
これなら、https://character-nantoka.appspot.comとかの画像も使えますね。
今回画像については将来機能拡張することを考えていますので、細かめに。
ソースコード
2024/05/12 更新版
import pygame from pygame.locals import * import sys import pyaudio import numpy as np import threading #pyaudio初期化 chunk = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 RATE = 44100 p = pyaudio.PyAudio() stream = p.open(format = FORMAT,channels = CHANNELS, rate = RATE, input = True, frames_per_buffer = chunk) # 閾値 threshold = 0.025 # 音データ格納用配列 x = np.empty(chunk) #pygame初期化 pygame.init() screen = pygame.display.set_mode((500,500)) pygame.display.set_caption("VTuber?") #画像取得 #基本 bg = pygame.image.load("Vtuber_base.png").convert_alpha() image_rect = bg.get_rect() #目 me1 = pygame.image.load("Vtuber_me.png").convert_alpha() me2 = pygame.image.load("Vtuber_me2.png").convert_alpha() me3 = pygame.image.load("Vtuber_me3.png").convert_alpha() #口 kuti1 = pygame.image.load("Vtuber_kuti.png").convert_alpha() kuti2 = pygame.image.load("Vtuber_kuti2.png").convert_alpha() #その他 add = pygame.image.load("Vtuber_add.png").convert_alpha() #フォント font = pygame.font.Font(None, 20) #タイマー変数 Timer = 0 #描画周期設定 Clock = pygame.time.Clock() FPS = 30 def draw(): while True: pygame.display.update() #時間経過 Timer = pygame.time.get_ticks() # 画像生成 ImageOut(screen, image_rect, bg, Select_Eye(Timer), Select_mouse(x, threshold), Select_Add()) #デバッグ用 #DebugGraph(screen, Timer) pygame.display.flip() Clock.tick(FPS) #画面更新 def streamread(): global x while True: # 音データの取得 data = stream.read(chunk) # ndarrayに変換 x = np.frombuffer(data, dtype="int16") / 32768.0 def main(): while True: # 終了用のイベント処理 for event in pygame.event.get(): # 閉じるボタンが押されたときの処理 if event.type == QUIT: stream.close() p.terminate() pygame.quit() sys.exit() #---------------- # パーツ選択 #---------------- def Select_Eye(Timer): # 瞬き(力技。。。もうちょい自然な見せ方があるはず if ((Timer % 2100) <= 160): put_me = me2 #目(閉じ)描画 else: put_me = me1 #目描画 #キー押下によって目の描画オブジェクトを変更する pressed_key = pygame.key.get_pressed() if pressed_key[K_q]: put_me = me3 #目(><)描画 return put_me def Select_mouse(x, threshold): #音声認識によって口の描画オブジェクトを変更する if x.max() > threshold: put_kuti = kuti1 #音声入力があるときの口描画 else: put_kuti = kuti2 #何もないときの口描画 return put_kuti def Select_Add(): #一番上に描画する画像(アクセサリーなど) put_add = add return put_add #---------------- # 描画 #---------------- def ImageOut(screen, image_rect, bg, me, kuti, add): screen.fill((0, 255, 0, 0)) # 画面の背景色(緑) screen.blit(bg, image_rect) # ベース画像の描画 screen.blit(me, image_rect) # 目画像の描画 screen.blit(kuti, image_rect) # 口画像の描画 screen.blit(add, image_rect) # さらにその上に重なる画像の描画 return #---------------- # デバッグ用表示 #---------------- def DebugGraph(screen, Timer): print(Timer) return #スレッド作成 thread1 = threading.Thread(target=draw) thread2 = threading.Thread(target=streamread) thread1.setDaemon(True) thread2.setDaemon(True) thread1.start() thread2.start() if __name__ == "__main__": main()
使い方
サンプル素材の画像を使用して実行したウィンドウ。
時間や音声の条件を元にばらばらだった画像が合成されるので、それで出力されたウィンドウをOBSに設定するだけ!
アプリで合成したウィンドウの使用例。
…ただ特筆したような機能はないし、作ろうと思えば上記情報をもとにすぐ作れると思うので、ちょっとしたネタにいかがですか!?
やろうと思えばキャラをもっと動かしたり音声の音量で表情パターン変えたりとかいろいろできそうですね。…あんまり凝りすぎてももうそれLive2Dで良くね?ってなりそうですが!