AI(Google Mediapipe)によるハンドトラッキングハンズオン

27.1K Views

July 16, 22

スライド概要

2022/07/16に開催したハンズオンの資料

profile-image

可視化技術や人間計測/空間計測技術を活用した問題解決に関する研究開発。 ARコンテンツ作成勉強会(tryAR)を主催。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

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

2.

もろもろのダウンロード https://github.com/TakashiYoshinaga/AR-Fukuoka/ raw/main/mediahand-20220716/Sample.zip

3.

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

4.

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

5.

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

6.

ハッシュタグ #エンジニアカフェ #AR_Fukuoka

7.

本題に⼊ります

8.

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

9.

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

10.

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

11.

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

12.

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

13.

プレビューを閉じる index.htmlをクリックし、コードが表⽰されることを確認 メニューを開く

14.

プレビューを閉じる index.htmlをクリックし、コードが表⽰されることを確認 Close Preview

15.

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

16.

テンプレートの確認 Lesson01

17.

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

18.

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

19.
[beta]
HTMLの記述の解説
<!--Webカメラの映像を取得(不可視に設定済み)-->
<video id="input_video" style="position:absolute; display:none;"></video>
<!--ライトセーバー的な画像(今は何も表⽰されない)-->
<img id="beam" src="画像のURL" style="position:absolute;">
<!--カメラ画像と⼿の認識結果を合成して表⽰-->
<canvas id="output_canvas" style="position:absolute;"></canvas>

beam

input_video

input_video

output_canvas

input_video

20.

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

21.
[beta]
ライブラリ読み込みの解説
<!--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>

Camera Utils

Hands + drawing utils

OpenCV

22.

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

23.

javascript記述の解説

24.

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

25.

変数の宣⾔ 変数宣⾔

26.

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

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

28.
[beta]
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();
};

output_canvas

input_video

input_video

function recvResults(results) {/*⼿の検出結果を利⽤する*/ }

29.
[beta]
ハンドトラッキングの初期化
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) {/*⼿の検出結果を利⽤する*/ }

30.
[beta]
カメラの初期化と起動
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
});
//結果を処理する関数を登録
videoElementの映像を
hands.onResults(recvResults);
ハンドトラッキング処理に渡す
//カメラの初期化
const camera = new Camera(videoElement, {
onFrame: async () => {
await hands.send({image: videoElement});
},
width: 1280, height: 720
});
画像サイズを設定
camera.start(); //カメラの動作開始
};
function recvResults(results) {/*⼿の検出結果を利⽤する*/ }

31.

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

32.

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

33.

カメラ画像の表⽰ 認識結果の利⽤

34.

カメラ画像の表⽰ height //⼿の検出結果を利⽤する function recvResults(results) { let width=results.image.width; let height=results.image.height; //画像のサイズとcanvasのサイズが異なる場合はサイズを調整 if(width!=canvasElement.width){ //⼊⼒画像と同じサイズのcanvas(描画領域)を⽤意 (0,0) canvasElement.width=width; canvasElement.height=height; width } //以下、canvasへの描画に関する記述 canvasCtx.save(); //画像を表⽰ canvasCtx.drawImage(results.image, 0, 0, width, height); canvasCtx.restore(); Lesson02 }

35.

動作確認 PREVIEW

36.

動作確認 Preview in a New Window

37.

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

38.

動作確認 カメラへ画像が表⽰される

39.

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

40.

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

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

Lesson03

42.

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

43.

動作確認 再読み込み

44.

動作確認

45.

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

46.

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

47.
[beta]
画像の反転
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

48.
[beta]
認識する⼿の数の上限を変更
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を1に変更
selfieMode:true, //画像を左右反転
maxNumHands: 1, //認識可能な⼿の最⼤数
modelComplexity: 1,//精度に関する設定(0~1)
minDetectionConfidence: 0.5,//⼿検出の信頼度
minTrackingConfidence: 0.5,//⼿追跡の信頼度
useCpuInference: false, //M1 MacのSafariの場合は1
});
/*以下省略*/

Lesson05

49.

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

50.

動作確認 再読み込み

51.

動作確認

52.

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

53.

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

54.

変数の宣⾔ 変数宣⾔

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

56.

画像の追加しよう <img id="beam" src="画像のURL" style="position:absolute; display:none;"> 描画領域等

57.

画像のアップロード Assets

58.

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

59.

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

60.

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

61.

画像のURLを取得 Copy URL

62.

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

63.

画像をページ内に追加 <body> URLを貼り付ける <video id="input_video" style="position:absolute; display:none;"></video> <img id="beam" src="画像のURL" style="position:absolute;"> <canvas id="output_canvas" style="position:absolute;"></canvas> </body> beam input_video(⾮表⽰) カメラ画像が出る前に⼀瞬表⽰される Lesson07

64.

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

65.

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

66.

⼿の検出と連動した画像表⽰ function recvResults(results) { /*canvasのサイズ指定と画像の描画(スペースの都合により省略)*/ } if (results.multiHandLandmarks) { for (const landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(/*省略*/); //関節を描画 drawLandmarks(/*省略*/); drawLightSaber(); } } canvasCtx.restore(); //ライトセイバーを表⽰ function drawLightSaber(){ 画像, 位置X, 位置Y, 横幅, 縦幅 canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); } Lesson09

67.

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

68.

動作確認 height (0,0) width ⼿の検出に合わせて画像が表⽰される

69.

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

70.

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

71.

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

72.
[beta]
⼿の位置・⾓度の計算
let
let
let
let

canvasElement;
canvasCtx;
beam; //ライトセイバー的な画像
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

73.

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

74.

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

75.

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

76.

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

77.

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

78.

動作確認

79.

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

80.

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

81.

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

82.

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

83.

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

84.

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

85.
[beta]
⾓度の補正
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);
Lesson15

86.

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

87.

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

88.
[beta]
サイズを⼤きくしよう
//楕円の⾓度
let angle = ell.angle;
//ライトセイバーの向きを反転
if(angle<90){ angle=angle-180; }
//デフォルトサイズを元画像の2倍くらいにしたい。
let mul = 2.0;*ell.size.width/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);
Lesson16

89.

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

90.

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

91.

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

92.
[beta]
画像の位置の補正
//楕円の⾓度
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();
//デフォルトサイズに倍数をかける
あらかじめx⽅向に半分ずらす
canvasCtx.scale(mul, mul);
canvasCtx.drawImage(beam, -beam.width/2.0, 0,
beam.width, beam.height);
Lesson17

93.

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

94.

動作確認

95.

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

96.

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

97.
[beta]
親指の状態の計算
let
let
let
let
let

canvasElement;
canvasCtx;
beam; //ライトセイバー的な画像
ell; //⼿の位置や傾きを表す楕円
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

98.

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

99.
[beta]
親指の状態計算
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

100.
[beta]
親指の状態を反映
function drawLightSaber(){
//楕円の⾓度
let angle = ell.angle;
//ライトセイバーの向きを反転
if(angle<90){ angle=angle-180; }

親指の⽴ち具合をかける

//デフォルトサイズを元画像の2倍くらいにしたい。
let mul = ratio * 2.0 * ell.size.width/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();
/*スペースの都合により省略*/
}

Lesson19

101.

完成︕