Shape Detection API で 顔を読み取る

Shape Detection API を用いて、前回は 1 次元 2 次元バーコードを読み取りしました。
今回は、顔の読み取りをしてみます。

最終的に、こんなことができました。

目次

参考

用意

今回も、Experimental Web Platform featureを有効化する必要があります。
WebNFC を試そう を参照して設定ください。

今回動作を確認していたところ、Android 版 Chrome では動作しませんでした。
Windows 版 Chrome を使用します。

実装 1(画像の中の顔を読み取る)

まずは、画像の中の顔を読み取りしてみます。

test1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
crossorigin="anonymous"
></script>
</head>
<body>
<img src="face.png" id="source" />
<div id="result"></div>

<script type="text/javascript" src="app1.js"></script>
</body>
</html>
app1.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const faceDetector = new FaceDetector();

const image = document.getElementById("source");

let faces = null;

window.onload = async () => {
try {
faces = await faceDetector.detect(image);
} catch (e) {
$("#result").text("ERROR");
}

if (faces == null) {
return;
}

let resultText = "";
for (const face of faces) {
resultText += `
<ul>
<li>width = ${face.boundingBox.width}</li>
<li>height = ${face.boundingBox.height}</li>
<li>x = ${face.boundingBox.x}</li>
<li>y = ${face.boundingBox.y}</li>
</ul>
`;
}
$("#result").html(resultText);
};

detect()のレスポンスには、顔の座標だけではなくlandmarksという配列で顔のパーツを返す要素があるのですが取得できていませんでした。
今回は表示項目からも省略しています。

確認 1

動かしてみた様子は、以下のようになります。

顔写真の素材は、photoAC - AI 人物素材(ベータ版)を使用しました。
最初は、レナを使おうかと考えたんですが、どうも利用にあたっての問題点がありそうなので取りやめています。

実装 2 カメラから読み込む

バーコードの読み取りの時と同様に、カメラから取得した画像顔の位置を取得し、枠を描いてみます。

test2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs="
crossorigin="anonymous"
></script>
</head>
<body>
<div id="canvas_area">
<canvas id="result" width="300" height="300"></canvas>
</div>
<div id="result_text"></div>
<script type="text/javascript" src="app2.js"></script>
</body>
</html>
app2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
const faceDetector = new FaceDetector();

// streamを入力するvideoを作成する
const image = document.createElement("video");

// 画像を加工するcanvasを作成する
const offscreen_canvas = document.createElement("canvas");
const offscreen_context = offscreen_canvas.getContext("2d");

// 最終的に取得した画像を表示するcanvasを取得する
const canvas = document.querySelector("#result");
const context = canvas.getContext("2d");

//カメラと中間処理のキャンバスのサイズを最終的に表示するキャンバスを基準に設定
offscreen_canvas.width = canvas.width;
image.videoWidth = canvas.width;
offscreen_canvas.height = canvas.height;
image.videoHeight = canvas.height;

//取得結果は使いまわすので、外で定義する
let faces = null;

window.onload = async () => {
//カメラを取得
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
//{
// facingMode: { exact: "face" },
//},
});

//オブジェクトと関連付ける
image.srcObject = stream;
image.play();

//バーコードの解析処理自体の実行
analysis();

//解析結果をdomに書き出す処理の定期呼び出しを設定
setInterval(() => {
reflesh();
}, 800);
};

const analysis = async () => {
offscreen_context.drawImage(image, 0, 0);

try {
faces = await faceDetector.detect(image);
} catch {
window.requestAnimationFrame(analysis);
return;
}

let state = true;

if (faces == null) {
state = false;
}
if (state == true && faces.length == 0) {
state = false;
}

console.log(faces);

//バーコードの値が取れていた場合、赤い線で囲む
if (state) {
offscreen_context.strokeStyle = "rgb(255, 0, 0) ";
offscreen_context.lineWidth = 10;

//特定できた顔の数だけ赤く囲む
faces.forEach((face) => {
offscreen_context.beginPath(face.boundingBox.x, face.boundingBox.y);
offscreen_context.lineTo(
face.boundingBox.x + face.boundingBox.width,
face.boundingBox.y
);
offscreen_context.lineTo(
face.boundingBox.x + face.boundingBox.width,
face.boundingBox.y + face.boundingBox.height
);
offscreen_context.lineTo(
face.boundingBox.x,
face.boundingBox.y + face.boundingBox.height
);
offscreen_context.lineTo(face.boundingBox.x, face.boundingBox.y);
offscreen_context.closePath();
offscreen_context.stroke();
});
}
context.drawImage(offscreen_canvas, 0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(analysis);
};

//結果を文字で書き出す
const reflesh = () => {
$("#result_text").empty();
if (faces == null) {
$("#result_text").text("ERROR");
return;
}
if (faces.length == 0) {
$("#result_text").text("ERROR");
return;
}
let resultText = "";
for (const face of faces) {
resultText += `
<ul>
<li>width = ${face.boundingBox.width}</li>
<li>height = ${face.boundingBox.height}</li>
<li>x = ${face.boundingBox.x}</li>
<li>y = ${face.boundingBox.y}</li>
</ul>
`;
}

$("#result_text").html(resultText);
};

確認 2

photoAC - AI 人物素材(ベータ版)のサイトをカメラで撮影してみまた。

複数人の顔を取得し、枠を描くことができました。
気が付いたこととして、サイズが違いすぎる(遠近の差があるなど)と小さいほうが取得されにくいようです。


今回は、Shape Detection API で顔の座標取得を試みました。
実験的な API だけあり、Android と Mac で動作ができず、気が付くのに小一時間要してしまいました。
バーコードの時は Android でできていたのでそこに固執してしまったんですね。
反省です。

ではでは。