Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
MediaPipeのハンドトラッキングで作るARライトセイバー
TakashiYoshinaga
March 05, 2022
1
52
MediaPipeのハンドトラッキングで作るARライトセイバー
2022/03/05のXRワークショップでの資料
TakashiYoshinaga
March 05, 2022
Tweet
Share
More Decks by TakashiYoshinaga
See All by TakashiYoshinaga
Nreal Light / Air 開発入門ハンズオン
takashiyoshinaga
0
68
UnityとZapWorksで始めようWebAR開発
takashiyoshinaga
0
1k
Getting Started with WebAR for HoloLens2 and Meta Quest
takashiyoshinaga
0
590
Getting Started with HoloSDK
takashiyoshinaga
0
170
Getting Started with Non-Programming AR Development with MRTK v2.4.0
takashiyoshinaga
0
840
Getting Started With MRTK (for Beginner)
takashiyoshinaga
0
380
始めようWebAR/VR開発
takashiyoshinaga
1
520
Getting Started with Azure Kinect DK
takashiyoshinaga
1
3.6k
Getting Started with WebVR Contents Creation for Oculus Quest
takashiyoshinaga
2
390
Featured
See All Featured
Bash Introduction
62gerente
597
210k
The Pragmatic Product Professional
lauravandoore
19
3k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
655
120k
Fantastic passwords and where to find them - at NoRuKo
philnash
27
1.5k
Pencils Down: Stop Designing & Start Developing
hursman
112
9.8k
Practical Orchestrator
shlominoach
178
8.6k
What's in a price? How to price your products and services
michaelherold
229
9.4k
Six Lessons from altMBA
skipperchong
14
1.4k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
12
920
Why You Should Never Use an ORM
jnunemaker
PRO
47
7.6k
Web development in the modern age
philhawksworth
197
9.3k
GitHub's CSS Performance
jonrohan
1020
420k
Transcript
AI x WebAR ハンドトラッキングで作るARライトセイバー
もろもろのダウンロード http://arfukuoka.lolipop.jp/ lightsaber_web/Sample.zip
⾃⼰紹介 ⽒名︓吉永崇(Takashi Yoshinaga) 専⾨︓ARシステムに関する研究・開発 コミュニティ︓ARコンテンツ作成勉強会 主催 Twitter: @Taka_Yoshinaga
ARコンテンツ作成勉強会の紹介 p 2013年5⽉に勉強会をスタート。 p ARコンテンツの作り⽅をハンズオン形式で学ぶ p ⼈数は5~10名程度の少⼈数で実施 p 参加条件はAR/VRに興味がある⼈(知識不要) p
各地で開催 (福岡、熊本、宮崎、⻑崎、⼤分、 ⿅児島、⼭⼝、広島、札幌、関東)
Twitterと勉強会ページで情報を発信しています @AR_Fukuoka Googleで「AR勉強会」で検索
#AR_Fukuoka ハッシュタグ
本題に⼊ります
本⽇のゴール MediaPipeのHandsによるハンドトラッキングで遊ぶ
テンプレートの複製 https://glitch.com/~lightsaber-template2 GET STARTED
テンプレートの複製 Remix Your Own
テンプレートの確認 index.htmlをクリックし、コードが表⽰されることを確認 index.html
index.htmlをクリックし、コードが表⽰されることを確認 テンプレートの確認 エディタ
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
テンプレートの確認 Lesson01
テンプレートの確認 ライブラリの 読み込み MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン) 描画領域等
テンプレートの確認 描画領域等
HTMLの記述の解説 <!--Webカメラの映像を取得(不可視に設定済み)--> <video id="input_video" style="position:absolute; display:none;"></video> <!--ライトセーバー的な画像(不可視に設定済み)--> <img id="beam" src="画像のURL"
style="position:absolute; display:none;"> <!--カメラ画像と⼿の認識結果を合成して表⽰--> <canvas id="output_canvas" style="position:absolute;"></canvas> input_video beam input_video output_canvas input_video
テンプレートの確認 ライブラリの 読み込み
ライブラリ読み込みの解説 <!--media pipe: 手の骨格取得や認識結果の描画に使用--> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/ camera_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/
drawing_utils.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/ hands.js" crossorigin="anonymous"></script> <!--opencv.js: 手の傾きや中心位置を計算するために使用--> <script src="https://docs.opencv.org/3.4.1/opencv.js"></script> OpenCV Camera Utils Hands + drawing utils
テンプレートの確認 MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン)
javascript記述の解説
javascript記述の解説 変数宣⾔ 初期化 描画領域/カメラ/ ハンドトラッキング 認識結果の利⽤
変数の宣⾔ 変数宣⾔
初期化に関する記述 初期化 描画領域/カメラ/ ハンドトラッキング
初期化に関する記述 window.onload = function() { let videoElement = document.getElementById('input_video'); canvasElement
= document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ maxNumHands: 2, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合は1 }); //結果を処理する関数を登録 hands.onResults(recvResults); //カメラの初期化 const camera = new Camera(videoElement, { onFrame: async () => { await hands.send({image: videoElement}); }, width: 1280, height: 720 }); camera.start(); //カメラの動作開始 }; function recvResults(results) {/*⼿の検出結果を利⽤する*/ }
HTMLの要素との関連付け window.onload = function() { let videoElement = document.getElementById('input_video'); //ビデオ要素の取得
canvasElement = document.getElementById('output_canvas'); //表⽰⽤のCanvasを取得 canvasCtx = canvasElement.getContext('2d'); //Canvas描画に関する情報にアクセス //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ maxNumHands: 2, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合は1 }); //結果を処理する関数を登録 hands.onResults(recvResults); //カメラの初期化 const camera = new Camera(videoElement, { onFrame: async () => { await hands.send({image: videoElement}); }, width: 1280, height: 720 }); camera.start(); }; function recvResults(results) {/*⼿の検出結果を利⽤する*/ } input_video output_canvas input_video
ハンドトラッキングの初期化 window.onload = function() { let videoElement = document.getElementById('input_video'); //ビデオ要素の取得
canvasElement = document.getElementById('output_canvas'); //表⽰⽤のCanvasを取得 canvasCtx = canvasElement.getContext('2d'); //Canvas描画に関する情報にアクセス //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ maxNumHands: 2, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合はtrue }); //結果を処理する関数を登録 hands.onResults(recvResults); //カメラの初期化 const camera = new Camera(videoElement, { onFrame: async () => { await hands.send({image: videoElement}); }, width: 1280, height: 720 }); camera.start(); //カメラの動作開始 }; function recvResults(results) {/*⼿の検出結果を利⽤する*/ } 詳細は後ほど実装
カメラの初期化と起動 window.onload = function() { let videoElement = document.getElementById('input_video'); canvasElement
= document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ maxNumHands: 2, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合は1 }); //結果を処理する関数を登録 hands.onResults(recvResults); //カメラの初期化 const camera = new Camera(videoElement, { onFrame: async () => { await hands.send({image: videoElement}); }, width: 1280, height: 720 }); camera.start(); //カメラの動作開始 }; function recvResults(results) {/*⼿の検出結果を利⽤する*/ } videoElementの映像を ハンドトラッキング処理に渡す 画像サイズを設定
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
テンプレートの確認 認識結果の利⽤
カメラ画像の表⽰ //⼿の検出結果を利⽤する function recvResults(results) { let width=results.image.width; let height=results.image.height; //画像のサイズとcanvasのサイズが異なる場合はサイズを調整
if(width!=canvasElement.width){ //⼊⼒画像と同じサイズのcanvas(描画領域)を⽤意 canvasElement.width=width; canvasElement.height=height; } //以下、canvasへの描画に関する記述 canvasCtx.save(); //画像を表⽰ canvasCtx.drawImage(results.image, 0, 0, width, height); canvasCtx.restore(); } Lesson02 (0,0) width height
動作確認 PREVIEW
動作確認 Preview in a New Window
動作確認 カメラへのアクセスを許可
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンドトラッキング結果の表⽰ Lesson03 canvasCtx.save(); //画像を表⽰ canvasCtx.drawImage(results.image, 0, 0, width, height); //⼿を検出したならばtrue
if (results.multiHandLandmarks) { //⾒つけた⼿の数だけ処理を繰り返す for (const landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {color: '#00FF00', lineWidth: 2}); //関節を描画 drawLandmarks(canvasCtx, landmarks, { color: '#FF0000', lineWidth: 1,radius:2}); } } canvasCtx.restore();
動作確認 プレビュー⽤のタブをクリック
動作確認 再読み込み
動作確認
MediaPipe Handsのパラメータをいじろう • 画像の左右反転 • 認識する⼿の数の上限の変更
初期化に関する記述 初期化 描画領域/カメラ/ ハンドトラッキング
画像の反転 window.onload = function() { let videoElement = document.getElementById('input_video'); canvasElement
= document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ selfieMode:true, //画像を左右反転 maxNumHands: 2, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合は1 }); /*以下省略*/ Lesson04
認識する⼿の数の上限を変更 window.onload = function() { let videoElement = document.getElementById('input_video'); canvasElement
= document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ selfieMode:true, //画像を左右反転 maxNumHands: 1, //認識可能な⼿の最⼤数 modelComplexity: 1,//精度に関する設定(0~1) minDetectionConfidence: 0.5,//⼿検出の信頼度 minTrackingConfidence: 0.5,//⼿追跡の信頼度 useCpuInference: false, //M1 MacのSafariの場合は1 }); /*以下省略*/ Lesson05 maxNumHandsを1に変更
動作確認 プレビュー⽤のタブをクリック
動作確認 再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
変数の宣⾔ 変数宣⾔
画像をスクリプトで扱う準備 let canvasElement; let canvasCtx; let beam; //ライトセイバー的な画像 //初期化 window.onload
= function() { //画像の読み込み beam = document.getElementById("beam"); let videoElement = document.getElementById('input_videoʼ); canvasElement = document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ /*省略*/ }); Lesson06
画像の追加しよう 描画領域等 <img id="beam" src="画像のURL" style="position:absolute; display:none;">
画像のアップロード Assets
Upload an Asset 画像のアップロード
画像のアップロード beam.png
画像のアップロード 画像をクリック
画像のURLを取得 Copy URL
画像のURLを取得 空⽩をクリック
画像をページ内に追加 <body> <video id="input_video" style="position:absolute; display:none;"></video> <img id="beam" src="画像のURL" style="position:absolute;
display:none;"> <canvas id="output_canvas" style="position:absolute;"></canvas> </body> URLを貼り付ける ⾮表⽰Offの場合、カメラ画像が出る前に⼀瞬表⽰される display:none;を⼀旦削除(動作確認後は戻す) beam input_video(⾮表⽰) Lesson07
Canvas上でライトセイバーを表⽰ 認識結果の利⽤ rcvResults
⼿の検出と連動した画像表⽰ function recvResults(results) { /*canvasのサイズ指定と画像の描画(スペースの都合により省略)*/ if (results.multiHandLandmarks) { //⾒つけた⼿の数だけ処理を繰り返す for
(const landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(/*省略*/); //関節を描画 drawLandmarks(/*省略*/); drawLightSaber(); } } canvasCtx.restore(); } //ライトセイバーを表⽰ function drawLightSaber(){ } Lesson08
⼿の検出と連動した画像表⽰ function recvResults(results) { /*canvasのサイズ指定と画像の描画(スペースの都合により省略)*/ if (results.multiHandLandmarks) { for (const
landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(/*省略*/); //関節を描画 drawLandmarks(/*省略*/); drawLightSaber(); } } canvasCtx.restore(); } //ライトセイバーを表⽰ function drawLightSaber(){ canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); } Lesson09 画像, 位置X, 位置Y, 横幅, 縦幅
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 (0,0) width height
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
⼿の位置・⾓度の算出に関する考え⽅ 関節点の集まり(多数の点)から、⼿の位置・⾓度を計算したい ⎼ OpenCVで図形に近似すると扱いが楽になる。 → 今回は楕円に近似 ⎼ 楕円の中⼼を⼿の位置、傾きを⼿の向き、幅を画⾯上の⼿のサイズとする ⎼ ⼿⾸や親指の付け根は計算には⽤いないこととする
OpenCV
⼿の位置・⾓度の計算 let canvasElement; let canvasCtx; let beam; //ライトセイバー的な画像 let ell;
//⼿の位置や傾きを表す楕円 //初期化 window.onload = function() { //画像の読み込み beam = loadImage('画像のURL'); let videoElement = document.getElementById('input_videoʼ); canvasElement = document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); //⼿の認識に関するオプション hands.setOptions({ /*省略*/ }); Lesson10
⼿の位置・⾓度の計算 function recvResults(results) { /*描画領域のサイズ設定など(スペースの都合上省略)*/ if (results.multiHandLandmarks) { //⾒つけた⼿の数だけ処理を繰り返す for
(const landmarks of results.multiHandLandmarks) { drawConnectors(/*略*/); drawLandmarks(canvasCtx, landmarks, {/*略*/); cvFunction(landmarks,width,height); drawLightSaber(); } } canvasCtx.restore(); } //⼿の中⼼や傾きを計算 function cvFunction(landmarks,width,height){ } Lesson11
⼿の関節に対応するインデックス ここは無視
OpenCVを⽤いた楕円近似 //⼿の中⼼や傾きを計算 function cvFunction(landmarks,width,height){ //⼿の関節を保持する配列 let points = []; //⼿のひらや親指の付け根付近以外の関節を取得
for(var i=2;i<21;i++){ //0~1で表現されたx,yを画像のサイズに変換 points.push(landmarks[i].x*width); points.push(landmarks[i].y*height); } //点の集まりをOpenCVで扱えるデータフォーマットに変換 let mat = cv.matFromArray( points.length/2, 1, cv.CV_32SC2, points); //点の集まりにフィットする楕円を計算 ell = cv.fitEllipse(mat); //メモリの解放 mat.delete(); } Lesson12
楕円の表⽰ function drawLightSaber(){ //楕円の⾓度 let angle = ell.angle; //位置指定 canvasCtx.translate(ell.center.x,
ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, //位置 ell.size.width/2.0, ell.size.height/2.0, //半径 0, 0, 2 * Math.PI); //⾓度と表⽰の開始・終了 canvasCtx.stroke(); //⼀旦コメント化 //canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); } Lesson13
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
画像を表⽰ Lesson14 function drawLightSaber(){ //楕円の⾓度 let angle = ell.angle; //位置指定
canvasCtx.translate(ell.center.x, ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, //位置 ell.size.width/2.0, ell.size.height/2.0, //半径 0, 0, 2 * Math.PI); //⾓度と表⽰の開始・終了 canvasCtx.stroke(); //再度有効化 canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); }
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 右に傾けると上⼿く⾏かない
傾きが正しい場合と正しくない場合 140度 60度 120度 楕円の傾きはY軸からの⾓度(円の左半分)で計算される
⾓度の補正 Lesson15 function drawLightSaber(){ //楕円の⾓度 let angle = ell.angle; //ライトセイバーの向きを反転
if(angle<90){ angle=angle-180; } //位置指定 canvasCtx.translate(ell.center.x, ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, ell.size.width/2.0, ell.size.height/2.0, 0, 0, 2 * Math.PI); canvasCtx.stroke(); canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); }
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 ⼤きさがちょっとショボい
サイズを⼤きくしよう Lesson16 //楕円の⾓度 let angle = ell.angle; //ライトセイバーの向きを反転 if(angle<90){ angle=angle-180;
} //デフォルトサイズを元画像の2倍くらいにしたい。 let mul = (ell.size.width*2.0)/beam.width; //位置指定 canvasCtx.translate(ell.center.x, ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, ell.size.width/2.0, ell.size.height/2.0, 0, 0, 2 * Math.PI); canvasCtx.stroke(); //デフォルトサイズに倍数をかける canvasCtx.scale(mul, mul); canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height);
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 ⼤きくなったけどズレてる
現状確認 描画の原点=⼿の位置 画像beamの輪郭 サイズを変える前から 実はそもそもズレていた
画像の位置の補正 Lesson17 //楕円の⾓度 let angle = ell.angle; //ライトセイバーの向きを反転 if(angle<90){ angle=angle-180;
} //デフォルトサイズを元画像の2倍くらいにしたい。 let mul = (ell.size.width*2.0)/beam.width; //位置指定 canvasCtx.translate(ell.center.x, ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, ell.size.width/2.0, ell.size.height/2.0, 0, 0, 2 * Math.PI); canvasCtx.stroke(); //デフォルトサイズに倍数をかける canvasCtx.scale(mul, mul); canvasCtx.drawImage(beam, -beam.width/2.0, 0, beam.width, beam.height); あらかじめx⽅向に半分ずらす
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動
親指の状態の計算 let canvasElement; let canvasCtx; let beam; //ライトセイバー的な画像 let ell;
//⼿の位置や傾きを表す楕円 let ratio; //親指の⽴ち具合を保持 //初期化 window.onload = function() { //画像の読み込み beam = loadImage('画像のURL'); let videoElement = document.getElementById('input_videoʼ); canvasElement = document.getElementById('output_canvas'); canvasCtx = canvasElement.getContext('2d'); //HandTrackingを使⽤するための関連ファイルの取得と初期化 const hands = new Hands({locateFile: (file) => { return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; }}); /*以下省略*/ Lesson18
親指の状態の計算 4 7 19 l Distance1: 親指(4)から人差し指(7)までの距離 l Distance2: 人差し指(7)から小指(19)までの距離
l 親指の状態(ratio): Distance1 / Distance2 ※ratioの大小で親指の状態を評価
親指の状態計算 function cvFunction(landmarks,width,height){ /*⼿を楕円に近似(スペースの都合上、省略)*/ //メモリの解放 mat.delete(); //親指と⼈差し指までの距離 let dx=(landmarks[7].x-landmarks[4].x)*width; let
dy=(landmarks[7].y-landmarks[4].y)*height; let distance1 = Math.sqrt(dx*dx + dy*dy); //⼈差し指から⼩指までの距離 dx=(landmarks[7].x-landmarks[19].x)*width; dy=(landmarks[7].y-landmarks[19].y)*height; let distance2 = Math.sqrt(dx*dx + dy*dy); //⽐率の計算 ratio=distance1/distance2; //0.9:close, 1.3:sumb upとして0.9~1.3を0~1に正規化 let close=0.9; let up=1.3; ratio=(Math.max(close,Math.min(up,ratio))-close)/(up-close); } Lesson19
親指の状態を反映 Lesson19 function drawLightSaber(){ //楕円の⾓度 let angle = ell.angle; //ライトセイバーの向きを反転
if(angle<90){ angle=angle-180; } //デフォルトサイズを元画像の2倍くらいにしたい。 let mul = ratio * (ell.size.width*2.0)/beam.width; //位置指定 canvasCtx.translate(ell.center.x, ell.center.y); //⾓度指定 canvasCtx.rotate(angle * Math.PI /180.0); //楕円を描画 canvasCtx.beginPath(); canvasCtx.ellipse(0, 0, ell.size.width/2.0, ell.size.height/2.0, 0, 0, 2 * Math.PI); canvasCtx.stroke(); /*スペースの都合により省略*/ } 親指の⽴ち具合をかける
完成︕