3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

face-landmarks-detectionを使って口の中心と開き具合を検出

Posted at

概要

顔検出を使用して口の中心と開き具合を検出する

参考

サンプルコード

内容

videoタグ

canvasに転写するためのvideoタグを作成する

video = document.createElement('video');
video.id = 'video';
video.textContent = 'Video stream not available.';
video.setAttribute('playsinline', '');
video.setAttribute('muted', '');
video.display = 'none';

videoタグのsrcObjectにカメラの映像を設定する

  • localhostであればhttpsじゃなくても大丈夫
  • スマホから試したいときはローカル認証局などを使って回避
  • facingMode: 'user'でスマホの場合はインカメラを使用するようにする
  • 今回はaudioを使わないのでfalseに設定する
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// カメラとvideoタグの紐づけ
navigator.mediaDevices
  .getUserMedia({ video: { facingMode: 'user'}, audio: false })
  .then(function(stream) {
    video.srcObject = stream;
  })
  .catch((err) => {
    console.error(`An error occurred: ${err}`);
    alert('カメラの使用を許可してください。');
  });
} else {
    alert('ブラウザが対応していません。');
}

videoタグのsrcObjectに設定したstreamcanvasに描画

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

this.ctx.drawImage(
  this.video, 
  0, 0, this.video.videoWidth, this.video.videoHeight,
  0, 0, this.canvas.width, this.canvas.height
);

videoとcanvasの画像サイズに差異があるため記憶する

scaleX = canvas.width / video.videoWidth;
scaleY = canvas.height / video.videoHeight;

face-landmarks-detection

必要なライブラリをCDNから取得する場合は以下

    <!-- Require the peer dependencies of face-landmarks-detection. -->
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>

    <!-- You must explicitly require a TF.js backend if you're not using the TF.js union bundle. -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgl"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection"></script>

検出器の作成

// 検出オプション
estimationConfig = { flipHorizontal: false };

const model = faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;
// tfjs
// const detectorConfig = {
//   runtime: 'tfjs',
//   refineLandmarks: true,
// };

// mediapipe
const detectorConfig = {
  runtime: 'mediapipe',
  // CDN
  // solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh',
  // LOCAL
  solutionPath: 'node_modules/@mediapipe/face_mesh',
  // maxFaces: 1 // default value 1
}
const detector = await faceLandmarksDetection.createDetector(model, detectorConfig);

検出

video.readyStateについて
video.readyState<2のときは顔検出を実行しないで待つ

// videoの準備ができているか確認
if (video.readyState < 2) {
  await new Promise((resolve) => {
    video.onloadeddata = () => {
      resolve(video);
    };
  });
}

// 顔検出
faces = await detector.estimateFaces(video, estimationConfig);

口だけ検出

estimateFaces()の結果から口だけ使用する
createDetector()で指定したmaxFacesプロパティによって検出する顔の数(faces)が変わる

const face = faces[0];

// keypointsはカメラ画像のものであるためvideoとcanvasのスケール差を合わせる
const keypoints = face.keypoints.map((keypoint) => [keypoint.x*scaleX, keypoint.y*scaleY]);
const contours = faceLandmarksDetection.util
  .getKeypointIndexByContour(faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh);

// ラベルごとにループする
for (const [label, contour] of Object.entries(contours)) {
  ctx.strokeStyle = '#E0E0E0';
  ctx.lineWidth = 1;

  const path = contour.map((index) => keypoints[index]);
  if (path.every(value => value != undefined)) {
    // lips以外にも取得できる要素はあるが、今回はlips(口)のみ使用        
    switch(label) {
      case 'lips':
        return this._getMouthCenter(path);
        break;
      default:
        break;
    }
  }
}

口の中心位置と開き具合

口のpathは以下のようにkeypointがあるため、口の中心位置と開き具合を検出するために必要な以下のkeypointのみ使用した。

  • 内側下唇の真ん中(path[25])
  • 内側上唇の真ん中(path[35])
場所 index
外側下唇 左から 0 ~ 9
外側上唇 左から 10 ~ 19
内側下唇 左から 20 ~ 29
内側上唇 左から 30 ~ 39
// 内側下唇の中心
const innerLowerLip = path[25]
// 内側上唇の中心
const innerUpperLip = path[35]

// 口の開き具合:唇中心位置のy座標の差分
const openMouthSize = innerLowerLip[1] - innerUpperLip[1];
// 口の中心位置のy座標
const y = innerLowerLip[1] - openMouthSize / 2;

return [innerLowerLip[0], y, openMouthSize < MOUTH_MIN_SIZE ? MOUTH_MIN_SIZE : openMouthSize];
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?