8_3_2 姿勢検出(複数)

複数の姿勢検出には、posenetのestimateMultiplePoses()を使用します。判定する数の最大数はデフォルトで5なので、5を超える数の姿勢検出を行いたいときには、maxPoseDetectionsパラメータを指定します。

// PoseNetを読み込み、イメージの姿勢検出を実行
posenet.load().then((net) => {
    const imageScaleFactor = 0.5;
    const flipHorizontal = false;
    const outputStride = 16;
    const maxPoseDetections = 9;
    return net.estimateMultiplePoses(imageData, imageScaleFactor, flipHorizontal, outputStride, maxPoseDetections)
}).then((pose) => {
    // 各部位を描画する
    // poseはmaxPoseDetections分のオブジェクトを持つ配列
    drawParts(context, pose);
});

また、estimateMultiplePoses()から返ってくる変数poseは配列です。この配列には、1つ分の姿勢検出の結果のオブジェクトが入っています。

以下は全コードです。

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

<head>
  <meta charset="utf-8">
  <title>Pose Detection (PoseNet)</title>
  <script src="https://unpkg.com/@tensorflow/tfjs"></script>
  <script src="https://unpkg.com/@tensorflow-models/posenet"></script>

  <style>
    /* ページに読み込む画像用<img>要素は表示しない */
    
    img {
      display: none;
    }
  </style>
</head>

<body>
  <h2>姿勢検出 (複数)</h2>
  <input type="file" id="input-file">
  <img src="" alt="Image preview...">
  <div id="image-container"></div>
  <script>
    document.addEventListener('DOMContentLoaded', async() => {
      const inputFile = document.getElementById('input-file');
      // ページには表示されない<img>要素
      const preview = document.querySelector('img');
      // [ファイルを選択]ボタンで画像を選択したら
      inputFile.addEventListener('change', (e) => {
        // 取得したファイル
        const imageFile = e.target.files[0];
        // ファイルの読み取りに成功したら、readPromise.then()を実行する
        new Promise((resolve, reject) => {
          const reader = new FileReader();
          // ファイルの読み取りに成功したら解決する
          reader.onload = (e) => {
              resolve(e.target.result);
            }
            // ファイルを画像として読み取る
          reader.readAsDataURL(imageFile);
          // 成功したら、<img>要素に画像を割り当てる
        }).then((img) => {
          // 画像が読み込まれたら
          preview.onload = () => {
            // 読み込む画像の幅と高さ用変数
            const imageWidth = preview.naturalWidth;
            const imageHeight = preview.naturalHeight;
            // <canvas>を作成して、それに画像のイメージデータを割り当てる
            const canvas = document.createElement('canvas');
            canvas.width = imageWidth;
            canvas.height = imageHeight;
            const context = canvas.getContext('2d');

            // キャンバスに画像を描画する
            context.drawImage(preview, 0, 0);
            // イメージデータを取得
            const imageData = context.getImageData(0, 0, imageWidth, imageHeight);
            // キャンバスをページに表示 = 写真が見えるようになる。
            document.getElementById('image-container').appendChild(canvas);

            // PoseNetを読み込み、イメージの姿勢検出を実行
            posenet.load().then((net) => {
              const imageScaleFactor = 0.5;
              const flipHorizontal = false;
              const outputStride = 16;
              const maxPoseDetections = 9;
              return net.estimateMultiplePoses(imageData, imageScaleFactor, flipHorizontal, outputStride, maxPoseDetections)
            }).then((pose) => {
              // 各部位を描画する
              // poseはmaxPoseDetections分のオブジェクトを持つ配列
              drawParts(context, pose);
            });
          }
          preview.src = img;
        });
      }, false);

      /* pose.keypoints[ {オブジェクト}. {オブジェクト}....]
      {
        position.x    // x位置
        position.y    // y位置
        part          // 部位名
      }
      */
      // 頭部と上半身、下半身の部位を色別に丸で描き、左右別に線で結ぶ
      const drawParts = (ctx, pose) => {
        // console.log(pose)

        // 円の半径
        const radius = 3;

        for (let i = 0; i < pose.length; i++) {
          const points = pose[i].keypoints;
          // pose.keypoints配列にあるオブジェクトの情報を元に部位を円で描く
          for (let j = 0; j < points.length; j++) {
            // x位置
            const xpos = points[j].position.x;
            // y位置
            const ypos = points[j].position.y;
            // 部位の名前
            const part = points[j].part;
            // 頭部と上半身、下半身で色を分ける
            if (part === 'nose' || part === 'leftEye' || part === 'rightEye' || part === 'leftEar' || part === 'rightEar') {
              ctx.fillStyle = "lightgreen";
            }
            else if (part === 'leftShoulder' || part === 'rightShoulder' || part === 'leftElbow' || part === 'rightElbow' || part === 'leftWrist' || part === 'rightWrist') {
              ctx.fillStyle = "red";
            }
            else {
              ctx.fillStyle = "blue";
            }
            // 円で塗る
            ctx.beginPath();
            ctx.arc(xpos, ypos, radius, 0, Math.PI * 2);
            ctx.fill();
          }

          // 上半身、左右別に部位を線で結ぶ
          ctx.beginPath();
          ctx.strokeStyle = 'red';
          ctx.lineWidth = 4;
          // leftSholder5 - leftElbow7 - leftWrist9
          ctx.moveTo(points[5].position.x, points[5].position.y);
          ctx.lineTo(points[7].position.x, points[7].position.y);
          ctx.lineTo(points[9].position.x, points[9].position.y);
          // rightSholder6 - rightElbow8 - rightWrist10
          ctx.moveTo(points[6].position.x, points[6].position.y);
          ctx.lineTo(points[8].position.x, points[8].position.y);
          ctx.lineTo(points[10].position.x, points[10].position.y);
          // 線を描く
          ctx.stroke();

          // 下半身、左右別に部位を線で結ぶ
          ctx.beginPath();
          ctx.strokeStyle = 'blue';
          // leftHip11 - leftKnee13 - leftAnkle15
          ctx.moveTo(points[11].position.x, points[11].position.y);
          ctx.lineTo(points[13].position.x, points[13].position.y);
          ctx.lineTo(points[15].position.x, points[15].position.y);
          // rightHip12 - rightKnee14 - rightAnkle16
          ctx.moveTo(points[12].position.x, points[12].position.y);
          ctx.lineTo(points[14].position.x, points[14].position.y);
          ctx.lineTo(points[16].position.x, points[16].position.y);
          ctx.stroke();

        }
      }
    }, false);
  </script>
</body>
</html>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA