見出し画像

MediaPipe 入門 (6) - 指ポーズの検出

MediaPipeのHandsを使って指ポーズの検出を行ってみます。

前回

1. webpackプロジェクトの準備

はじめに、webpackプロジェクトを準備します。

2. 指ポーズの検出

(1) パッケージのインポート。

$ npm i -S three @mediapipe/hands
$ npm i -S three @mediapipe/camera_utils
$ npm i -S three @mediapipe/control_utils
$ npm i -S three @mediapipe/drawing_utils

(2) 「dist/index.html」を以下のように作成。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <div class="container">
      <video class="input_video" width="0px" height="0px"></video>
      <canvas class="output_canvas" width="640px" height="360px"></canvas>
    </div>
    <div style="font-size:320%;color:#FF0000;width:640px;" class="label"></div>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

以下のDOM要素を配置してます。

・input_video : 入力動画の表示。
・output_canvas : 検出結果の表示。
・label : 検出したポーズ名の表示。
・main.js : スクリプト。

(3) 「src/index.html」を以下のように作成。
指ごとの2D角度の合計で、指ポーズを判定しています。

import {drawConnectors, drawLandmarks} from '@mediapipe/drawing_utils/drawing_utils'
import {Camera} from '@mediapipe/camera_utils/camera_utils'
import {Hands, HAND_CONNECTIONS} from '@mediapipe/hands/hands'

// DOM要素
const videoElement = document.getElementsByClassName('input_video')[0]
const canvasElement = document.getElementsByClassName('output_canvas')[0]
const canvasCtx = canvasElement.getContext('2d')
const label = document.getElementsByClassName('label')[0]

// 2頂点の距離の計算
function calcDistance(p0, p1) {
  let a1 = p1.x-p0.x
  let a2 = p1.y-p0.y
  return Math.sqrt(a1*a1 + a2*a2)
}

// 3頂点の角度の計算
function calcAngle(p0, p1, p2) {
  let a1 = p1.x-p0.x
  let a2 = p1.y-p0.y
  let b1 = p2.x-p1.x
  let b2 = p2.y-p1.y
  let angle = Math.acos( (a1*b1 + a2*b2) / Math.sqrt((a1*a1 + a2*a2)*(b1*b1 + b2*b2)) ) * 180/Math.PI
  return angle
}

// 指の角度の合計の計算
function cancFingerAngle(p0, p1, p2, p3, p4) {
  let result = 0
  result += calcAngle(p0, p1, p2)
  result += calcAngle(p1, p2, p3)
  result += calcAngle(p2, p3, p4)
  return result
}

// 指ポーズの検出
function detectFingerPose(landmarks) {
  // 指のオープン・クローズ
  let thumbIsOpen = cancFingerAngle(landmarks[0], landmarks[1], landmarks[2], landmarks[3], landmarks[4]) < 70
  let firstFingerIsOpen = cancFingerAngle(landmarks[0], landmarks[5], landmarks[6], landmarks[7], landmarks[8]) < 100
  let secondFingerIsOpen = cancFingerAngle(landmarks[0], landmarks[9], landmarks[10], landmarks[11], landmarks[12]) < 100
  let thirdFingerIsOpen = cancFingerAngle(landmarks[0], landmarks[13], landmarks[14], landmarks[15], landmarks[16]) < 100
  let fourthFingerIsOpen = cancFingerAngle(landmarks[0], landmarks[17], landmarks[18], landmarks[19], landmarks[20]) < 100

  // ジェスチャー
  if (calcDistance(landmarks[4], landmarks[8]) < 0.1 && secondFingerIsOpen && thirdFingerIsOpen && fourthFingerIsOpen) {
    return "OK"
  } else if (calcDistance(landmarks[4], landmarks[12]) < 0.1 && calcDistance(landmarks[4], landmarks[16]) < 0.1 && firstFingerIsOpen && fourthFingerIsOpen) {
    return "キツネ"
  } else if (thumbIsOpen && !firstFingerIsOpen && !secondFingerIsOpen && !thirdFingerIsOpen && !fourthFingerIsOpen) {
    return "いいね"
  } else if (thumbIsOpen && firstFingerIsOpen && secondFingerIsOpen && thirdFingerIsOpen && fourthFingerIsOpen) {
    return "5"
  } else if (!thumbIsOpen && firstFingerIsOpen && secondFingerIsOpen && thirdFingerIsOpen && fourthFingerIsOpen) {
    return "4"
  } else if (!thumbIsOpen && firstFingerIsOpen && secondFingerIsOpen && thirdFingerIsOpen && !fourthFingerIsOpen) {
    return "3"
  } else if (!thumbIsOpen && firstFingerIsOpen && secondFingerIsOpen && !thirdFingerIsOpen && !fourthFingerIsOpen) {
    return "2"
  } else if (!thumbIsOpen && firstFingerIsOpen && !secondFingerIsOpen && !thirdFingerIsOpen && !fourthFingerIsOpen) {
    return "1"
  }
  return "0"
}

// 結果取得時の処理
function onResults(results) {
 canvasCtx.save()
 canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height)
 canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height)
 if (results.multiHandLandmarks) {
   for (const landmarks of results.multiHandLandmarks) {
     // 指ポーズの検出
     label.innerHTML = "<center>"+detectFingerPose(landmarks)+"</center>"

     drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS,
       {color: '#00FF00', lineWidth: 5})
     drawLandmarks(canvasCtx, landmarks, {color: '#FF0000', lineWidth: 2})
   }
 }
 canvasCtx.restore()
}

// Handsオブジェクトの準備
const hands = new Hands({locateFile: (file) => {
 return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
}}) 
hands.setOptions({
 maxNumHands: 2,
 minDetectionConfidence: 0.7,
 minTrackingConfidence: 0.5
})
hands.onResults(onResults)

// カメラの準備
const camera = new Camera(videoElement, {
 onFrame: async () => {
   await hands.send({image: videoElement})
 },
 width: 640,
 height: 360
})
camera.start()

(4) ビルドと実行。

$ npm run build
$ npm run start

画像1

3. 参考

【おまけ】 カメラのフレームレートの変更

デフォルトで30FPS。「node_modules/@mediapipe/camera_utils/camera_utils.js」のgetUserMediaのパラメータにフレームレート(frameRate)を追加することで変更することは可能。

navigator.mediaDevices.getUserMedia({video:{facingMode:b.facingMode,width:b.width,height:b.height}}

navigator.mediaDevices.getUserMedia({video:{facingMode:b.facingMode,width:b.width,height:b.height,frameRate:{ideal:10}}}





この記事が気に入ったらサポートをしてみませんか?