2021-05-25 14:58:20 +02:00
|
|
|
/**
|
2021-09-25 17:51:15 +02:00
|
|
|
* Face algorithm implementation
|
|
|
|
* Uses FaceMesh, Emotion and FaceRes models to create a unified pipeline
|
2021-05-25 14:58:20 +02:00
|
|
|
*/
|
|
|
|
|
2021-09-27 19:58:13 +02:00
|
|
|
import { log, now } from '../util/util';
|
2021-10-23 15:38:52 +02:00
|
|
|
import { env } from '../util/env';
|
2021-09-27 19:58:13 +02:00
|
|
|
import * as tf from '../../dist/tfjs.esm.js';
|
2021-09-28 18:01:48 +02:00
|
|
|
import * as facemesh from './facemesh';
|
2021-09-27 19:58:13 +02:00
|
|
|
import * as emotion from '../gear/emotion';
|
|
|
|
import * as faceres from './faceres';
|
2021-11-12 21:07:23 +01:00
|
|
|
import * as mask from './mask';
|
2021-10-13 16:56:56 +02:00
|
|
|
import * as antispoof from './antispoof';
|
2021-11-09 20:37:50 +01:00
|
|
|
import * as liveness from './liveness';
|
2021-09-27 19:58:13 +02:00
|
|
|
import type { FaceResult } from '../result';
|
|
|
|
import type { Tensor } from '../tfjs/types';
|
2021-11-12 21:07:23 +01:00
|
|
|
import type { Human } from '../human';
|
2021-09-28 18:01:48 +02:00
|
|
|
import { calculateFaceAngle } from './angles';
|
2021-03-21 12:49:55 +01:00
|
|
|
|
2021-11-12 21:07:23 +01:00
|
|
|
export const detectFace = async (parent: Human /* instance of human */, input: Tensor): Promise<FaceResult[]> => {
|
2021-03-21 12:49:55 +01:00
|
|
|
// run facemesh, includes blazeface and iris
|
|
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
|
|
let timeStamp;
|
|
|
|
let ageRes;
|
2021-08-01 02:42:28 +02:00
|
|
|
let gearRes;
|
2021-03-21 12:49:55 +01:00
|
|
|
let genderRes;
|
|
|
|
let emotionRes;
|
|
|
|
let embeddingRes;
|
2021-10-13 16:56:56 +02:00
|
|
|
let antispoofRes;
|
2021-11-09 20:37:50 +01:00
|
|
|
let livenessRes;
|
2021-03-21 19:18:51 +01:00
|
|
|
let descRes;
|
2021-09-12 05:54:35 +02:00
|
|
|
const faceRes: Array<FaceResult> = [];
|
2021-03-21 12:49:55 +01:00
|
|
|
parent.state = 'run:face';
|
|
|
|
timeStamp = now();
|
2021-09-28 18:01:48 +02:00
|
|
|
|
2021-04-25 22:56:10 +02:00
|
|
|
const faces = await facemesh.predict(input, parent.config);
|
2021-10-23 15:38:52 +02:00
|
|
|
parent.performance.face = env.perfadd ? (parent.performance.face || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
|
2021-05-31 16:40:07 +02:00
|
|
|
if (!input.shape || input.shape.length !== 4) return [];
|
2021-03-21 12:49:55 +01:00
|
|
|
if (!faces) return [];
|
2021-05-18 17:26:16 +02:00
|
|
|
// for (const face of faces) {
|
|
|
|
for (let i = 0; i < faces.length; i++) {
|
2021-03-21 12:49:55 +01:00
|
|
|
parent.analyze('Get Face');
|
|
|
|
|
|
|
|
// is something went wrong, skip the face
|
2021-08-17 14:51:17 +02:00
|
|
|
// @ts-ignore possibly undefied
|
2021-08-12 00:59:02 +02:00
|
|
|
if (!faces[i].tensor || faces[i].tensor['isDisposedInternal']) {
|
|
|
|
log('Face object is disposed:', faces[i].tensor);
|
2021-03-21 12:49:55 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-12 21:07:23 +01:00
|
|
|
// optional face mask
|
|
|
|
if (parent.config.face.detector?.mask) {
|
|
|
|
const masked = await mask.mask(faces[i]);
|
|
|
|
tf.dispose(faces[i].tensor);
|
|
|
|
faces[i].tensor = masked as Tensor;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate face angles
|
|
|
|
const rotation = faces[i].mesh && (faces[i].mesh.length > 200) ? calculateFaceAngle(faces[i], [input.shape[2], input.shape[1]]) : null;
|
2021-03-21 12:49:55 +01:00
|
|
|
|
|
|
|
// run emotion, inherits face from blazeface
|
|
|
|
parent.analyze('Start Emotion:');
|
|
|
|
if (parent.config.async) {
|
2021-11-12 21:07:23 +01:00
|
|
|
emotionRes = parent.config.face.emotion?.enabled ? emotion.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-03-21 12:49:55 +01:00
|
|
|
} else {
|
|
|
|
parent.state = 'run:emotion';
|
|
|
|
timeStamp = now();
|
2021-11-12 21:07:23 +01:00
|
|
|
emotionRes = parent.config.face.emotion?.enabled ? await emotion.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-10-23 15:38:52 +02:00
|
|
|
parent.performance.emotion = env.perfadd ? (parent.performance.emotion || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
|
2021-03-21 12:49:55 +01:00
|
|
|
}
|
|
|
|
parent.analyze('End Emotion:');
|
|
|
|
|
2021-10-13 16:56:56 +02:00
|
|
|
// run antispoof, inherits face from blazeface
|
|
|
|
parent.analyze('Start AntiSpoof:');
|
|
|
|
if (parent.config.async) {
|
2021-11-12 21:07:23 +01:00
|
|
|
antispoofRes = parent.config.face.antispoof?.enabled ? antispoof.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-10-13 16:56:56 +02:00
|
|
|
} else {
|
|
|
|
parent.state = 'run:antispoof';
|
|
|
|
timeStamp = now();
|
2021-11-12 21:07:23 +01:00
|
|
|
antispoofRes = parent.config.face.antispoof?.enabled ? await antispoof.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-10-23 15:38:52 +02:00
|
|
|
parent.performance.antispoof = env.perfadd ? (parent.performance.antispoof || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
|
2021-10-13 16:56:56 +02:00
|
|
|
}
|
|
|
|
parent.analyze('End AntiSpoof:');
|
|
|
|
|
2021-11-09 20:37:50 +01:00
|
|
|
// run liveness, inherits face from blazeface
|
|
|
|
parent.analyze('Start Liveness:');
|
|
|
|
if (parent.config.async) {
|
2021-11-12 21:07:23 +01:00
|
|
|
livenessRes = parent.config.face.liveness?.enabled ? liveness.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-11-09 20:37:50 +01:00
|
|
|
} else {
|
|
|
|
parent.state = 'run:liveness';
|
|
|
|
timeStamp = now();
|
2021-11-12 21:07:23 +01:00
|
|
|
livenessRes = parent.config.face.liveness?.enabled ? await liveness.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-11-09 20:37:50 +01:00
|
|
|
parent.performance.antispoof = env.perfadd ? (parent.performance.antispoof || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
|
|
|
|
}
|
|
|
|
parent.analyze('End Liveness:');
|
|
|
|
|
2021-08-01 02:42:28 +02:00
|
|
|
// run gear, inherits face from blazeface
|
|
|
|
/*
|
|
|
|
parent.analyze('Start GEAR:');
|
|
|
|
if (parent.config.async) {
|
2021-08-12 00:59:02 +02:00
|
|
|
gearRes = parent.config.face.agegenderrace.enabled ? agegenderrace.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : {};
|
2021-08-01 02:42:28 +02:00
|
|
|
} else {
|
|
|
|
parent.state = 'run:gear';
|
|
|
|
timeStamp = now();
|
2021-08-12 00:59:02 +02:00
|
|
|
gearRes = parent.config.face.agegenderrace.enabled ? await agegenderrace.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : {};
|
2021-08-01 02:42:28 +02:00
|
|
|
parent.performance.emotion = Math.trunc(now() - timeStamp);
|
|
|
|
}
|
|
|
|
parent.analyze('End GEAR:');
|
|
|
|
*/
|
|
|
|
|
2021-03-21 19:18:51 +01:00
|
|
|
// run emotion, inherits face from blazeface
|
|
|
|
parent.analyze('Start Description:');
|
|
|
|
if (parent.config.async) {
|
2021-11-12 21:07:23 +01:00
|
|
|
descRes = parent.config.face.description?.enabled ? faceres.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-03-21 19:18:51 +01:00
|
|
|
} else {
|
|
|
|
parent.state = 'run:description';
|
|
|
|
timeStamp = now();
|
2021-11-12 21:07:23 +01:00
|
|
|
descRes = parent.config.face.description?.enabled ? await faceres.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : null;
|
2021-10-27 15:45:38 +02:00
|
|
|
parent.performance.description = env.perfadd ? (parent.performance.description || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
|
2021-03-21 19:18:51 +01:00
|
|
|
}
|
|
|
|
parent.analyze('End Description:');
|
2021-03-21 12:49:55 +01:00
|
|
|
|
|
|
|
// if async wait for results
|
|
|
|
if (parent.config.async) {
|
2021-11-09 20:37:50 +01:00
|
|
|
[ageRes, genderRes, emotionRes, embeddingRes, descRes, gearRes, antispoofRes, livenessRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes, descRes, gearRes, antispoofRes, livenessRes]);
|
2021-03-21 12:49:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
parent.analyze('Finish Face:');
|
|
|
|
|
|
|
|
// calculate iris distance
|
|
|
|
// iris: array[ center, left, top, right, bottom]
|
2021-11-12 21:07:23 +01:00
|
|
|
if (!parent.config.face.iris?.enabled && faces[i]?.annotations?.leftEyeIris && faces[i]?.annotations?.rightEyeIris) {
|
2021-05-18 17:26:16 +02:00
|
|
|
delete faces[i].annotations.leftEyeIris;
|
|
|
|
delete faces[i].annotations.rightEyeIris;
|
2021-03-21 12:49:55 +01:00
|
|
|
}
|
2021-09-28 18:01:48 +02:00
|
|
|
const irisSize = (faces[i].annotations && faces[i].annotations.leftEyeIris && faces[i].annotations.leftEyeIris[0] && faces[i].annotations.rightEyeIris && faces[i].annotations.rightEyeIris[0]
|
2021-09-23 20:09:41 +02:00
|
|
|
&& (faces[i].annotations.leftEyeIris.length > 0) && (faces[i].annotations.rightEyeIris.length > 0)
|
|
|
|
&& (faces[i].annotations.leftEyeIris[0] !== null) && (faces[i].annotations.rightEyeIris[0] !== null))
|
2021-05-24 13:16:38 +02:00
|
|
|
? Math.max(Math.abs(faces[i].annotations.leftEyeIris[3][0] - faces[i].annotations.leftEyeIris[1][0]), Math.abs(faces[i].annotations.rightEyeIris[4][1] - faces[i].annotations.rightEyeIris[2][1])) / input.shape[2]
|
2021-09-23 20:09:41 +02:00
|
|
|
: 0; // note: average human iris size is 11.7mm
|
2021-03-21 12:49:55 +01:00
|
|
|
|
2021-08-12 00:59:02 +02:00
|
|
|
// optionally return tensor
|
2021-11-12 21:07:23 +01:00
|
|
|
const tensor = parent.config.face.detector?.return ? tf.squeeze(faces[i].tensor) : null;
|
2021-08-12 00:59:02 +02:00
|
|
|
// dispose original face tensor
|
|
|
|
tf.dispose(faces[i].tensor);
|
|
|
|
// delete temp face image
|
|
|
|
if (faces[i].tensor) delete faces[i].tensor;
|
2021-03-21 12:49:55 +01:00
|
|
|
// combine results
|
|
|
|
faceRes.push({
|
2021-05-18 17:26:16 +02:00
|
|
|
...faces[i],
|
2021-06-01 13:37:17 +02:00
|
|
|
id: i,
|
2021-10-21 16:54:51 +02:00
|
|
|
age: descRes?.age,
|
|
|
|
gender: descRes?.gender,
|
|
|
|
genderScore: descRes?.genderScore,
|
|
|
|
embedding: descRes?.descriptor,
|
2021-03-21 12:49:55 +01:00
|
|
|
emotion: emotionRes,
|
2021-10-13 16:56:56 +02:00
|
|
|
real: antispoofRes,
|
2021-11-09 20:37:50 +01:00
|
|
|
live: livenessRes,
|
2021-05-24 13:16:38 +02:00
|
|
|
iris: irisSize !== 0 ? Math.trunc(500 / irisSize / 11.7) / 100 : 0,
|
2021-03-28 14:40:39 +02:00
|
|
|
rotation,
|
2021-08-12 00:59:02 +02:00
|
|
|
tensor,
|
2021-03-21 12:49:55 +01:00
|
|
|
});
|
|
|
|
parent.analyze('End Face');
|
|
|
|
}
|
|
|
|
parent.analyze('End FaceMesh:');
|
|
|
|
if (parent.config.async) {
|
2021-05-31 16:40:07 +02:00
|
|
|
if (parent.performance.face) delete parent.performance.face;
|
|
|
|
if (parent.performance.age) delete parent.performance.age;
|
|
|
|
if (parent.performance.gender) delete parent.performance.gender;
|
|
|
|
if (parent.performance.emotion) delete parent.performance.emotion;
|
2021-03-21 12:49:55 +01:00
|
|
|
}
|
|
|
|
return faceRes;
|
|
|
|
};
|