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

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

TakashiYoshinaga
March 05, 2022
180

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

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

TakashiYoshinaga

March 05, 2022
Tweet

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. #AR_Fukuoka
    ハッシュタグ

    View full-size slide

  7. 本題に⼊ります

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. HTMLの記述の解説






    input_video
    beam
    input_video
    output_canvas
    input_video

    View full-size slide

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

    View full-size slide

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






    OpenCV
    Camera Utils Hands + drawing utils

    View full-size slide

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

    View full-size slide

  21. javascript記述の解説

    View full-size slide

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

    View full-size slide

  23. 変数の宣⾔
    変数宣⾔

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

  33. 動作確認
    PREVIEW

    View full-size slide

  34. 動作確認
    Preview in a New Window

    View full-size slide

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

    View full-size slide

  36. 動作確認

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

  41. 動作確認
    再読み込み

    View full-size slide

  42. 動作確認

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

  48. 動作確認
    再読み込み

    View full-size slide

  49. 動作確認

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. 変数の宣⾔
    変数宣⾔

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  59. 画像のURLを取得
    Copy URL

    View full-size slide

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

    View full-size slide

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





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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

  76. 動作確認

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

  92. 動作確認

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide