1.3K Views
February 05, 24
スライド概要
可視化技術や人間計測/空間計測技術を活用した問題解決に関する研究開発。 ARコンテンツ作成勉強会(tryAR)を主催。
AI x WebXR! フェイストラッキングを⽤いた擬似3D表現
まずは素材のダウンロード http://arfukuoka.lolipop.jp /parallax/sample.zip
⾃⼰紹介 ⽒名︓吉永崇(Takashi Yoshinaga) 所属︓Steampunk Digital株式会社 専⾨︓医療⽀援AR,運動計測,3D Video コミュニティ︓ARコンテンツ作成勉強会
Twitterと勉強会ページで情報を発信しています @AR_Fukuoka Googleで「AR勉強会」で検索
ハッシュタグ #AR_Fukuoka
本題に⼊ります
今⽇のゴール https://youtu.be/fyzXNG_0IIE AIを⽤いた顔の認識とカメラコントロールを使った擬似3D表現
使⽤ツール︓MediaPipe & TensorFlow GoogleのMediaPipeとTnsorflow.jsがコラボでパッケージを開発。 顔認識を実現するfacemeshと⼿の認識を実現するhandposeが 提供されている中で今回はfacemeshを使⽤。 これを使う
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整 カメラ(=視点)の位置を調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
今回の進め⽅ サンプルをコピペしながら コードの意味を解説
テンプレートの取得 https://glitch.com/~parallax-template
少し下にスクロール テンプレートの取得
テンプレートの取得 Remix Your Own
テンプレートの確認 index.html
テンプレートの確認
テンプレートの確認 ① ライブラリ読み込み ② スクリプト記述 ③ CGとカメラ映像 の表⽰ Lesson01
テンプレートの確認 ① ライブラリ読み込み ② スクリプト記述 ③ CGとカメラ映像 の表⽰
①ライブラリの読み込み
<head>
<meta charset="utf-8">
<title>AR Fukuoka</title>
<!̶A-Frame読み込み-->
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<!--TensorFlow関連の読み込み-->
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-core.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-converter.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-webgl.js">
</script>
<!--顔認識の学習モデル読み込み-->
<script src="https://unpkg.com/@tensorflow-models/[email protected]/dist/facemesh.js"></script>
<!--⾃前のスクリプトを記述-->
<script type="text/javascript">
</script>
</head>
各種ライブラリの読み込み
・ A-Frame (3D描画に使⽤)
・ TensorFlow関連 (顔認識に使⽤)
②スクリプトの記述
<head>
<meta charset="utf-8">
<title>AR Fukuoka</title>
<!̶A-Frame読み込み-->
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<!--TensorFlow関連の読み込み-->
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-core.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-converter.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-webgl.js">
</script>
<!--顔認識の学習モデル読み込み-->
<script src="https://unpkg.com/@tensorflow-models/[email protected]/dist/facemesh.js"></script>
<!--⾃前のスクリプトを記述-->
<script type="text/javascript">
次のページで解説
</script>
</head>
②スクリプトの記述
<!--⾃前のスクリプトを記述。今回は事前に3つの関数を⽤意。-->
<script type="text/javascript">
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
初期化(1)
・ カメラ(PC/スマホ)の起動
・ 映像サイズや描画領域の取得
c
初期化(2)
・ 顔認識に⽤いる学習データ
の読み込み
c
毎フレーム実⾏
・ 顔の認識
・ 顔の位置を⽤いた視点変更
テンプレートの確認 ① ライブラリ読み込み ② スクリプト記述 ③ CGとカメラ映像 の表⽰
テンプレートの確認 ① ライブラリ読み込み ② スクリプト記述 ③ CGとカメラ映像 の表⽰
③CGとカメラ映像の表⽰
<!̶A-Frameを⽤いた3次元描画-->
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
<a-entity id="room" scale="1 1 1">
<a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL"></a-plane>
<a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane>
<a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane>
<a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane>
<a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane>
</a-entity>
<!--カメラ(3D視点の設定)-->
<a-entity camera position="0 0 0" ></a-entity>
</a-scene>
<!--カメラ画像表⽰-->
<video id="video" style="position: absolute; width:320px; height:240px;"></video>
A-Frameで3D描画
ビデオ映像の表⽰
③CGとカメラ映像の表⽰ この後の作業を進めるとこうなる予定(ビデオ映像はまだ表⽰されない)
A-Frame解説
<!̶A-Frameを⽤いた3次元描画-->
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
<a-entity id="room" scale="1 1 1">
<a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL"></a-plane>
<a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane>
<a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane>
<a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane>
<a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane>
</a-entity>
<!--カメラ(3D視点の設定)-->
<a-entity camera position="0 0 0" ></a-entity>
</a-scene>
3D描画に関する記述
<a-scene>と</a-scene>の間に表⽰するオブジェクトを記述
A-Frame解説
<!̶A-Frameを⽤いた3次元描画-->
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
<a-entity id="room" scale="1 1 1">
<a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL"></a-plane>
<a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane>
<a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane>
<a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane>
<a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane>
</a-entity>
<!--カメラ(3D視点の設定)-->
<a-entity camera position="0 0 0" ></a-entity>
</a-scene>
3D描画に関する記述
<a-scene>と</a-scene>の間に表⽰するオブジェクトを記述
A-Frame解説
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0"
color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125"
color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15"
color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4"
color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
p 基本図形はa-xxxで提供されている
<a-entity id="room" scale="1 1 1">
https://aframe.io/docs/1.1.0/primitives/
<!--中略-->
a-box.html
</a-entity>
p position,rotationで位置や⾓度指定
<!--カメラ(3D視点の設定)-->
p a-entityタグで囲むことで⼦要素の
<a-entity camera position="0 0 0" ></a-entity>
オブジェクトの⼀括調整も可能
A-Frame解説
<!̶A-Frameを⽤いた3次元描画-->
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
<a-entity id="room" scale="1 1 1">
<a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL"></a-plane>
<a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane>
<a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane>
<a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane>
<a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane>
</a-entity>
<!--カメラ(3D視点の設定)-->
<a-entity camera position="0 0 0" ></a-entity>
</a-scene>
3D描画に関する記述
<a-scene>と</a-scene>の間に表⽰するオブジェクトを記述
A-Frame解説
<!̶A-Frameを⽤いた3次元描画-->
<a-scene id="scene" background="color: #FAFAFA" style="position: absolute;">
<!--表⽰するオブジェクト-->
<a-entity id="objects" position = "0 0 -0.5" scale="1 1 1">
<a-box position="-0.1 -0.05 0.1" scale="0.1 0.1 0.1" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
<a-sphere position="0 0.015 -0.1" radius="0.125" color="#EF2D5E" shadow></a-sphere>
<a-cylinder position="0.1 -0.025 0.1" radius="0.05" height="0.15" color="#FFC65D" shadow></a-cylinder>
<a-plane position="0 -0.11 0" rotation="-90 0 0" width="0.4" height="0.4" color="#7BC8A4" shadow></a-plane>
</a-entity>
<!--ウィンドウの向こう側に表⽰する部屋のオブジェクト-->
<a-entity id="room" scale="1 1 1">
<a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL"></a-plane>
<a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane>
<a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane>
<a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane>
<a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane>
</a-entity>
<!--カメラ(3D視点の設定)-->
<a-entity camera position="0 0 0" ></a-entity>
</a-scene>
3D描画に関する記述
<a-scene>と</a-scene>の間に表⽰するオブジェクトを記述
A-Frame解説 <!--ウィンドウの向こう側に表⽰する部屋のオブジェクト--> <a-entity id="room" scale="1 1 1"> <a-plane position="0 0.5 -0.5" rotation="90 0 0" src="画像URL "></a-plane> <a-plane position="0 -0.5 -0.5" rotation="-90 0 0" src="画像URL"></a-plane> <a-plane position="-0.5 0 -0.5" rotation="0 90 0" src="画像URL"></a-plane> <a-plane position="0.5 0 -0.5" rotation="0 -90 0" src="画像URL"></a-plane> <a-plane position="0 0.0 -1" rotation="0 0 0" src="画像URL"></a-plane> </a-entity> <!-- カメラ(3D視点の設定)--> <a-entity camera position="0 0 0" ></a-entity> p 板(a-plane)を配置して天井・床・壁を作成 p srcの右辺でテクスチャが画像を指定 p サイズは1m3相当。親要素のa-entityの scaleを編集して縦・横・奥⾏きを調節可能 p <a-entity camera>で視点の位置を設定
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
ビデオを描画する
<script type="text/javascript">
//ビデオを扱う変数
var video;
//ページを読み込み終わったら実⾏
window.onload = function() {
//カメラ画像のサイズ設定(とりあえず640x480くらい)
let constraints = { video: { width: 640, height: 480 } };
//カメラデバイスの取得
navigator.mediaDevices.getUserMedia(constraints).then(
function(mediaStream) {
//body内の描画領域(id=video)を取得
video = document.getElementById("video");
//リアルタイム映像をvideo要素に対応づける
video.srcObject = mediaStream;
//準備が整ったら再⽣
video.onloadedmetadata = function(e) {
video.play();
};
}
);
}
</script>
Lesson02
動作確認 Show
動作確認 In a New Window
動作確認 カメラの映像が表⽰されればOK うまく動かない場合は[完成品]フォルダの中の2.txtをコピペで差し替える
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
顔認識の学習モデル読み込み
//ビデオを扱う変数
var video;
//ページを読み込み終わったら実⾏
window.onload = function() {
//カメラ画像のサイズ設定
var constraints = { video: { width: 640, height: 480 } };
//カメラデバイスの取得
navigator.mediaDevices.getUserMedia(constraints).then(
function(mediaStream) {
//body内のid=videoの要素を取得
video = document.getElementById("video");
//リアルタイム映像をvideo要素に対応づける
video.srcObject = mediaStream;
//準備が整ったら再⽣
video.onloadedmetadata = function(e) {
video.play();
//顔認識の学習モデルを読み込む(このあと記述)
LoadFaceModel();
};
}
);
};
Lesson03
顔認識の学習モデル読み込み //ビデオを扱う変数 var video; //顔認識の学習モデル var model; //ページを読み込み終わったら実⾏ window.onload = function() { //カメラ画像のサイズ設定 let constraints = { video: { width: 640, height: 480 } }; //カメラデバイスの取得 navigator.mediaDevices.getUserMedia(constraints).then( //スペースの都合上省略 //カメラの準備が整ったらLoadFaceModelを呼び出す ); } //トラッキング開始準備の関数 async function LoadFaceModel(){ //顔認識のための学習モデル読み込み model = await facemesh.load(); //顔認識のループ(次のステップで使⽤) FaceTracking (); } Lesson04
顔の認識 //ビデオを扱う変数 var video; //顔認識の学習モデル var model; window.onload = function() {/*スペースの都合上省略*/ }; async function LoadFaceModel(){ model = await facemesh.load(); FaceTracking (); } //顔検出と描画のループ async function FaceTracking () { //顔の検出 let predictions = await model.estimateFaces(video); //⾒つかった顔が1つ以上あれば if (predictions.length > 0) { //検出された顔の情報にアクセスして表⽰ console.log(predictions[0]); } requestAnimationFrame(FaceTracking); } Lesson05
動作確認 カメラの映像は表⽰されるけど、 学習データの読み込みに時間がかかる
動作確認(Safari) ①適当な箇所を右クリック ②要素を調査とか 要素の詳細を表⽰
動作確認(Safari) ①コンソール ②検出結果
認識結果の内訳 faceInViewConfidence: 1, //顔らしさ(信頼度 0~1) boundingBox: { // 顔を囲む矩形の⾓の座標 topLeft: [232.28, 145.26], bottomRight: [449.75, 308.36], }, mesh: [ // 顔の各特徴の座標[x,y,z] (認識⽤に192x192pxに縮⼩した画像上の座標) [92.07, 119.49, -17.54], [91.97, 102.52, -30.54], ... ], scaledMesh: [// 顔の各特徴の座標[x,y,z] (ビデオ画像の座標) [322.32, 297.58, -17.54], [322.18, 263.95, -30.54], ... ], annotations: { //顔のパーツと座標の組み合わせ silhouette: [ [326.19, 124.72, -3.82], [351.06, 126.30, -3.00], ... ], 今回はboundingBoxの ... 中⼼を使⽤する︕ }
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整 カメラ(=視点)の位置を調整
顔の中⼼位置を算出
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
</script>
c
}
次はここを編集
顔の中⼼位置を算出 //ビデオを扱う変数 var video; //顔認識の学習モデル var model; window.onload = function() {/*スペースの都合上省略*/ }; async function LoadFaceModel(){{/*スペースの都合上省略*/ } c async function FaceTracking () { //顔の検出 let predictions = await model.estimateFaces(video); //⾒つかった顔が1つ以上あれば if (predictions.length > 0) { //バウンディングボックスとその左上・右下の座標を取得 let keypoints = predictions[0].boundingBox; let topLeft=keypoints.topLeft; let bottomRight=keypoints.bottomRight; //中⼼を計算 let cX=((topLeft[0]+bottomRight[0])/2); let cY=(topLeft[1]+bottomRight[1])/2; console.log(predictions[0]); } requestAnimationFrame(FaceTracking); } ここに注⽬︕
顔の中⼼位置を算出
//ビデオを扱う変数
var video;
//顔認識の学習モデル
var model;
window.onload = function() {/*スペースの都合上省略*/ };
async function LoadFaceModel(){{/*スペースの都合上省略*/ }
async function FaceTracking () {
//顔の検出
let predictions = await model.estimateFaces(video);
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//バウンディングボックスとその左上・右下の座標を取得
let keypoints = predictions[0].boundingBox;
let topLeft=keypoints.topLeft;
let bottomRight=keypoints.bottomRight;
//中⼼を計算
let cX=(topLeft[0]+bottomRight[0])/2;
let cY=(topLeft[1]+bottomRight[1])/2;
console.log(cX+", "+cY);
}
requestAnimationFrame(FaceTracking);
}
Lesson06
このあとやりたいこと x -1 Y Y x -1 1 1 p p p p 原点は画像の左上 座標はピクセルで表現 画素数はカメラによって違う xy座標(ピクセル)を顔の位置と して反映させると値が⼤きすぎる p p p p 原点は画像の中央 座標は画像中の相対位置 X,Y共に-1~1正規化 カメラの画素数に依存せず 座標を扱うことが可能
顔の中⼼位置を正規化
//ビデオを扱う変数
var video;
//顔認識の学習モデル
var model;
window.onload = function() {/*スペースの都合上省略*/ };
async function LoadFaceModel(){{/*スペースの都合上省略*/ }
async function FaceTracking () {
//顔の検出
let predictions = await model.estimateFaces(video);
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//バウンディングボックスとその左上・右下の座標を取得
let keypoints = predictions[0].boundingBox;
let topLeft=keypoints.topLeft;
let bottomRight=keypoints.bottomRight;
//中⼼を計算
let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1;
let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1;
console.log(cX+", "+cY);
}
requestAnimationFrame(FaceTracking);
}
Lesson07
顔の中⼼位置を正規化
//ビデオを扱う変数
var video;
//顔認識の学習モデル
var model;
window.onload = function() {/*スペースの都合上省略*/ };
async function LoadFaceModel(){{/*スペースの都合上省略*/ }
async function FaceTracking () {
//顔の検出
let predictions = await model.estimateFaces(video);
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//バウンディングボックスとその左上・右下の座標を取得
let keypoints = predictions[0].boundingBox;
let topLeft=keypoints.topLeft;
let bottomRight=keypoints.bottomRight;
//中⼼を計算
let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1;
let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1;
console.log(cX+", "+cY);
//console.log(cX+",
"+cY);
}
⽂字の表⽰は不要
requestAnimationFrame(FaceTracking);
}
カメラの動きへの反映
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
カメラ(=視点)の位置を調整
Lesson08
カメラの動きへの反映
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
c
};
次はここを編集
カメラの動きへの反映
window.onload = function() {
//a-scene(id=scene)を取得
let scene = document.getElementById('scene');
//A-Frameのカメラを取得
camera= scene.camera;
//カメラ画像のサイズ設定(とりあえず640x480くらい)
let constraints = { video: { width: 640, height: 480 } };
//カメラデバイスの取得
navigator.mediaDevices.getUserMedia(constraints).then(
function(mediaStream) {
video = document.getElementById("video");
video.srcObject = mediaStream;
video.onloadedmetadata = function(e) {
video.play();
LoadFaceModel();
};
}
);
Lesson09
};
カメラの動きへの反映
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
</script>
c
}
次はここを編集
カメラの動きへの反映 async function FaceTracking(){ //顔の検出 let predictions = await model.estimateFaces(video); //⾒つかった顔が1つ以上あれば if (predictions.length > 0) { //バウンディングボックスとその左上・右下の座標を取得 let keypoints = predictions[0].boundingBox; let topLeft=keypoints.topLeft; let bottomRight=keypoints.bottomRight; //中⼼を計算(-1~1で正規化) let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1; let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1; //カメラの位置を操作 camera.position.x=-cX; //左右反転 camera.position.y=-cY; //上下反転 camera.position.z=2.5; //奥⾏きは分からないので適当に設定 } window.requestAnimationFrame(FaceTracking); Lesson10 }
動作確認 顔の位置に連動
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
部屋サイズの設定
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
var canvas: //描画領域
var screenWidth, screenHeight; //表⽰領域のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
Lesson11
部屋サイズの設定
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
var canvas; //描画領域
var screenWidth, screenHeight; //表⽰領域のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
c
};
次はここを編集
部屋サイズの設定
window.onload = function() {
//キャンバス領域を取得
canvas=document.getElementsByClassName('a-canvas')[0];
};
//a-scene(id=scene)を取得
let scene = document.getElementById('scene');
//A-Frameのカメラを取得
camera= scene.camera;
//カメラ画像のサイズ設定(とりあえず640x480くらい)
let constraints = { video: { width: 640, height: 480 } };
//カメラデバイスの取得
navigator.mediaDevices.getUserMedia(constraints).then(
function(mediaStream) {
//中略
}
);
Lesson12
部屋サイズの設定
<script type="text/javascript">
var video; //ビデオを扱う変数
var model; //顔認識の学習モデル
var camera; //カメラ(視点)を扱う変数
var canvas; //描画領域
var screenWidth, screenHeight; //表⽰領域のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
</script>
c
}
次はここを編集
部屋サイズの設定
async function FaceTracking(){
//描画領域のサイズが変わった場合
if(screenWidth!=canvas.width || screenHeight!=canvas.height){
screenWidth=canvas.width;
screenHeight=canvas.height;
//部屋の横幅を1に固定した場合の⾼さを計算
let height=screenHeight/screenWidth;
//部屋のオブジェクトを取得してサイズを変更
let room= document.getElementById('room');
room.setAttribute('scale', { x: 1, y: height, z: 1 });
}
//顔の検出
let predictions = await model.estimateFaces(video);
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//中略
}
window.requestAnimationFrame(FaceTracking);
Lesson13
}
動作確認 頭の位置と視点は連動するが、部屋がモニタに固定されない
原因と解決⽅法 【現状】 p カメラの視野⾓は固定されている p カメラが部屋のモデルから離れると 視野内に他の領域も⼊ってくる 理想的には・・・ p カメラ(頭)の位置に応じて視野⾓が 部屋の上下左右にフィットするように 変化してくれると良い 【理想】
ハンズオン⼿順 p 3D表⽰のテンプレート取得 p ビデオ画像の取得と表⽰ p facemeshで顔の認識 p 顔とカメラの位置を連動 p 奥⾏き空間のサイズ調整 p 画⾓の調整
CGの世界の視野設定 描画可能エリア top left ar e n r fa 背後の世界を スクリーンに投影 right bottom 【開発者にとっての視野設定】 p 描画可能なエリアをコンピュータに伝える⼿段。 p カメラを起点とした奥⾏(near far), 横⽅向の座標(left right), 縦⽅向の座標(top bottom)の6パラメータで数式化して表現。
描画判定(コンピュータ側の仕事) 1 0 -1 near x top left 1 right y z far -1 1 -1 bottom 【前後範囲の内外判定】 near~farのzを-1~1とし、CGのz座標を変換したときに範囲内なら描画 【スクリーンの内外判定】 left~rightのxを-1~1、bottom~topのyを-1~1とし、スクリーン(near) に投影した(座標変換した)ときのxy座標が範囲内なら描画
描画判定(開発者側の仕事) z c) y c, , (x c near bottom 0 zs x 1 top right near -1 y z 1 left right left top -1 1 (xs,ys) -1 bottom p left, right, top, bottom, near, farを使って描画領域を決定する p カメラを原点にした3次元座標(xc,yc,zc)を、描画領域の座標系での 座標(xs,ys,zs)に変換するためのルール(式)をコンピュータ側に教える
変換式 【やりたいこと】 CGの3D座標 xc, yc, zc 変換ルールをUnityがわかる形(数式)で表現 1 いい感じに変換 0 -1 𝑥! 𝑦! 𝑧! 1 = 座標変換 top left z 描画判定用の座標系 xs, ys, zs 1 near right y 【数式で書くと】 far -1 1 x -1 bottom 4×4の⾏列で表現 ←さっきの6パラメータで作れる 𝑥" 𝑦" 𝑧" 1 − 1 𝑧! 2 𝑛𝑒𝑎𝑟 𝑟𝑖𝑔ℎ𝑡 − 𝑙𝑒𝑓𝑡 0 0 2 𝑛𝑒𝑎𝑟 𝑡𝑜𝑝 − 𝑏𝑜𝑡𝑡𝑜𝑚 0 0 0 0 𝑟𝑖𝑔ℎ𝑡 + 𝑙𝑒𝑓𝑡 𝑟𝑖𝑔ℎ𝑡 − 𝑙𝑒𝑓𝑡 𝑡𝑜𝑝 + 𝑏𝑜𝑡𝑡𝑜𝑚 𝑡𝑜𝑝 − 𝑏𝑜𝑡𝑡𝑜𝑚 𝑓𝑎𝑟 + 𝑛𝑒𝑎𝑟 − 𝑓𝑎𝑟 − 𝑛𝑒𝑎𝑟 −1 0 0 − 2𝑓𝑎𝑟 𝑛𝑒𝑎𝑟 𝑓𝑎𝑟 − 𝑛𝑒𝑎𝑟 0
変換⾏列作成スクリプト c 2 𝑛𝑒𝑎𝑟 𝑟𝑖𝑔ℎ𝑡 − 𝑙𝑒𝑓𝑡 0 0 0 𝑟𝑖𝑔ℎ𝑡 + 𝑙𝑒𝑓𝑡 0 𝑟𝑖𝑔ℎ𝑡 − 𝑙𝑒𝑓𝑡 2 𝑛𝑒𝑎𝑟 𝑡𝑜𝑝 + 𝑏𝑜𝑡𝑡𝑜𝑚 0 𝑡𝑜𝑝 − 𝑏𝑜𝑡𝑡𝑜𝑚 𝑡𝑜𝑝 − 𝑏𝑜𝑡𝑡𝑜𝑚 𝑓𝑎𝑟 + 𝑛𝑒𝑎𝑟 2𝑓𝑎𝑟 𝑛𝑒𝑎𝑟 0 − − 𝑓𝑎𝑟 − 𝑛𝑒𝑎𝑟 𝑓𝑎𝑟 − 𝑛𝑒𝑎𝑟 0 −1 0 0
画⾓の調整
<script type="text/javascript">
var
var
var
var
var
video; //ビデオを扱う変数
model; //顔認識の学習モデル
camera; //カメラ(視点)を扱う変数
canvas; //描画領域
screenWidth, screenHeight; //表⽰領域のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
c
</script>
次はここに関数を追加
画⾓の調整 function PerspectiveOffCenter(left, right, bottom, top, near, far) { var x = 2.0 * near / (right - left); var y = 2.0 * near / (top - bottom); var a = (right + left) / (right - left); var b = (top + bottom) / (top - bottom); var c = -(far + near) / (far - near); var d = -(2.0 * far * near) / (far - near); var e = -1.0; var m = new THREE.Matrix4(); m.set( x,0,a,0, 0,y,b,0, 0,0,c, d,0,0, e,0); return m; } Lesson14
Left/Right/Top/Bottom 1 (-0.5, height/2, 0) (0.5, height/2, 0) Y X (-0.5, -height/2, 0) height (0.5, -height/2, 0) (x,y,z)
画⾓の調整
<script type="text/javascript">
var
var
var
var
var
video; //ビデオを扱う変数
model; //顔認識の学習モデル
camera; //カメラ(視点)を扱う変数
canvas; //描画領域
screenWidth, screenHeight; //表⽰領域のサイズ
var halfWidth, halfHeight; //表⽰領域の半分のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
}
</script>
Lesson15
画⾓の調整
<script type="text/javascript">
var
var
var
var
var
video; //ビデオを扱う変数
model; //顔認識の学習モデル
camera; //カメラ(視点)を扱う変数
canvas; //描画領域
screenWidth, screenHeight; //表⽰領域のサイズ
var halfWidth, halfHeight; //表⽰領域の半分のサイズ
//ページを読みこんだら実⾏。初期化に使⽤。
window.onload = function() {
};
//顔認識の学習モデル読み込み
async function LoadFaceModel(){
}
//顔認識をリアルタイムに⾏う
async function FaceTracking(){
c
}
</script>
次はここを編集
画⾓の調整
//描画領域のサイズが変わった場合
if(screenWidth!=canvas.width || screenHeight!=canvas.height){
screenWidth=canvas.width;
screenHeight=canvas.height;
//部屋の横幅を1に固定した場合の⾼さを計算
let height=screenHeight/screenWidth;
//部屋のオブジェクトを取得してサイズを変更
let room= document.getElementById('room');
room.setAttribute('scale', { x: 1, y: height, z: 1 });
}
halfwidth = 0.5; //今回は横幅を1に固定しているので0.5でOK
halfHeight = height/2; //部屋オブジェクトの⾼さの半分
//顔の検出
let predictions = await model.estimateFaces(video);
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//中略
}
Lesson16
画⾓の調整
//描画領域のサイズが変わった場合
if(screenWidth!=canvas.width || screenHeight!=canvas.height){
screenWidth=canvas.width;
screenHeight=canvas.height;
//部屋の横幅を1に固定した場合の⾼さを計算
let height=screenHeight/screenWidth;
//部屋のオブジェクトを取得してサイズを変更
let room= document.getElementById('room');
room.setAttribute('scale', { x: 1, y: height, z: 1 });
}
halfwidth = 0.5; //今回は横幅を1に固定しているので0.5でOK
halfHeight = height/2; //部屋オブジェクトの⾼さの半分
//顔の検出
let predictions = await model.estimateFaces(video);
c
//⾒つかった顔が1つ以上あれば
if (predictions.length > 0) {
//中略
}
次はここを編集
画⾓の調整
if (predictions.length > 0) {
//バウンディングボックスとその左上・右下の座標を取得
let keypoints = predictions[0].boundingBox;
let topLeft=keypoints.topLeft;
let bottomRight=keypoints.bottomRight;
//中⼼を計算(-1~1で正規化)
let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1;
let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1;
//カメラの位置を操作
camera.position.x=-cX; //左右反転
camera.position.y=-cY; //上下反転
camera.position.z=2.5; //奥⾏きは分からないので適当に設定
}
//変換⾏列を計算してカメラに適⽤
camera.projectionMatrix=PerspectiveOffCenter(
(-halfWidth-camera.position.x), (halfWidth-camera.position.x),
(-halfHeight-camera.position.y),(halfHeight-camera.position.y),
camera.position.z,5);
Lesson17
画⾓の調整
if (predictions.length > 0) {
//バウンディングボックスとその左上・右下の座標を取得
let keypoints = predictions[0].boundingBox;
let topLeft=keypoints.topLeft;
let bottomRight=keypoints.bottomRight;
//中⼼を計算(-1~1で正規化)
let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1;
let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1;
//カメラの位置を操作
camera.position.x=-cX * halfWidth; //左右反転
camera.position.y=-cY * halfHeight; //上下反転
camera.position.z=2.5; //奥⾏きは分からないので適当に設定
}
//変換⾏列を計算してカメラに適⽤
camera.projectionMatrix=PerspectiveOffCenter(
(-halfWidth-camera.position.x), (halfWidth-camera.position.x),
(-halfHeight-camera.position.y),(halfHeight-camera.position.y),
camera.position.z,5);
Lesson18
仕上げ a-sceneとvideoタグのsytleで指定している position: absolute;をそれぞれ削除
完成︕
c おまけ︓部屋の前⾯より⼿前を表⽰可能にする <a-entity id="objects" position = "0 0 0" scale="1 1 1">
おまけ︓部屋の前⾯より⼿前を表⽰可能にする //バウンディングボックスとその左上・右下の座標を取得 let keypoints = predictions[0].boundingBox; let topLeft=keypoints.topLeft; let bottomRight=keypoints.bottomRight; //中⼼を計算(-1~1で正規化) let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1; let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1; //カメラの位置を操作 camera.position.x=-cX * halfWidth; //左右反転 camera.position.y=-cY * halfHeight; //上下反転 camera.position.z=2.5; //奥⾏きは分からないので適当に設定 次はここを編集 //変換⾏列を計算してカメラに適⽤ camera.projectionMatrix=PerspectiveOffCenter( (-halfWidth-camera.position.x), (halfWidth-camera.position.x), (-halfHeight-camera.position.y),(halfHeight-camera.position.y), camera.position.z,5);
おまけ︓部屋の前⾯より⼿前を表⽰可能にする //中⼼を計算(-1~1で正規化) let cX=(topLeft[0]+bottomRight[0])/video.videoWidth-1; let cY=(topLeft[1]+bottomRight[1])/video.videoHeight-1; //カメラの位置を操作 camera.position.x=-cX * halfWidth; //左右反転 camera.position.y=-cY * halfHeight; //上下反転 camera.position.z=2.5; //奥⾏きは分からないので適当に設定 let near =1; //nearの値をカメラのz(=2.5)ではなくカメラから1m⼿前に変更 let a = near/camera.position.z; //near変更に伴うnear⾯のサイズ変化の割合 //変換⾏列を計算してカメラに適⽤ camera.projectionMatrix=PerspectiveOffCenter( (-halfWidth-camera.position.x)*a, (halfWidth-camera.position.x)*a, (-halfHeight-camera.position.y)*a,(halfHeight-camera.position.y)*a, near,5); 完成品19
参考資料 • A-Frameチュートリアル https://www.slideshare.net/ssuserc0d7fb/osc2020fukuoka-webar • mediapipeチュートリアル https://www.slideshare.net/ssuserc0d7fb/ai-xwebar-mediapipe-in •Kinect x Unity版ヘッドトラッキング https://speakerdeck.com/takashiyoshinaga/kinectwo yong-itahetudotoratukingu