human/src/face/face.ts

133 lines
5.5 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
*/
2021-09-27 19:58:13 +02:00
import { log, now } from '../util/util';
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';
import type { FaceResult } from '../result';
import type { Tensor } from '../tfjs/types';
2021-09-28 18:01:48 +02:00
import { calculateFaceAngle } from './angles';
2021-03-21 12:49:55 +01:00
2021-09-12 05:54:35 +02:00
export const detectFace = async (parent /* 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;
let gearRes;
2021-03-21 12:49:55 +01:00
let genderRes;
let emotionRes;
let embeddingRes;
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);
parent.performance.face = 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-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;
}
const rotation = calculateFaceAngle(faces[i], [input.shape[2], input.shape[1]]);
2021-03-21 12:49:55 +01:00
// run emotion, inherits face from blazeface
parent.analyze('Start Emotion:');
if (parent.config.async) {
2021-08-12 00:59:02 +02:00
emotionRes = parent.config.face.emotion.enabled ? emotion.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : {};
2021-03-21 12:49:55 +01:00
} else {
parent.state = 'run:emotion';
timeStamp = now();
2021-08-12 00:59:02 +02:00
emotionRes = parent.config.face.emotion.enabled ? await emotion.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : {};
parent.performance.emotion = Math.trunc(now() - timeStamp);
2021-03-21 12:49:55 +01:00
}
parent.analyze('End Emotion:');
// 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) : {};
} 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) : {};
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-08-12 00:59:02 +02:00
descRes = parent.config.face.description.enabled ? faceres.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : [];
2021-03-21 19:18:51 +01:00
} else {
parent.state = 'run:description';
timeStamp = now();
2021-08-12 00:59:02 +02:00
descRes = parent.config.face.description.enabled ? await faceres.predict(faces[i].tensor || tf.tensor([]), parent.config, i, faces.length) : [];
parent.performance.embedding = 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) {
[ageRes, genderRes, emotionRes, embeddingRes, descRes, gearRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes, descRes, gearRes]);
2021-03-21 12:49:55 +01:00
}
parent.analyze('Finish Face:');
// calculate iris distance
// iris: array[ center, left, top, right, bottom]
if (!parent.config.face.iris.enabled && faces[i]?.annotations?.leftEyeIris && faces[i]?.annotations?.rightEyeIris) {
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
const tensor = parent.config.face.detector.return ? tf.squeeze(faces[i].tensor) : null;
// 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({
...faces[i],
id: i,
2021-04-25 00:43:59 +02:00
age: descRes.age,
gender: descRes.gender,
2021-06-01 14:59:09 +02:00
genderScore: descRes.genderScore,
2021-04-25 00:43:59 +02:00
embedding: descRes.descriptor,
2021-03-21 12:49:55 +01:00
emotion: emotionRes,
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) {
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;
};