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

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

TakashiYoshinaga
March 05, 2022
110

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

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

TakashiYoshinaga

March 05, 2022
Tweet

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. #AR_Fukuoka
    ハッシュタグ

    View Slide

  7. 本題に⼊ります

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. HTMLの記述の解説






    input_video
    beam
    input_video
    output_canvas
    input_video

    View Slide

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

    View Slide

  19. ライブラリ読み込みの解説






    OpenCV
    Camera Utils Hands + drawing utils

    View Slide

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

    View Slide

  21. javascript記述の解説

    View Slide

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

    View Slide

  23. 変数の宣⾔
    変数宣⾔

    View Slide

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

    View Slide

  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) {/*⼿の検出結果を利⽤する*/ }

    View Slide

  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

    View Slide

  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) {/*⼿の検出結果を利⽤する*/ }
    詳細は後ほど実装

    View Slide

  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の映像を
    ハンドトラッキング処理に渡す
    画像サイズを設定

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  33. 動作確認
    PREVIEW

    View Slide

  34. 動作確認
    Preview in a New Window

    View Slide

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

    View Slide

  36. 動作確認

    View Slide

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

    View Slide

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

    View Slide

  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();

    View Slide

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

    View Slide

  41. 動作確認
    再読み込み

    View Slide

  42. 動作確認

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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に変更

    View Slide

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

    View Slide

  48. 動作確認
    再読み込み

    View Slide

  49. 動作確認

    View Slide

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

    View Slide

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

    View Slide

  52. 変数の宣⾔
    変数宣⾔

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  59. 画像のURLを取得
    Copy URL

    View Slide

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

    View Slide

  61. 画像をページ内に追加





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

    View Slide

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

    View Slide

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

    View Slide

  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, 横幅, 縦幅

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  76. 動作確認

    View Slide

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

    View Slide

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

    View Slide

  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);
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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);
    }

    View Slide

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

    View Slide

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

    View Slide

  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);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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⽅向に半分ずらす

    View Slide

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

    View Slide

  92. 動作確認

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  99. 完成︕

    View Slide