手の骨格を取得する(Handpose)
OpenProcessing、ml5.jsを使って手の骨格を取得します。
まず ml5.jsの手の骨格取得ライブラリHandpose からソースを頂きます。Examplesのp5.js→Handpose webcamを選択します。index.htmlとsketch.jsの2つのファイルがありますが、sketch.js(JavaScriptファイル)のみ使います。index.htmlの作業はOpenProcessingが提供してくれています。
OpenProcessingにログインして、Create a sketchで新しいsketchを開きます。デフォルトでp5.jsモードになっているのでそのまま使います。ここにsketch.jsの中身をコピペします。デフォルトで出ているプログラムは消去してください。またPosenetのときと同様にLIBRARIESでml5を選んでください。
実行すると下記のように各指の関節位置とその関節を結んだ線が表示されます。
https://qiita.com/youtoy/items/318b2ed0f9f27d379409 に黄色のリンクを追加
各関節のプログラム内での順番は下の図のようになっているようです。
以下にプログラムを示します。OpenProcessingへのリンクはこちら。
// https://learn.ml5js.org/#/reference/handpose
let handpose; //手の骨格用変数
let video; //ビデオ用変数
let flippedVideo; //上のvideoを左右反転させた動画を表す変数
let predictions = []; //手の骨格の配列(2つ以上の手を検出する場合のため)
let ftip = new Array(5); //各指先を保存するための配列
const options = {
flipHorizontal: true, // boolean value for if the video should be flipped, defaults to false
maxContinuousChecks: Infinity, // How many frames to go without running the bounding box detector. Defaults to infinity, but try a lower value if the detector is consistently producing bad predictions.
detectionConfidence: 0.5, // Threshold for discarding a prediction. Defaults to 0.8.
scoreThreshold: 0.75, // A threshold for removing multiple (likely duplicate) detections based on a "non-maximum suppression" algorithm. Defaults to 0.75
iouThreshold: 0.3, // A float representing the threshold for deciding whether boxes overlap too much in non-maximum suppression. Must be between [0, 1]. Defaults to 0.3.
}
function setup() {
createCanvas(640, 480); //Canvasサイズの指定
video = createCapture(VIDEO); //カメラ入力をvideoという変数に代入
video.size(width, height); //ビデオのサイズを指定
for (let i = 0; i < 5; i++) {
ftip[i] = new Array(2); // 2個
}
flippedVideo = ml5.flipImage(video); //ビデオを左右反転
handpose = ml5.handpose(video, options, modelReady); //手の骨格検出のための準備
// handpose = ml5.handpose(video, modelReady);
// This sets up an event that fills the global variable "predictions"
// with an array every time new hand poses are detected
handpose.on("predict", results => { //手の骨格検出をスタート
predictions = results; //結果をpredictionsという変数に入れる
});
// Hide the video element, and just show the canvas
video.hide(); //ビデオの表示を隠す
strokeWeight(5); //線の幅の指定
}
function modelReady() {
console.log("Model ready!"); //手の骨格検出の準備ができたらModel ready!と表示する
}
function draw() {
flippedVideo = ml5.flipImage(video); //ビデオの左右反転処理
image(flippedVideo, 0, 0, width, height); //ビデオを(0,0)の位置にwidth幅、height高さで表示
// scale(-1,1);
// image(video, -width, 0, width, height);
// We can call both functions to draw all keypoints and the skeletons
drawKeypoints(); //keypoints(関節位置およびリンク)を描く
}
// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
// if(frameCount % 60 == 0) console.log(predictions.length);
for (let i = 0; i < predictions.length; i += 1) { //predictions.length:検出した手の数(今のところ1のみ)
const prediction = predictions[i]; ////predictionという変数にredictionsのi番目を入れる(今のところ0のみ)
let pKeypoint;
// for (let j = 4; j < 5; j += 1) {
for (let j = 0; j < prediction.landmarks.length; j += 1) { //0から関節の数(今は20)分ループを回す
const keypoint = prediction.landmarks[j]; //各関節の位置(x、y)座標値をkeypointに入れる
//keypoint[0]:keypointのx座標値、keypoint[1]:y座標値
if (j > 0 && (j != 5 && j != 9 && j != 13 && j != 17)) { //リンクを描く関節間のみ扱うため、0番、5,9,13,17番(手首と指先)は描かないようにする。
stroke(255, 255, 0); //リンクの色を黄色とする
line(pKeypoint[0], pKeypoint[1], keypoint[0], keypoint[1]); //関節間を線で結ぶ
}
noStroke();
fill(0, 255, 0); //fillを緑色にする
// ellipse(-keypoint[0] - width, keypoint[1], 10, 10);
ellipse(keypoint[0], keypoint[1], 10, 10); //keypointの位置に直径10の円を描く
pKeypoint = keypoint; //現在のkeypointをpKeypointとして保存し、次回関節間を線で結ぶ時の1つ前のkeypointとして使う
}
count = 0; //指先位置のカウント
for(let i=4; i<=20; i+=4){ //指先は4, 8, 12, 16, 20番目なのでそれらの(x、y)座標値をftipに保存する
ftip[count][0] = prediction.landmarks[i][0];
ftip[count][1] = prediction.landmarks[i][1];
count++;
}
// for(let i=0; i<5; i++){
// for(let j=0; j<2; j++){
// print(ftip[i][j]);
// }
// }
// print("**********************");
}
}