概要
顔検出を使用して口の中心と開き具合を検出する
参考
サンプルコード
内容
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
に設定したstream
をcanvas
に描画
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];