27.3K Views
July 16, 22
スライド概要
2022/07/16に開催したハンズオンの資料
可視化技術や人間計測/空間計測技術を活用した問題解決に関する研究開発。 ARコンテンツ作成勉強会(tryAR)を主催。
AI x OpenCV x WebAR ハンドトラッキングで作るARライトセイバー
もろもろのダウンロード https://github.com/TakashiYoshinaga/AR-Fukuoka/ raw/main/mediahand-20220716/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をクリックし、コードが表⽰されることを確認 エディタ Preview
プレビューを閉じる index.htmlをクリックし、コードが表⽰されることを確認 メニューを開く
プレビューを閉じる index.htmlをクリックし、コードが表⽰されることを確認 Close Preview
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
テンプレートの確認 Lesson01
テンプレートの確認 ライブラリの 読み込み MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン) 描画領域等
テンプレートの確認 描画領域等
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
テンプレートの確認 ライブラリの 読み込み
ライブラリ読み込みの解説
<!--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
テンプレートの確認 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();
};
output_canvas
input_video
input_video
function recvResults(results) {/*⼿の検出結果を利⽤する*/ }
ハンドトラッキングの初期化
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
});
//結果を処理する関数を登録
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) {/*⼿の検出結果を利⽤する*/ }
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
カメラ画像の表⽰ 認識結果の利⽤
カメラ画像の表⽰ 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 }
動作確認 PREVIEW
動作確認 Preview in a New Window
動作確認 カメラへのアクセスを許可
動作確認 カメラへ画像が表⽰される
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンドトラッキング結果の表⽰
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
動作確認 プレビュー⽤のタブをクリック
動作確認 再読み込み
動作確認
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({
maxNumHandsを1に変更
selfieMode:true, //画像を左右反転
maxNumHands: 1, //認識可能な⼿の最⼤数
modelComplexity: 1,//精度に関する設定(0~1)
minDetectionConfidence: 0.5,//⼿検出の信頼度
minTrackingConfidence: 0.5,//⼿追跡の信頼度
useCpuInference: false, //M1 MacのSafariの場合は1
});
/*以下省略*/
Lesson05
動作確認 プレビュー⽤のタブをクリック
動作確認 再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
変数の宣⾔ 変数宣⾔
画像をスクリプトで扱う準備
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> 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
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(){ 画像, 位置X, 位置Y, 横幅, 縦幅 canvasCtx.drawImage(beam, 0, 0, beam.width, beam.height); } Lesson09
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 height (0,0) width ⼿の検出に合わせて画像が表⽰される
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
⼿の位置・⾓度の算出に関する考え⽅ 関節点の集まり(多数の点)から、⼿の位置・⾓度を計算したい ⎼ OpenCVで図形に近似すると扱いが楽になる。 → 今回は楕円に近似 ⎼ 楕円の中⼼を⼿の位置、傾きを⼿の向き、幅を画⾯上の⼿のサイズとする ⎼ ⼿⾸や親指の付け根は計算には⽤いないこととする OpenCV
⼿の位置・⾓度の計算
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
⼿の位置・⾓度の計算 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
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
画像を表⽰ 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
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 右に傾けると上⼿く⾏かない
傾きが正しい場合と正しくない場合 楕円の傾きはY軸からの⾓度(円の左半分)で計算される 140度 120度 60度
⾓度の補正
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
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 ⼤きさがちょっとショボい
サイズを⼤きくしよう
//楕円の⾓度
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
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認 ⼤きくなったけどズレてる
現状確認 画像beamの輪郭 描画の原点=⼿の位置 サイズを変える前から 実はそもそもズレていた
画像の位置の補正
//楕円の⾓度
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
動作確認 プレビュー⽤のタブを開いたあと、再読み込み
動作確認
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
ハンズオンの⼿順 カメラ画像の表⽰ ⼿の位置・⾓度計算 ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度に追従 親指の状態に連動
親指の状態の計算
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
親指の状態の計算 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
親指の状態を反映
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
完成︕