Slide 1

Slide 1 text

AI x WebAR ハンドトラッキングで作るARライトセイバー

Slide 2

Slide 2 text

もろもろのダウンロード http://arfukuoka.lolipop.jp/ lightsaber_web/Sample.zip

Slide 3

Slide 3 text

⾃⼰紹介 ⽒名︓吉永崇(Takashi Yoshinaga) 専⾨︓ARシステムに関する研究・開発 コミュニティ︓ARコンテンツ作成勉強会 主催 Twitter: @Taka_Yoshinaga

Slide 4

Slide 4 text

ARコンテンツ作成勉強会の紹介 p 2013年5⽉に勉強会をスタート。 p ARコンテンツの作り⽅をハンズオン形式で学ぶ p ⼈数は5~10名程度の少⼈数で実施 p 参加条件はAR/VRに興味がある⼈(知識不要) p 各地で開催 (福岡、熊本、宮崎、⻑崎、⼤分、 ⿅児島、⼭⼝、広島、札幌、関東)

Slide 5

Slide 5 text

Twitterと勉強会ページで情報を発信しています @AR_Fukuoka Googleで「AR勉強会」で検索

Slide 6

Slide 6 text

#AR_Fukuoka ハッシュタグ

Slide 7

Slide 7 text

本題に⼊ります

Slide 8

Slide 8 text

本⽇のゴール MediaPipeのHandsによるハンドトラッキングで遊ぶ

Slide 9

Slide 9 text

テンプレートの複製 https://glitch.com/~lightsaber-template2 GET STARTED

Slide 10

Slide 10 text

テンプレートの複製 Remix Your Own

Slide 11

Slide 11 text

テンプレートの確認 index.htmlをクリックし、コードが表⽰されることを確認 index.html

Slide 12

Slide 12 text

index.htmlをクリックし、コードが表⽰されることを確認 テンプレートの確認 エディタ

Slide 13

Slide 13 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 14

Slide 14 text

テンプレートの確認 Lesson01

Slide 15

Slide 15 text

テンプレートの確認 ライブラリの 読み込み MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン) 描画領域等

Slide 16

Slide 16 text

テンプレートの確認 描画領域等

Slide 17

Slide 17 text

HTMLの記述の解説 input_video beam input_video output_canvas input_video

Slide 18

Slide 18 text

テンプレートの確認 ライブラリの 読み込み

Slide 19

Slide 19 text

ライブラリ読み込みの解説 OpenCV Camera Utils Hands + drawing utils

Slide 20

Slide 20 text

テンプレートの確認 MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン)

Slide 21

Slide 21 text

javascript記述の解説

Slide 22

Slide 22 text

javascript記述の解説 変数宣⾔ 初期化 描画領域/カメラ/ ハンドトラッキング 認識結果の利⽤

Slide 23

Slide 23 text

変数の宣⾔ 変数宣⾔

Slide 24

Slide 24 text

初期化に関する記述 初期化 描画領域/カメラ/ ハンドトラッキング

Slide 25

Slide 25 text

初期化に関する記述 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) {/*⼿の検出結果を利⽤する*/ }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

ハンドトラッキングの初期化 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) {/*⼿の検出結果を利⽤する*/ } 詳細は後ほど実装

Slide 28

Slide 28 text

カメラの初期化と起動 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の映像を ハンドトラッキング処理に渡す 画像サイズを設定

Slide 29

Slide 29 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 30

Slide 30 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 31

Slide 31 text

テンプレートの確認 認識結果の利⽤

Slide 32

Slide 32 text

カメラ画像の表⽰ //⼿の検出結果を利⽤する 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

Slide 33

Slide 33 text

動作確認 PREVIEW

Slide 34

Slide 34 text

動作確認 Preview in a New Window

Slide 35

Slide 35 text

動作確認 カメラへのアクセスを許可

Slide 36

Slide 36 text

動作確認

Slide 37

Slide 37 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 38

Slide 38 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 39

Slide 39 text

ハンドトラッキング結果の表⽰ 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();

Slide 40

Slide 40 text

動作確認 プレビュー⽤のタブをクリック

Slide 41

Slide 41 text

動作確認 再読み込み

Slide 42

Slide 42 text

動作確認

Slide 43

Slide 43 text

MediaPipe Handsのパラメータをいじろう • 画像の左右反転 • 認識する⼿の数の上限の変更

Slide 44

Slide 44 text

初期化に関する記述 初期化 描画領域/カメラ/ ハンドトラッキング

Slide 45

Slide 45 text

画像の反転 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

Slide 46

Slide 46 text

認識する⼿の数の上限を変更 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に変更

Slide 47

Slide 47 text

動作確認 プレビュー⽤のタブをクリック

Slide 48

Slide 48 text

動作確認 再読み込み

Slide 49

Slide 49 text

動作確認

Slide 50

Slide 50 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 51

Slide 51 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 52

Slide 52 text

変数の宣⾔ 変数宣⾔

Slide 53

Slide 53 text

画像をスクリプトで扱う準備 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

Slide 54

Slide 54 text

画像の追加しよう 描画領域等

Slide 55

Slide 55 text

画像のアップロード Assets

Slide 56

Slide 56 text

Upload an Asset 画像のアップロード

Slide 57

Slide 57 text

画像のアップロード beam.png

Slide 58

Slide 58 text

画像のアップロード 画像をクリック

Slide 59

Slide 59 text

画像のURLを取得 Copy URL

Slide 60

Slide 60 text

画像のURLを取得 空⽩をクリック

Slide 61

Slide 61 text

画像をページ内に追加 URLを貼り付ける ⾮表⽰Offの場合、カメラ画像が出る前に⼀瞬表⽰される display:none;を⼀旦削除(動作確認後は戻す) beam input_video(⾮表⽰) Lesson07

Slide 62

Slide 62 text

Canvas上でライトセイバーを表⽰ 認識結果の利⽤ rcvResults

Slide 63

Slide 63 text

⼿の検出と連動した画像表⽰ function recvResults(results) { /*canvasのサイズ指定と画像の描画(スペースの都合により省略)*/ if (results.multiHandLandmarks) { //⾒つけた⼿の数だけ処理を繰り返す for (const landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(/*省略*/); //関節を描画 drawLandmarks(/*省略*/); drawLightSaber(); } } canvasCtx.restore(); } //ライトセイバーを表⽰ function drawLightSaber(){ } Lesson08

Slide 64

Slide 64 text

⼿の検出と連動した画像表⽰ 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, 横幅, 縦幅

Slide 65

Slide 65 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 66

Slide 66 text

動作確認 (0,0) width height

Slide 67

Slide 67 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 68

Slide 68 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 69

Slide 69 text

⼿の位置・⾓度の算出に関する考え⽅ 関節点の集まり(多数の点)から、⼿の位置・⾓度を計算したい ⎼ OpenCVで図形に近似すると扱いが楽になる。 → 今回は楕円に近似 ⎼ 楕円の中⼼を⼿の位置、傾きを⼿の向き、幅を画⾯上の⼿のサイズとする ⎼ ⼿⾸や親指の付け根は計算には⽤いないこととする OpenCV

Slide 70

Slide 70 text

⼿の位置・⾓度の計算 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

Slide 71

Slide 71 text

⼿の位置・⾓度の計算 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

Slide 72

Slide 72 text

⼿の関節に対応するインデックス ここは無視

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

楕円の表⽰ 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

Slide 75

Slide 75 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 76

Slide 76 text

動作確認

Slide 77

Slide 77 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 78

Slide 78 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 79

Slide 79 text

画像を表⽰ 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); }

Slide 80

Slide 80 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 81

Slide 81 text

動作確認 右に傾けると上⼿く⾏かない

Slide 82

Slide 82 text

傾きが正しい場合と正しくない場合 140度 60度 120度 楕円の傾きはY軸からの⾓度(円の左半分)で計算される

Slide 83

Slide 83 text

⾓度の補正 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); }

Slide 84

Slide 84 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 85

Slide 85 text

動作確認 ⼤きさがちょっとショボい

Slide 86

Slide 86 text

サイズを⼤きくしよう 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);

Slide 87

Slide 87 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 88

Slide 88 text

動作確認 ⼤きくなったけどズレてる

Slide 89

Slide 89 text

現状確認 描画の原点=⼿の位置 画像beamの輪郭 サイズを変える前から 実はそもそもズレていた

Slide 90

Slide 90 text

画像の位置の補正 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⽅向に半分ずらす

Slide 91

Slide 91 text

動作確認 プレビュー⽤のタブを開いたあと、再読み込み

Slide 92

Slide 92 text

動作確認

Slide 93

Slide 93 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 94

Slide 94 text

ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

Slide 95

Slide 95 text

親指の状態の計算 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

Slide 96

Slide 96 text

親指の状態の計算 4 7 19 l Distance1: 親指(4)から人差し指(7)までの距離 l Distance2: 人差し指(7)から小指(19)までの距離 l 親指の状態(ratio): Distance1 / Distance2 ※ratioの大小で親指の状態を評価

Slide 97

Slide 97 text

親指の状態計算 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

Slide 98

Slide 98 text

親指の状態を反映 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(); /*スペースの都合により省略*/ } 親指の⽴ち具合をかける

Slide 99

Slide 99 text

完成︕