2020-10-12 16:08:00 +02:00
|
|
|
const tf = require('@tensorflow/tfjs');
|
2020-10-14 17:43:33 +02:00
|
|
|
const facemesh = require('./facemesh/facemesh.js');
|
|
|
|
const ssrnet = require('./ssrnet/ssrnet.js');
|
2020-10-15 00:22:38 +02:00
|
|
|
const emotion = require('./emotion/emotion.js');
|
2020-10-14 17:43:33 +02:00
|
|
|
const posenet = require('./posenet/posenet.js');
|
|
|
|
const handpose = require('./handpose/handpose.js');
|
2020-10-12 01:22:43 +02:00
|
|
|
const defaults = require('./config.js').default;
|
|
|
|
|
|
|
|
const models = {
|
|
|
|
facemesh: null,
|
|
|
|
blazeface: null,
|
|
|
|
ssrnet: null,
|
|
|
|
iris: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
function mergeDeep(...objects) {
|
|
|
|
const isObject = (obj) => obj && typeof obj === 'object';
|
|
|
|
return objects.reduce((prev, obj) => {
|
2020-10-12 03:21:41 +02:00
|
|
|
Object.keys(obj || {}).forEach((key) => {
|
2020-10-12 01:22:43 +02:00
|
|
|
const pVal = prev[key];
|
|
|
|
const oVal = obj[key];
|
|
|
|
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
|
|
|
prev[key] = pVal.concat(...oVal);
|
|
|
|
} else if (isObject(pVal) && isObject(oVal)) {
|
|
|
|
prev[key] = mergeDeep(pVal, oVal);
|
|
|
|
} else {
|
|
|
|
prev[key] = oVal;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return prev;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function detect(input, userConfig) {
|
2020-10-14 02:52:30 +02:00
|
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
|
|
return new Promise(async (resolve) => {
|
|
|
|
const config = mergeDeep(defaults, userConfig);
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
// load models if enabled
|
|
|
|
if (config.face.age.enabled) await ssrnet.loadAge(config);
|
|
|
|
if (config.face.gender.enabled) await ssrnet.loadGender(config);
|
2020-10-15 00:22:38 +02:00
|
|
|
if (config.face.emotion.enabled) await emotion.load(config);
|
2020-10-14 02:52:30 +02:00
|
|
|
if (config.body.enabled && !models.posenet) models.posenet = await posenet.load(config.body);
|
|
|
|
if (config.hand.enabled && !models.handpose) models.handpose = await handpose.load(config.hand);
|
|
|
|
if (config.face.enabled && !models.facemesh) models.facemesh = await facemesh.load(config.face);
|
2020-10-13 04:01:35 +02:00
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
tf.engine().startScope();
|
2020-10-13 04:01:35 +02:00
|
|
|
|
2020-10-14 17:43:33 +02:00
|
|
|
let savedWebglPackDepthwiseConvFlag;
|
|
|
|
if (tf.getBackend() === 'webgl') {
|
|
|
|
savedWebglPackDepthwiseConvFlag = tf.env().get('WEBGL_PACK_DEPTHWISECONV');
|
|
|
|
tf.env().set('WEBGL_PACK_DEPTHWISECONV', true);
|
|
|
|
}
|
|
|
|
|
2020-10-14 19:23:02 +02:00
|
|
|
const perf = {};
|
|
|
|
let timeStamp;
|
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
// run posenet
|
2020-10-14 19:23:02 +02:00
|
|
|
timeStamp = performance.now();
|
2020-10-14 02:52:30 +02:00
|
|
|
let poseRes = [];
|
2020-10-14 17:43:33 +02:00
|
|
|
if (config.body.enabled) poseRes = await models.posenet.estimatePoses(input, config.body);
|
2020-10-14 19:23:02 +02:00
|
|
|
perf.body = Math.trunc(performance.now() - timeStamp);
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
// run handpose
|
2020-10-14 19:23:02 +02:00
|
|
|
timeStamp = performance.now();
|
2020-10-14 02:52:30 +02:00
|
|
|
let handRes = [];
|
|
|
|
if (config.hand.enabled) handRes = await models.handpose.estimateHands(input, config.hand);
|
2020-10-14 19:23:02 +02:00
|
|
|
perf.hand = Math.trunc(performance.now() - timeStamp);
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
// run facemesh, includes blazeface and iris
|
|
|
|
const faceRes = [];
|
|
|
|
if (config.face.enabled) {
|
2020-10-14 19:23:02 +02:00
|
|
|
timeStamp = performance.now();
|
2020-10-14 02:52:30 +02:00
|
|
|
const faces = await models.facemesh.estimateFaces(input, config.face);
|
2020-10-14 19:23:02 +02:00
|
|
|
perf.face = Math.trunc(performance.now() - timeStamp);
|
2020-10-14 02:52:30 +02:00
|
|
|
for (const face of faces) {
|
|
|
|
// run ssr-net age & gender, inherits face from blazeface
|
2020-10-14 19:23:02 +02:00
|
|
|
timeStamp = performance.now();
|
2020-10-14 02:52:30 +02:00
|
|
|
const ssrdata = (config.face.age.enabled || config.face.gender.enabled) ? await ssrnet.predict(face.image, config) : {};
|
2020-10-14 19:23:02 +02:00
|
|
|
perf.agegender = Math.trunc(performance.now() - timeStamp);
|
2020-10-15 00:22:38 +02:00
|
|
|
// run emotion, inherits face from blazeface
|
|
|
|
timeStamp = performance.now();
|
|
|
|
const emotiondata = config.face.emotion.enabled ? await emotion.predict(face.image, config) : {};
|
|
|
|
perf.emotion = Math.trunc(performance.now() - timeStamp);
|
2020-10-14 02:52:30 +02:00
|
|
|
face.image.dispose();
|
2020-10-15 00:22:38 +02:00
|
|
|
// calculate iris distance
|
2020-10-14 02:52:30 +02:00
|
|
|
// iris: array[ bottom, left, top, right, center ]
|
|
|
|
const iris = (face.annotations.leftEyeIris && face.annotations.rightEyeIris)
|
|
|
|
? Math.max(face.annotations.leftEyeIris[3][0] - face.annotations.leftEyeIris[1][0], face.annotations.rightEyeIris[3][0] - face.annotations.rightEyeIris[1][0])
|
|
|
|
: 0;
|
|
|
|
faceRes.push({
|
|
|
|
confidence: face.confidence,
|
|
|
|
box: face.box,
|
|
|
|
mesh: face.mesh,
|
|
|
|
annotations: face.annotations,
|
|
|
|
age: ssrdata.age,
|
|
|
|
gender: ssrdata.gender,
|
2020-10-15 00:22:38 +02:00
|
|
|
emotion: emotiondata,
|
|
|
|
iris: (iris !== 0) ? Math.trunc(100 * 11.7 /* human iris size in mm */ / iris) / 100 : 0,
|
2020-10-14 02:52:30 +02:00
|
|
|
});
|
|
|
|
}
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
2020-10-13 04:01:35 +02:00
|
|
|
|
2020-10-14 17:43:33 +02:00
|
|
|
tf.env().set('WEBGL_PACK_DEPTHWISECONV', savedWebglPackDepthwiseConvFlag);
|
|
|
|
|
2020-10-14 02:52:30 +02:00
|
|
|
tf.engine().endScope();
|
|
|
|
// combine results
|
2020-10-14 19:23:02 +02:00
|
|
|
perf.total = Object.values(perf).reduce((a, b) => a + b);
|
|
|
|
resolve({ face: faceRes, body: poseRes, hand: handRes, performance: perf });
|
2020-10-14 02:52:30 +02:00
|
|
|
});
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.detect = detect;
|
|
|
|
exports.defaults = defaults;
|
|
|
|
exports.models = models;
|
2020-10-12 03:21:41 +02:00
|
|
|
exports.facemesh = facemesh;
|
|
|
|
exports.ssrnet = ssrnet;
|
|
|
|
exports.posenet = posenet;
|
|
|
|
exports.handpose = handpose;
|
2020-10-12 16:08:00 +02:00
|
|
|
exports.tf = tf;
|