Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MediaPipeのハンドトラッキングで作るARライトセイバー
Search
TakashiYoshinaga
March 05, 2022
390
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
MediaPipeのハンドトラッキングで作るARライトセイバー
2022/03/05のXRワークショップでの資料
TakashiYoshinaga
March 05, 2022
More Decks by TakashiYoshinaga
See All by TakashiYoshinaga
Nreal Light / Air 開発入門ハンズオン
takashiyoshinaga
0
1.1k
UnityとZapWorksで始めようWebAR開発
takashiyoshinaga
0
4.2k
Getting Started with WebAR for HoloLens2 and Meta Quest
takashiyoshinaga
0
2.9k
Getting Started with HoloSDK
takashiyoshinaga
0
320
Getting Started with Non-Programming AR Development with MRTK v2.4.0
takashiyoshinaga
0
2.9k
Getting Started With MRTK (for Beginner)
takashiyoshinaga
0
750
始めようWebAR/VR開発
takashiyoshinaga
1
1.1k
Getting Started with Azure Kinect DK
takashiyoshinaga
1
6.2k
Getting Started with WebVR Contents Creation for Oculus Quest
takashiyoshinaga
2
660
Featured
See All Featured
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
190
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Google's AI Overviews - The New Search
badams
0
1k
Designing Experiences People Love
moore
143
24k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
65
55k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
330
Navigating Team Friction
lara
192
16k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
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(); /*スペースの都合により省略*/ } 親指の⽴ち具合をかける
完成︕