human/src/face/face.ts

243 lines
13 KiB
TypeScript
Raw Normal View History

2021-05-25 14:58:20 +02:00
/**
* Face algorithm implementation
* Uses FaceMesh, Emotion and FaceRes models to create a unified pipeline
2021-05-25 14:58:20 +02:00
*/
2022-10-17 02:28:57 +02:00
import * as tf from 'dist/tfjs.esm.js';
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-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';
import * as liveness from './liveness';
2021-11-13 18:23:32 +01:00
import * as gear from '../gear/gear';
import * as ssrnetAge from '../gear/ssrnet-age';
import * as ssrnetGender from '../gear/ssrnet-gender';
2021-11-13 23:26:19 +01:00
import * as mobilefacenet from './mobilefacenet';
2022-08-08 21:09:26 +02:00
import * as insightface from './insightface';
2021-12-28 17:39:54 +01:00
import type { FaceResult, Emotion, Gender, Race } from '../result';
2022-10-17 02:28:57 +02:00
import type { Tensor4D } 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
2022-08-21 19:34:51 +02:00
interface DescRes { age: number, gender: Gender, genderScore: number, descriptor: number[], race?: { score: number, race: Race }[] }
2021-12-28 17:39:54 +01:00
2022-10-17 02:28:57 +02:00
export const detectFace = async (instance: Human /* instance of human */, input: Tensor4D): Promise<FaceResult[]> => {
2021-03-21 12:49:55 +01:00
// run facemesh, includes blazeface and iris
2021-12-28 17:39:54 +01:00
let timeStamp: number = now();
let ageRes: { age: number } | Promise<{ age: number }> | null;
let gearRes: gear.GearType | Promise<gear.GearType> | null;
let genderRes: { gender: string, genderScore: number } | Promise<{ gender: string, genderScore: number }> | null;
let emotionRes: { score: number, emotion: Emotion }[] | Promise<{ score: number, emotion: Emotion }[]>;
let mobilefacenetRes: number[] | Promise<number[]> | null;
2022-08-08 21:09:26 +02:00
let insightfaceRes: number[] | Promise<number[]> | null;
2021-12-28 17:39:54 +01:00
let antispoofRes: number | Promise<number> | null;
let livenessRes: number | Promise<number> | null;
let descRes: DescRes | Promise<DescRes> | null;
2022-08-21 19:34:51 +02:00
const faceRes: FaceResult[] = [];
2021-11-14 17:22:52 +01:00
instance.state = 'run:face';
2021-09-28 18:01:48 +02:00
2021-11-14 17:22:52 +01:00
const faces = await facemesh.predict(input, instance.config);
instance.performance.face = env.perfadd ? (instance.performance.face || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
if (!input.shape || input.shape.length !== 4) return [];
2021-03-21 12:49:55 +01:00
if (!faces) return [];
// for (const face of faces) {
for (let i = 0; i < faces.length; i++) {
2021-11-14 17:22:52 +01:00
instance.analyze('Get Face');
2021-03-21 12:49:55 +01:00
// is something went wrong, skip the face
2021-08-17 14:51:17 +02:00
// @ts-ignore possibly undefied
2022-08-21 19:34:51 +02:00
if (!faces[i].tensor || faces[i].tensor.isDisposedInternal) {
2021-08-12 00:59:02 +02:00
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
2021-11-14 17:22:52 +01:00
if (instance.config.face.detector?.mask) {
2021-11-12 21:07:23 +01:00
const masked = await mask.mask(faces[i]);
tf.dispose(faces[i].tensor);
2022-08-21 19:34:51 +02:00
if (masked) faces[i].tensor = masked;
2021-11-12 21:07:23 +01:00
}
// 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
2021-11-14 17:22:52 +01:00
instance.analyze('Start Emotion:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
emotionRes = instance.config.face.emotion?.enabled ? emotion.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : [];
2021-03-21 12:49:55 +01:00
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:emotion';
2021-03-21 12:49:55 +01:00
timeStamp = now();
2022-10-17 02:28:57 +02:00
emotionRes = instance.config.face.emotion?.enabled ? await emotion.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : [];
2021-11-14 17:22:52 +01:00
instance.performance.emotion = env.perfadd ? (instance.performance.emotion || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
2021-03-21 12:49:55 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End Emotion:');
2021-03-21 12:49:55 +01:00
2021-10-13 16:56:56 +02:00
// run antispoof, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start AntiSpoof:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
antispoofRes = instance.config.face.antispoof?.enabled ? antispoof.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : 0;
2021-10-13 16:56:56 +02:00
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:antispoof';
2021-10-13 16:56:56 +02:00
timeStamp = now();
2022-10-17 02:28:57 +02:00
antispoofRes = instance.config.face.antispoof?.enabled ? await antispoof.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : 0;
2021-11-14 17:22:52 +01:00
instance.performance.antispoof = env.perfadd ? (instance.performance.antispoof || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
2021-10-13 16:56:56 +02:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End AntiSpoof:');
2021-10-13 16:56:56 +02:00
// run liveness, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start Liveness:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
livenessRes = instance.config.face.liveness?.enabled ? liveness.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : 0;
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:liveness';
timeStamp = now();
2022-10-17 02:28:57 +02:00
livenessRes = instance.config.face.liveness?.enabled ? await liveness.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : 0;
2021-11-14 17:22:52 +01:00
instance.performance.liveness = env.perfadd ? (instance.performance.antispoof || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
}
2021-11-14 17:22:52 +01:00
instance.analyze('End Liveness:');
// run gear, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start GEAR:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
gearRes = instance.config.face.gear?.enabled ? gear.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:gear';
timeStamp = now();
2022-10-17 02:28:57 +02:00
gearRes = instance.config.face.gear?.enabled ? await gear.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2021-11-14 17:22:52 +01:00
instance.performance.gear = Math.trunc(now() - timeStamp);
}
2021-11-14 17:22:52 +01:00
instance.analyze('End GEAR:');
2021-11-13 18:23:32 +01:00
// run gear, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start SSRNet:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
ageRes = instance.config.face['ssrnet']?.enabled ? ssrnetAge.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
genderRes = instance.config.face['ssrnet']?.enabled ? ssrnetGender.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2021-11-13 18:23:32 +01:00
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:ssrnet';
2021-11-13 18:23:32 +01:00
timeStamp = now();
2022-10-17 02:28:57 +02:00
ageRes = instance.config.face['ssrnet']?.enabled ? await ssrnetAge.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
genderRes = instance.config.face['ssrnet']?.enabled ? await ssrnetGender.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2021-11-14 17:22:52 +01:00
instance.performance.ssrnet = Math.trunc(now() - timeStamp);
2021-11-13 18:23:32 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End SSRNet:');
2022-08-08 21:09:26 +02:00
// run mobilefacenet alternative, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start MobileFaceNet:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
mobilefacenetRes = instance.config.face['mobilefacenet']?.enabled ? mobilefacenet.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2021-11-13 23:26:19 +01:00
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:mobilefacenet';
2021-11-13 23:26:19 +01:00
timeStamp = now();
2022-10-17 02:28:57 +02:00
mobilefacenetRes = instance.config.face['mobilefacenet']?.enabled ? await mobilefacenet.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2021-11-14 17:22:52 +01:00
instance.performance.mobilefacenet = Math.trunc(now() - timeStamp);
2021-11-13 23:26:19 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End MobileFaceNet:');
2021-11-13 23:26:19 +01:00
2022-08-08 21:09:26 +02:00
// run insightface alternative, inherits face from blazeface
instance.analyze('Start InsightFace:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
insightfaceRes = instance.config.face['insightface']?.enabled ? insightface.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2022-08-08 21:09:26 +02:00
} else {
instance.state = 'run:mobilefacenet';
timeStamp = now();
2022-10-17 02:28:57 +02:00
insightfaceRes = instance.config.face['insightface']?.enabled ? await insightface.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length) : null;
2022-08-08 21:09:26 +02:00
instance.performance.mobilefacenet = Math.trunc(now() - timeStamp);
}
instance.analyze('End InsightFace:');
// run faceres, inherits face from blazeface
2021-11-14 17:22:52 +01:00
instance.analyze('Start Description:');
if (instance.config.async) {
2022-10-17 02:28:57 +02:00
descRes = faceres.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length);
2021-03-21 19:18:51 +01:00
} else {
2021-11-14 17:22:52 +01:00
instance.state = 'run:description';
2021-03-21 19:18:51 +01:00
timeStamp = now();
2022-10-17 02:28:57 +02:00
descRes = await faceres.predict(faces[i].tensor as Tensor4D || tf.tensor([]), instance.config, i, faces.length);
2021-11-14 17:22:52 +01:00
instance.performance.description = env.perfadd ? (instance.performance.description || 0) + Math.trunc(now() - timeStamp) : Math.trunc(now() - timeStamp);
2021-03-21 19:18:51 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End Description:');
2021-03-21 12:49:55 +01:00
// if async wait for results
2021-11-14 17:22:52 +01:00
if (instance.config.async) {
2022-08-08 21:09:26 +02:00
[ageRes, genderRes, emotionRes, mobilefacenetRes, insightfaceRes, descRes, gearRes, antispoofRes, livenessRes] = await Promise.all([ageRes, genderRes, emotionRes, mobilefacenetRes, insightfaceRes, descRes, gearRes, antispoofRes, livenessRes]);
2021-03-21 12:49:55 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('Finish Face:');
2021-03-21 12:49:55 +01:00
2021-12-28 17:39:54 +01:00
if (instance.config.face['ssrnet']?.enabled && ageRes && genderRes) { // override age/gender if ssrnet model is used
descRes = {
...(descRes as DescRes),
age: (ageRes as { age: number}).age,
gender: (genderRes as { gender: Gender, genderScore: number }).gender,
genderScore: (genderRes as { gender: Gender, genderScore: number }).genderScore,
};
}
2022-08-21 19:34:51 +02:00
if (instance.config.face.gear?.enabled && gearRes) { // override age/gender/race if gear model is used
2021-12-28 17:39:54 +01:00
descRes = {
...(descRes as DescRes),
age: (gearRes as gear.GearType).age,
gender: (gearRes as gear.GearType).gender,
genderScore: (gearRes as gear.GearType).genderScore,
race: (gearRes as gear.GearType).race,
};
}
2022-08-08 21:09:26 +02:00
if (instance.config.face['mobilefacenet']?.enabled && mobilefacenetRes) { // override descriptor if mobilefacenet model is used
2021-12-28 17:39:54 +01:00
(descRes as DescRes).descriptor = mobilefacenetRes as number[];
}
2021-11-13 18:23:32 +01:00
2022-08-08 21:09:26 +02:00
if (instance.config.face['insightface']?.enabled && insightfaceRes) { // override descriptor if insightface model is used
(descRes as DescRes).descriptor = insightfaceRes as number[];
}
2021-03-21 12:49:55 +01:00
// calculate iris distance
// iris: array[ center, left, top, right, bottom]
2021-12-15 15:26:32 +01:00
if (!instance.config.face.iris?.enabled) {
// if (faces[i]?.annotations?.leftEyeIris) delete faces[i].annotations.leftEyeIris;
// if (faces[i]?.annotations?.rightEyeIris) delete faces[i].annotations.rightEyeIris;
2021-03-21 12:49:55 +01:00
}
2022-08-21 21:23:03 +02:00
const irisSize = (faces[i]?.annotations?.leftEyeIris?.[0] && 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
2022-10-17 02:28:57 +02:00
const tensor = instance.config.face.detector?.return ? tf.squeeze(faces[i].tensor as Tensor4D) : 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
2021-11-13 18:23:32 +01:00
const res: FaceResult = {
...faces[i],
id: i,
2021-11-13 18:23:32 +01:00
};
2022-08-21 19:34:51 +02:00
if ((descRes as DescRes).age) res.age = (descRes as DescRes).age;
if ((descRes as DescRes).gender) res.gender = (descRes as DescRes).gender;
if ((descRes as DescRes).genderScore) res.genderScore = (descRes as DescRes).genderScore;
if ((descRes as DescRes).descriptor) res.embedding = (descRes as DescRes).descriptor;
if ((descRes as DescRes).race) res.race = (descRes as DescRes).race as { score: number, race: Race }[];
if (emotionRes) res.emotion = emotionRes as { score: number, emotion: Emotion }[];
2021-12-28 17:39:54 +01:00
if (antispoofRes) res.real = antispoofRes as number;
if (livenessRes) res.live = livenessRes as number;
2021-11-13 18:23:32 +01:00
if (irisSize && irisSize !== 0) res.iris = Math.trunc(500 / irisSize / 11.7) / 100;
if (rotation) res.rotation = rotation;
if (tensor) res.tensor = tensor;
faceRes.push(res);
2021-11-14 17:22:52 +01:00
instance.analyze('End Face');
2021-03-21 12:49:55 +01:00
}
2021-11-14 17:22:52 +01:00
instance.analyze('End FaceMesh:');
if (instance.config.async) {
if (instance.performance.face) delete instance.performance.face;
if (instance.performance.age) delete instance.performance.age;
if (instance.performance.gender) delete instance.performance.gender;
if (instance.performance.emotion) delete instance.performance.emotion;
2021-03-21 12:49:55 +01:00
}
return faceRes;
};