複数の姿勢検出には、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>