👌

MediaPipe Handpose Javascript版

2023/05/20に公開

概要

  • Googleの機械学習を用いたライブラリMediaPipeが2023年更新されました
  • 以前のサンプルは廃止されるようなので、新しいライブラリの使い方について解説します
  • 今回は手を認識できるHandposeのJavascript版を扱います
  • 顔認識(Face landmark detection)や全身トラッキング(Pose landmark detection)も今回のサンプルの一部を書き換えると使うことができます

公式サイト
https://developers.google.com/mediapipe/solutions/guide

今回のコードはこちらのgithubにアップロードしてあります
またLive Demoで体験することもできます
https://github.com/tkada/mediapipe-handpose-example

学習済みのモデルデータを入手する

MediaPipeでは学習済みの.taskファイルを用意する必要があります
Handposeの場合は下記の場所からダウンロードすることができます
https://developers.google.com/mediapipe/solutions/vision/hand_landmarker#models

ダウンロードしたファイルはアクセスできる場所にアップロードしておきます

HTMLとJavascriptを用意する

HTML側では必要なライブラリ読み込みを行います
また、Webカメラを使うためのvideoタグと結果を出力するためのcanvasを用意しておきます

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <head>
      <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>
      <script type="modele" src="https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.js"crossorigin="anonymous"></script>
    </head>

    <title>Mediapipe Handpose Example</title>

    <!-- The website JavaScript file -->
    <script type="module" src="./main.js" defer></script>
  </head>
  <body>
    <video id="input_video" style="display:none"></video>
    <canvas id="output_canvas" width="1280px" height="720px"></canvas>
  </body>
</html>

Javascript側ではWebカメラの起動し、Webカメラの映像をMediapipeに流し込みます

main.js
import {
  HandLandmarker,
  FilesetResolver
} from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0";

import Stats  from 'https://cdnjs.cloudflare.com/ajax/libs/stats.js/r17/Stats.min.js';

const init = async () =>{
  const stats = new Stats();
    document.body.appendChild(stats.dom);
  
  const video = document.getElementById("input_video");
  const canvasElement = document.getElementById("output_canvas"); 
  const canvasCtx = canvasElement.getContext("2d");

  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    navigator.mediaDevices
      .getUserMedia({ video: true })
      .then(function (stream) {
        video.srcObject = stream;
        video.play();
      })
      .catch(function (error) {
        console.error("Error accessing the camera: ", error);
      });
  } else {
    alert("Sorry, your browser does not support the camera API.");
  }
  
  const vision = await FilesetResolver.forVisionTasks(
    // path/to/wasm/root
    "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
  );

  const handLandmarker = await HandLandmarker.createFromOptions(
      vision,
      {
        baseOptions: {
          modelAssetPath: "./hand_landmarker.task", //.taskファイルを指定する
          delegate: "GPU" //CPU or GPUで処理するかを指定する
        },
        numHands: 2 //認識できる手の数
      });
  
  await handLandmarker.setOptions({ runningMode: "video" });

  let lastVideoTime = -1;
  
  
  const renderLoop = () => {
    canvasElement.width = video.videoWidth;
    canvasElement.height = video.videoHeight;
    let startTimeMs = performance.now();
    if (video.currentTime > 0 && video.currentTime !== lastVideoTime) {
      const results = handLandmarker.detectForVideo(video,startTimeMs);
      lastVideoTime = video.currentTime;
      
      canvasCtx.save();
      canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
      canvasCtx.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
      if (results.landmarks) {
        for (const landmarks of results.landmarks) {
          drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
            color: "#00FF00",
            lineWidth: 5
          });
          drawLandmarks(canvasCtx, landmarks, { color: "#FF0000", lineWidth: 2 });
        }
      }
      canvasCtx.restore();
    }

    requestAnimationFrame(() => {
      stats.begin();
      renderLoop();
      stats.end();
    });
  }
  
  renderLoop();
  
}


init();

renderLoop関数で毎フレームWebカメラの画像をMediapipeに流し込み結果を取得します
その結果をCanvasに書き込んでいます

const results = handLandmarker.detectForVideo(video,startTimeMs);
のresultをログ出力してみると手の各ランドマークの座標が出力されます
landmarksに各ランドマークの座標が入っています
配列の何番目がどの部位に当たるかは画像を参照してください

{
    "landmarks": [
        [
            {
                "x": 0.7927062511444092,
                "y": 0.7218624353408813,
                "z": 3.318519077311066e-7
            },
            ...
            {
                "x": 0.8173002004623413,
                "y": 0.34138065576553345,
                "z": -0.04457491636276245
            }
        ],
        [
            {
                "x": 0.21455314755439758,
                "y": 0.6943681240081787,
                "z": 4.5197063514024194e-7
            },
            ...
            {
                "x": 0.15950265526771545,
                "y": 0.3301934003829956,
                "z": -0.07225233316421509
            }
        ]
    ],
    "worldLandmarks": [
        [
            {
                "x": 0.0241236574947834,
                "y": 0.09063709527254105,
                "z": -0.0027828216552734375
            },
            ...
            {
                "x": 0.02904096245765686,
                "y": -0.053392037749290466,
                "z": -0.042449951171875
            }
        ],
        [
            {
                "x": -0.02501724101603031,
                "y": 0.08835680037736893,
                "z": 0.0098114013671875
            },
            ...
            {
                "x": -0.04410901665687561,
                "y": -0.05151066184043884,
                "z": -0.034423828125
            }
        ]
    ],
    "handednesses": [
        [
            {
                "score": 0.9588623046875,
                "index": 1,
                "categoryName": "Right",
                "displayName": "Right"
            }
        ],
        [
            {
                "score": 0.98583984375,
                "index": 0,
                "categoryName": "Left",
                "displayName": "Left"
            }
        ]
    ]
}

サイトにデプロイする

Webカメラを使うのでhttpsが使えるサイトにデプロイする必要があります。
Glitchnetlifyが便利です

トラブルシューティング

映像がちらつく場合はdelegate: "GPU"の部分をdelegate: "CPU"に書き換えてみてください
実行速度は遅くなりますが、安定するようになります

Discussion