【Hands/第2回】

Hands

今回は手に画像を表示してみたいと思います。

前回の課題で行った座標の取得が重要になるので、覚えていない人は確認しましょう。

確認

hands.onResults(results => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  if (results.multiHandLandmarks) {
      results.multiHandLandmarks.forEach(marks => {
          // 緑色の線で骨組みを可視化
          drawConnectors(ctx, marks, HAND_CONNECTIONS, { color: '#0f0' });
          // 赤色でランドマークを可視化
          drawLandmarks(ctx, marks, { color: '#f00' });

          console.log(marks[0]);  //手首の座標を取得
          console.log(marks[0].x);//手首のx座標を取得
          console.log(marks[0].y);//手首のy座標を取得
      })
  }
});

marksにはそれぞれのポイントの座標が入っています。marksは配列で例えばmarks[0]は手首の座標が入っています。また、配列の中はオブジェクトになっているので手首のx座標だけ欲しい場合はmarks[0].x、手首のy座標だけ欲しい場合はmarks[0].yとすると座標の数値を取ることができます。

座標を取得し画像を表示

今回は「Hands-2」というフォルダを作成しましょう。ここが作業場になります。flag_red.pngをインストールしてHands-2に入れます。右クリックをして名前を付けて画像を保存してください。名前は変えないでください。その後、Hands-2に「index.html」「script.js」を作成します。

次にそれぞれのファイルのコードを記述していきます。下にあるコードをコピペして動作確認をします。

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <!-- 3つのライブラリを読み込む -->
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>

    <style>
        /*ビデオとキャンバスを重ねるためのスタイル*/
        #input, #output {
            position: absolute;
            top: 0;
            left: 0;
        }
        #input {
            z-index: 1;
            transform: scaleX(-1);
        }
        #output {
            z-index: 2;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- Webカメラの映像(入力) -->
        <video id="input"autoplay"></video>
        <!-- 認識した手の形状を可視化した映像(出力) -->
        <canvas id="output" width="600" height="400"></canvas>
    </div>
    <script src="script.js"></script>
</body>

</html>

script.js

const video = document.getElementById('input');
const canvas = document.getElementById('output');
const ctx = canvas.getContext('2d');
  
const Img = new Image();
Img.src = "flag_red.png";
  
const config = {
    locateFile: file => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
};
  
const hands = new Hands(config);
  
const camera = new Camera(video, {
    onFrame: async () => {
        await hands.send({ image: video });
    },
    width: 600,
    height: 400
});
  
camera.start();
  
hands.setOptions({
    maxNumHands: 5,
    modelComplexity: 1,
    minDetectionConfidence: 0.5,
    minTrackingConfidence: 0.5
});
  
hands.onResults(results => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (results.multiHandLandmarks) {
        results.multiHandLandmarks.forEach(marks => {
            const wrist = marks[0]; // 手首
            const indexFingerMCP = marks[5]; // 人差し指の中手骨
            const pinkyMCP = marks[17]; // 小指の中手骨
            if (wrist && indexFingerMCP && pinkyMCP) {
                imgWrite(wrist, indexFingerMCP, pinkyMCP);    //手の位置に合わせて画像を表示する
            }
        })
    }
});
  
function imgWrite(wrist, indexFingerMCP, pinkyMCP) {
    const wrists = [wrist.x * canvas.width, wrist.y * canvas.height];
    const indexFingerMCPs = [indexFingerMCP.x * canvas.width, indexFingerMCP.y * canvas.height];
    const pinkyMCPs = [pinkyMCP.x * canvas.width, pinkyMCP.y * canvas.height];
    const centerX = (indexFingerMCPs[0] + wrists[0]) / 2;
    const centerY = (indexFingerMCPs[1] + wrists[1]) / 2;
    const magnification = (Math.sqrt((indexFingerMCPs[0] - pinkyMCPs[0]) ** 2 + (indexFingerMCPs[1] - pinkyMCPs[1]) ** 2))*2;
    // キャンバスの描画
    ctx.drawImage(Img, canvas.width-(centerX+magnification/2), centerY-magnification / 2, magnification, magnification); // 画像を中心に描画
}

index.htmlを開いてください。上手くいくと手に画像が表示されます。

コード解説

transform: scaleX(-1);

カメラの映像が反転していて鏡になっていることに気づきましたか。上のコードで反転させています。

const wrist = marks[0]; // 手首
const indexFingerMCP = marks[5]; // 人差し指の中手骨
const pinkyMCP = marks[17]; // 小指の中手

上のコードはmarksから必要な座標をそれぞれ取得しています。

const wrists = [wrist.x * canvas.width, wrist.y * canvas.height];
const indexFingerMCPs = [indexFingerMCP.x * canvas.width, indexFingerMCP.y * canvas.height];
const pinkyMCPs = [pinkyMCP.x * canvas.width, pinkyMCP.y * canvas.height];

imgWriteファンクションでは手に画像を表示するための処理を行っています。上のコードでキャンバスに表示できるようにするために、それぞれの座標にキャンバスのサイズをかけています。canvas.widthはキャンバスの横幅です。canvas.heightはキャンバスの縦幅です。その後、配列に入れて管理します。

const centerX = (indexFingerMCPs[0] + wrists[0]) / 2;
const centerY = (indexFingerMCPs[1] + wrists[1]) / 2;

上のコードは手の真ん中の座標を計算しています。今回は人差し指の中手骨と手首を使っています。

const magnification = (Math.sqrt((indexFingerMCPs[0] - pinkyMCPs[0]) ** 2 + (indexFingerMCPs[1] - pinkyMCPs[1]) ** 2))*2;

上のコードは手のサイズを計算しています。手のサイズによって画像のサイズが変わるようにしています。

ctx.drawImage(Img, canvas.width-(centerX+magnification/2), centerY-magnification / 2, magnification, magnification);

drawImageでキャンバスに画像を表示します。drawImageのかっこの中は(画像名, x座標の始点, y座標の始点, 画像の横幅, 画像の縦幅)となっています。映像が反転しているのでキャンバスの横幅から値を引いて表示させています。

課題

今回の課題は画像を切り替えてみましょう。

  • 以下の画像をインストールし、手のサイズによって画像を手に表示しなさい。
    magnificationが150以下の場合「ちっさ。」
    magnificationが150より大きい場合「デカ過ぎんだろ・・・」

ちっさはsmall.pngという名前でインストールしてください。デカ過ぎんだろ・・・はbig.pngという名前でインストールしてください。

jsにはsmall.pngはsmallImgという定数に入れてください。big.pngはbigImgという定数に入れてください。

small.png
big.png
  1. 画像はちゃんと読み込めていますか。
  2. const 定数名 = new Image();
  3. 定数名.src = “画像”;

画像の宣言は const 定数名 = new Image(); と 定数名.src = “画像”; で行うことができます。

画像の出力はdrawImageでできます。条件によってどの画像を表示するかを変える必要があります

const smallImg = new Image();
smallImg.src = "small.jpg";
const bigImg = new Image();
bigImg.src = "big.png";

if(magnification<=150){
  ctx.drawImage(smallImg, canvas.width-(centerX+magnification/2), centerY-magnification / 2, magnification, magnification); // 画像を中心に描画
}else{
  ctx.drawImage(bigImg, canvas.width-(centerX+magnification/2), centerY-magnification / 2, magnification, magnification);
}