human/src/efficientpose/efficientpose.ts

93 lines
3.5 KiB
TypeScript
Raw Normal View History

2021-04-09 14:07:58 +02:00
import { log, join } from '../helpers';
2021-03-26 23:50:19 +01:00
import * as tf from '../../dist/tfjs.esm.js';
let model;
2021-04-01 15:24:56 +02:00
let keypoints: Array<any> = [];
2021-03-26 23:50:19 +01:00
let skipped = Number.MAX_SAFE_INTEGER;
const bodyParts = ['head', 'neck', 'rightShoulder', 'rightElbow', 'rightWrist', 'chest', 'leftShoulder', 'leftElbow', 'leftWrist', 'pelvis', 'rightHip', 'rightKnee', 'rightAnkle', 'leftHip', 'leftKnee', 'leftAnkle'];
export async function load(config) {
if (!model) {
2021-04-09 14:07:58 +02:00
model = await tf.loadGraphModel(join(config.modelBasePath, config.body.modelPath));
if (!model || !model.modelUrl) log('load model failed:', config.body.modelPath);
else if (config.debug) log('load model:', model.modelUrl);
} else if (config.debug) log('cached model:', model.modelUrl);
2021-03-26 23:50:19 +01:00
return model;
}
// performs argmax and max functions on a 2d tensor
function max2d(inputs, minScore) {
const [width, height] = inputs.shape;
return tf.tidy(() => {
// modulus op implemented in tf
const mod = (a, b) => tf.sub(a, tf.mul(tf.div(a, tf.scalar(b, 'int32')), tf.scalar(b, 'int32')));
// combine all data
const reshaped = tf.reshape(inputs, [height * width]);
// get highest score
const score = tf.max(reshaped, 0).dataSync()[0];
if (score > minScore) {
// skip coordinate calculation is score is too low
const coords = tf.argMax(reshaped, 0);
const x = mod(coords, width).dataSync()[0];
const y = tf.div(coords, tf.scalar(width, 'int32')).dataSync()[0];
return [x, y, score];
}
return [0, 0, score];
});
}
export async function predict(image, config) {
if (!model) return null;
if ((skipped < config.body.skipFrames) && config.skipFrame && Object.keys(keypoints).length > 0) {
2021-03-26 23:50:19 +01:00
skipped++;
2021-03-27 20:43:48 +01:00
return keypoints;
2021-03-26 23:50:19 +01:00
}
skipped = 0;
2021-03-26 23:50:19 +01:00
return new Promise(async (resolve) => {
2021-03-27 20:43:48 +01:00
const tensor = tf.tidy(() => {
const resize = tf.image.resizeBilinear(image, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const enhance = tf.mul(resize, 2);
const norm = enhance.sub(1);
return norm;
});
2021-03-26 23:50:19 +01:00
let resT;
2021-04-25 19:16:04 +02:00
if (config.body.enabled) resT = await model.predict(tensor);
2021-03-27 20:43:48 +01:00
tensor.dispose();
2021-03-26 23:50:19 +01:00
if (resT) {
const parts: Array<{ id, score, part, position: { x, y }, positionRaw: { xRaw, yRaw} }> = [];
const squeeze = resT.squeeze();
tf.dispose(resT);
// body parts are basically just a stack of 2d tensors
const stack = squeeze.unstack(2);
tf.dispose(squeeze);
// process each unstacked tensor as a separate body part
for (let id = 0; id < stack.length; id++) {
// actual processing to get coordinates and score
2021-04-25 19:16:04 +02:00
const [x, y, score] = max2d(stack[id], config.body.minConfidence);
if (score > config.body.minConfidence) {
2021-03-26 23:50:19 +01:00
parts.push({
id,
2021-04-01 15:24:56 +02:00
score: Math.round(100 * score) / 100,
2021-03-26 23:50:19 +01:00
part: bodyParts[id],
positionRaw: {
xRaw: x / model.inputs[0].shape[2], // x normalized to 0..1
yRaw: y / model.inputs[0].shape[1], // y normalized to 0..1
},
position: {
x: Math.round(image.shape[2] * x / model.inputs[0].shape[2]), // x normalized to input image size
y: Math.round(image.shape[1] * y / model.inputs[0].shape[1]), // y normalized to input image size
},
});
}
}
stack.forEach((s) => tf.dispose(s));
2021-03-27 20:43:48 +01:00
keypoints = parts;
2021-03-26 23:50:19 +01:00
}
2021-04-01 15:24:56 +02:00
const score = keypoints.reduce((prev, curr) => (curr.score > prev ? curr.score : prev), 0);
resolve([{ score, keypoints }]);
2021-03-26 23:50:19 +01:00
});
}