Open Vtuber Studio 之開發(1)
Toby
Toby
Open Vtuber Studio 先暫定在 2021 年始春公開好了。

由頭部開始

由於很多現時的 Vtuber 並不能像四大天王那樣投入這麼多錢到全身追蹤的 捕捉技術,所以很大部分現在的 Vtuber 都只是依賴臉部表情捕捉 + 手動更改臉部表情而成的半 3D Vtuber 系統。當然,這種東西要做的話很簡單,隨便買一個 Face Tracking 的 SDK 來用就好。然而,基於開源計劃的原則,這方法行不通。這個時候我們就要自已幹一套出來了。

Face-api.js

https://github.com/justadudewhohacks/face-api.js/

FaceAPI.js 是一個使用 Neural Network (神經網絡)而編寫的臉部捕捉 API。話雖如此,它卻內置了很多不同的 Network Model (模型)來做同一件事情。而在這個開源計劃中我看上了這個功能:Face Landmark Detection

原圖:https://github.com/justadudewhohacks/face-api.js/

動態捕捉及 VRM 模型動作 映射演算法

簡單來說,這功能把捕捉到的臉部一些重要的點找出來,之後我們便可以對這些點進行後處理。當然,這模型只會回傳平面的位置或(x, y)值,因此,我們需要把它轉成 3D 位置,這就需要一點演算法了。

首先,我們先看看 FaceLandmarks68 模型的回傳值

然後對比一下臉部移動的時候的捕捉位置,我們很容易會發現幾個特點

  1. 第 1點跟第 17點之間的距離除了前後移動之外基本上不會改變
  2. 第 1點跟第 9點之間的垂直距離會跟據你頭部上下望而產生線性變化
  3. 第在第 3跟 15點間畫一條直線,在頭部旋轉的時候第 31點會按比例的在這條直線上滑動

就是這樣,基本上我們就能確定怎樣判斷頭部移動和旋轉的方向了。
我們在取得比較點在最高及最低值的位置之後把其 歸一化 (Normalize),就能得出頭部的 變換/旋轉矩陣 (Transformation / Rotation Matrix)。

向上向下看的計算方式
向左向右看的計算方式
向左傾跟向右傾的計算方法

所牌結合上述多個計算方法,頭部的 3D移動及旋轉就被計算出來了。根據捕捉到的頭部動態,使 VRM 模型的頭部骨架也跟隨著移動,最後便形成以下的效果。

自動眨眼

以 FaceAPI 的準確率來說要檢測到貶眼實在不太可能了。所以這裡就使用了自動眨眼功能。然而你可能想,這要寫應該不難吧?就這樣加個 setInterval() 就好了?不,你太少看 VRM 的麻煩程度了。
我這裡給大家看一下只是控制一個 VRM 模型眨眼的部分

function createEyeBlinkBlendValueFromCycle(s){
	//Eclipse time to sin function
	const scaleRatio = 2;
	const eyeCloseIntervalScale = 0.18;
	if (eyeBlinking){
		if (eyeBlinkingTimer[0] == 0){
			//Start to blink
			eyeBlinkingTimer[0] = s;
			return 0;
		}else{
			//Blinking in progress
			eyeBlinkingTimer[1] += s - eyeBlinkingTimer[0] 
			eyeBlinkingTimer[0] = s;
		}
		
		s = Math.sin( Math.PI * eyeBlinkingTimer[1] * scaleRatio);
		s = s * 3;
		var baseFormula = Math.min(s, 1);
		//console.log([baseFormula,s, eyeBlinkingTimer[1]]);
		if (baseFormula < 0){
			eyeBlinking = false;
			eyeBlinkingTimer = [0,0];
		}else if (s > 1){
			//Eye closed. Give it a blink interval skip
			eyeBlinkingTimer[1] += eyeCloseIntervalScale;
		}
		return baseFormula;
	}else{
		return 0;
	}	
}

然後人類一般眨眼時間間距為 2 – 10秒,為了做到讓 VRM 模型眨眼更真實,這個隨機眨眼的功能也是必須的。

function blinkEye(){
	eyeBlinking = true;
	setTimeout(function(){
		blinkEye();
	},getRandomInt(2000,10000));
}
function getRandomInt(min, max) {
	min = Math.ceil(min);
	max = Math.floor(max);
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

沒錯,要做到真實的眨眼是一點都不容易的。就只是要眨個眼而已就已經碰到好幾個 timer 跟 radian 角度的運算,真是少一點數學根基都不行。

小結

怎樣,是不是開始有一點像真呢?
然而,這可是有一個問題:運算需求太大了。
沒錯,一塊筆電的 i7 處理器的顯卡居然被吃到 100% 使用率,散熱口噴著快要能把雞蛋煮熟的熱風,實在太恐怖了啦!

某位同為軟件工程師的朋友如是說
效果如圖

那頭部的控制部分就先到裡了,之後就是更困難的身體捕捉部分喔~