Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MediaPipeのハンドトラッキングで作るARライトセイバー

357c9e58ecce2865f9eb748192e5143f?s=47 TakashiYoshinaga
March 05, 2022
52

 MediaPipeのハンドトラッキングで作るARライトセイバー

2022/03/05のXRワークショップでの資料

357c9e58ecce2865f9eb748192e5143f?s=128

TakashiYoshinaga

March 05, 2022
Tweet

Transcript

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

  2. もろもろのダウンロード http://arfukuoka.lolipop.jp/ lightsaber_web/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をクリックし、コードが表⽰されることを確認 テンプレートの確認 エディタ

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

  14. テンプレートの確認 Lesson01

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

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

  17. 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
  18. テンプレートの確認 ライブラリの 読み込み

  19. ライブラリ読み込みの解説 <!--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
  20. テンプレートの確認 MediaPipeや OpenCVでの 処理を記述 (今⽇のメイン)

  21. javascript記述の解説

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

  23. 変数の宣⾔ 変数宣⾔

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

  25. 初期化に関する記述 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) {/*⼿の検出結果を利⽤する*/ }
  26. 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
  27. ハンドトラッキングの初期化 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) {/*⼿の検出結果を利⽤する*/ } 詳細は後ほど実装
  28. カメラの初期化と起動 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の映像を ハンドトラッキング処理に渡す 画像サイズを設定
  29. ハンズオンの⼿順 カメラ画像の表⽰ ⼿の認識結果表⽰ ライトセイバーの表⽰ ⼿の位置・⾓度計算 ⼿の位置・⾓度に追従 親指の状態に連動

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

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

  32. カメラ画像の表⽰ //⼿の検出結果を利⽤する 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
  33. 動作確認 PREVIEW

  34. 動作確認 Preview in a New Window

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

  36. 動作確認

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

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

  39. ハンドトラッキング結果の表⽰ 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();
  40. 動作確認 プレビュー⽤のタブをクリック

  41. 動作確認 再読み込み

  42. 動作確認

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

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

  45. 画像の反転 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
  46. 認識する⼿の数の上限を変更 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に変更
  47. 動作確認 プレビュー⽤のタブをクリック

  48. 動作確認 再読み込み

  49. 動作確認

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

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

  52. 変数の宣⾔ 変数宣⾔

  53. 画像をスクリプトで扱う準備 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
  54. 画像の追加しよう 描画領域等 <img id="beam" src="画像のURL" style="position:absolute; display:none;">

  55. 画像のアップロード Assets

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

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

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

  59. 画像のURLを取得 Copy URL

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

  61. 画像をページ内に追加 <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
  62. Canvas上でライトセイバーを表⽰ 認識結果の利⽤ rcvResults

  63. ⼿の検出と連動した画像表⽰ function recvResults(results) { /*canvasのサイズ指定と画像の描画(スペースの都合により省略)*/ if (results.multiHandLandmarks) { //⾒つけた⼿の数だけ処理を繰り返す for

    (const landmarks of results.multiHandLandmarks) { //⾻格を描画 drawConnectors(/*省略*/); //関節を描画 drawLandmarks(/*省略*/); drawLightSaber(); } } canvasCtx.restore(); } //ライトセイバーを表⽰ function drawLightSaber(){ } Lesson08
  64. ⼿の検出と連動した画像表⽰ 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, 横幅, 縦幅
  65. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

  66. 動作確認 (0,0) width height

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

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

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

    OpenCV
  70. ⼿の位置・⾓度の計算 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
  71. ⼿の位置・⾓度の計算 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
  72. ⼿の関節に対応するインデックス ここは無視

  73. 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
  74. 楕円の表⽰ 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
  75. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

  76. 動作確認

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

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

  79. 画像を表⽰ 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); }
  80. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

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

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

  83. ⾓度の補正 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); }
  84. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

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

  86. サイズを⼤きくしよう 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);
  87. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

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

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

  90. 画像の位置の補正 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⽅向に半分ずらす
  91. 動作確認 プレビュー⽤のタブを開いたあと、再読み込み

  92. 動作確認

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

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

  95. 親指の状態の計算 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
  96. 親指の状態の計算 4 7 19 l Distance1: 親指(4)から人差し指(7)までの距離 l Distance2: 人差し指(7)から小指(19)までの距離

    l 親指の状態(ratio): Distance1 / Distance2 ※ratioの大小で親指の状態を評価
  97. 親指の状態計算 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
  98. 親指の状態を反映 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(); /*スペースの都合により省略*/ } 親指の⽴ち具合をかける
  99. 完成︕