まるぼ実験場

今年専門を卒業したけど就活サボってしまいました(関係ない職種でフリーター生活中)。どうすんだよこれ。

【Python】VTuberアプリソースコード


何の変哲もないアプリで遅くなりましたが公開いたします!
サムネもなんとなくYouTube風。

VTuberって、最近では手軽に参入できるアプリが出来はじめたものの、自分オリジナルのキャラを使いたい!と思うとすごーく手間がかかるってイメージですよね。

まずキャラクターをデザインして…
3Dだったらモデリングして、モーションキャプチャーして…
2Dでもグラフィックを細かくパーツ分けして…
配信を賑やかにしたいけどちょっとそこまでは厳しい…って思いながらひそかにバーチャルアバター配信を見て(最低限どんな動作があればぎりぎりそれっぽく見えるのか)研究しつつ、考えたものがこちら。

『なんちゃってVTuber』基本仕様

・2Dイラストのみ対応
・一定時間ごとにイラストが目パチ(瞬き)する
・マイク入力に合わせて口パクする

これだけ。カメラも使わない音声検知だけ簡単なんちゃってVTuber

なんか…どっかで見たような絵面だなと思ってたらゆっくりだこれ。
…これでVTuberなんて言ったらファンに怒られますかね?

でもやっぱり、自分の動画内でキャラクターが反応してくれるって面白いね…口パクと目パチだけでだいぶ印象違う。リアルタイム感が出た気がします。

技術的な話。

動作ツールはPython、使用した画像も顔のベース、目と口それぞれ二種、目と口より上に来るパーツの透明情報入り画像たった6枚だけ。

ウィンドウ画面表示に関しては、
OpenCVを使うかPygameにするか悩んだのですが、FPSの調整とかがやりやすそうだなと思いPygameを使用。
将来的に凝った画像加工や認識を使いたいならOpenCVにする方がいいかもしれない。
 
というか今の仕様なら画像の透明部分重ねて表示できればなんでもいい。

音声認識に関してはこちらを参考にしました。
qiita.com
基本的な流れとしては、Pygameの1ループに一回音声を検知して、入力があったら画像を切り替えるだけの雑な設定。

※当ソースコードを用いて発生した問題について、管理人は責任を負いかねます。ご了承ください。

使い方

1. Pythonを実行できる環境を作る
2. ソースコードを適当な名前.pyで保存
3. 画像を同じフォルダに置く
4. コマンドプロンプトを起動し、ファイルを置いたディレクトリにcdで移動し『python ファイル名』で実行

画像の構造は、
base.png

kami.png

kuti.png

kuti2.png

me.png

me2.png

me3.png

こんな感じで重ね合わせる前提で細かくパーツ分けされてますが、ちょっとソースいじったら通常ととじ目、口開けの3枚だけでも
何とかなると思います。
これなら、https://character-nantoka.appspot.comとかの画像も使えますね。
今回画像については将来機能拡張することを考えていますので、細かめに。

ソースコード

import pygame
from pygame.locals import *
import sys
import pyaudio
import numpy as np

#----------------
# pygame初期化
#----------------
pygame.init()
screen = pygame.display.set_mode((500,500))
pygame.display.set_caption("VTuber?")

#----------------
# 画像取得
#----------------
#基本
bg = pygame.image.load("base.png").convert_alpha()
image_rect = bg.get_rect()
#目
me1 = pygame.image.load("me.png").convert_alpha()
me2 = pygame.image.load("me2.png").convert_alpha()
me3 = pygame.image.load("me3.png").convert_alpha()
#口
kuti1 = pygame.image.load("kuti.png").convert_alpha()
kuti2 = pygame.image.load("kuti2.png").convert_alpha()
#髪
kami = pygame.image.load("kami.png").convert_alpha()
#フォント
font = pygame.font.Font(None, 20) 

def main():
	#----------------
	# 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)
	
	#----------------
	# 変数設定
	#----------------
	#タイマー変数
	Timer = 0
	
	# 閾値
	threshold = 0.025
	
	#描画周期設定
	Clock = pygame.time.Clock()
	FPS = 30
	
	#----------------
	# メインループ
	#----------------
	while(1):
		pygame.display.update()
		
		# 音データの取得
		data = stream.read(chunk)
		# ndarrayに変換
		x = np.frombuffer(data, dtype="int16") / 32768.0
		
		#時間経過
		Timer = pygame.time.get_ticks()
		
		# イベント処理
		for event in pygame.event.get():
			# 閉じるボタンが押されたときの処理
			if event.type == QUIT:
				stream.close()
				p.terminate()
				pygame.quit()
				sys.exit()
		
		# 画像生成
		ImageOut(screen, image_rect, bg, Select_Eye(Timer), Select_mouse(x, threshold), Select_Add())
		#デバッグ用
		#DebugGraph(screen, Timer)
		
		pygame.display.flip()
		Clock.tick(FPS)                     #画面更新

#----------------
# パーツ選択
#----------------
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_up = kami      #髪描画
	
	return put_up
	
#----------------
# 描画
#----------------
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

if __name__ == "__main__":
	main()

使い方



時間や音声の条件を元にばらばらだった画像が合成されるので、それで出力されたウィンドウをOBSに設定するだけ!
…ただ特筆したような機能はないし、作ろうと思えば上記情報をもとにすぐ作れると思うので、ちょっとしたネタにいかがですか!?
やろうと思えばキャラをもっと動かしたり音声の音量で表情パターン変えたりとかいろいろできそうですね。…あんまり凝りすぎてももうそれLive2Dで良くね?ってなりそうですが!

今後の展望

このプログラム、問題点は音ゲー配信と相性が悪いこと。
マイクが鍵盤の音を拾っちゃってそれで常に口パクしてる(うざい)という問題が発覚し、プレー配信時の合成画像は口パクのコードを削除した状態で撮った。
単指向性マイクにしたり音域カットしたりしたらマシになるのかもしれない。
・マイク感度の改善
・外部デバイスとの連携
・起動してるとタスクトレイのマイクが荒ぶる問題の解決(←大問題じゃないか
いろいろと拡張できる題材だと思うので、こんな機能つけてみたよ!とか色々拡張して遊べると思います。

余談

最初Markdown記法でソース貼ろうかと思ったんだけど、
コメントの#に反応していちいち見出しになってたのね…
なので今回の記事ははてな記法で書いています。