2021-03-21 12:49:55 +01:00
|
|
|
import { log } from '../helpers';
|
2020-11-18 14:26:28 +01:00
|
|
|
import * as tf from '../../dist/tfjs.esm.js';
|
2021-02-13 15:16:41 +01:00
|
|
|
import * as blazeface from './blazeface';
|
|
|
|
import * as facepipeline from './facepipeline';
|
|
|
|
import * as coords from './coords';
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2021-02-08 17:39:09 +01:00
|
|
|
export class MediaPipeFaceMesh {
|
|
|
|
facePipeline: any;
|
|
|
|
config: any;
|
|
|
|
|
2020-10-12 01:22:43 +02:00
|
|
|
constructor(blazeFace, blazeMeshModel, irisModel, config) {
|
2021-03-11 16:26:14 +01:00
|
|
|
this.facePipeline = new facepipeline.Pipeline(blazeFace, blazeMeshModel, irisModel);
|
2020-11-17 23:42:44 +01:00
|
|
|
this.config = config;
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
async estimateFaces(input, config): Promise<{ confidence, boxConfidence, faceConfidence, box, mesh, boxRaw, meshRaw, annotations, image }[]> {
|
2020-12-10 20:47:53 +01:00
|
|
|
const predictions = await this.facePipeline.predict(input, config);
|
2021-03-14 04:31:09 +01:00
|
|
|
const results: Array<{ confidence, boxConfidence, faceConfidence, box, mesh, boxRaw, meshRaw, annotations, image }> = [];
|
2020-10-13 04:01:35 +02:00
|
|
|
for (const prediction of (predictions || [])) {
|
2020-12-10 20:47:53 +01:00
|
|
|
if (prediction.isDisposedInternal) continue; // guard against disposed tensors on long running operations such as pause in middle of processing
|
2021-03-12 18:54:08 +01:00
|
|
|
const mesh = prediction.coords ? prediction.coords.arraySync() : [];
|
|
|
|
const meshRaw = mesh.map((pt) => [
|
2021-03-28 12:19:25 +02:00
|
|
|
pt[0] / input.shape[2],
|
|
|
|
pt[1] / input.shape[1],
|
2021-03-12 18:54:08 +01:00
|
|
|
pt[2] / this.facePipeline.meshSize,
|
|
|
|
]);
|
2020-11-06 19:50:16 +01:00
|
|
|
const annotations = {};
|
|
|
|
if (mesh && mesh.length > 0) {
|
2021-03-03 15:59:04 +01:00
|
|
|
for (const key of Object.keys(coords.MESH_ANNOTATIONS)) annotations[key] = coords.MESH_ANNOTATIONS[key].map((index) => mesh[index]);
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
2020-12-08 16:50:26 +01:00
|
|
|
const box = prediction.box ? [
|
|
|
|
Math.max(0, prediction.box.startPoint[0]),
|
|
|
|
Math.max(0, prediction.box.startPoint[1]),
|
2021-03-27 09:50:33 +01:00
|
|
|
Math.min(input.shape[2], prediction.box.endPoint[0]) - Math.max(0, prediction.box.startPoint[0]),
|
|
|
|
Math.min(input.shape[1], prediction.box.endPoint[1]) - Math.max(0, prediction.box.startPoint[1]),
|
2020-12-08 16:50:26 +01:00
|
|
|
] : 0;
|
2021-03-12 18:54:08 +01:00
|
|
|
const boxRaw = prediction.box ? [
|
2021-03-17 19:35:11 +01:00
|
|
|
prediction.box.startPoint[0] / input.shape[2],
|
|
|
|
prediction.box.startPoint[1] / input.shape[1],
|
|
|
|
(prediction.box.endPoint[0] - prediction.box.startPoint[0]) / input.shape[2],
|
|
|
|
(prediction.box.endPoint[1] - prediction.box.startPoint[1]) / input.shape[1],
|
2021-03-12 18:54:08 +01:00
|
|
|
] : [];
|
2021-03-03 15:59:04 +01:00
|
|
|
results.push({
|
2021-04-01 15:24:56 +02:00
|
|
|
confidence: Math.round(100 * prediction.faceConfidence || 100 * prediction.boxConfidence || 0) / 100,
|
|
|
|
boxConfidence: Math.round(100 * prediction.boxConfidence) / 100,
|
|
|
|
faceConfidence: Math.round(100 * prediction.faceConfidence) / 100,
|
2021-03-03 15:59:04 +01:00
|
|
|
box,
|
|
|
|
boxRaw,
|
2021-03-17 19:35:11 +01:00
|
|
|
mesh,
|
2021-03-03 15:59:04 +01:00
|
|
|
meshRaw,
|
|
|
|
annotations,
|
2021-03-13 17:26:53 +01:00
|
|
|
image: prediction.image ? prediction.image.clone() : null,
|
2021-03-03 15:59:04 +01:00
|
|
|
});
|
2020-10-17 16:06:02 +02:00
|
|
|
if (prediction.coords) prediction.coords.dispose();
|
|
|
|
if (prediction.image) prediction.image.dispose();
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
}
|
2020-10-13 04:01:35 +02:00
|
|
|
|
2020-11-24 04:55:01 +01:00
|
|
|
let faceModels = [null, null, null];
|
2021-03-26 23:50:19 +01:00
|
|
|
export async function load(config): Promise<MediaPipeFaceMesh> {
|
2021-02-08 18:47:38 +01:00
|
|
|
// @ts-ignore
|
2020-11-24 04:55:01 +01:00
|
|
|
faceModels = await Promise.all([
|
|
|
|
(!faceModels[0] && config.face.enabled) ? blazeface.load(config) : null,
|
|
|
|
(!faceModels[1] && config.face.mesh.enabled) ? tf.loadGraphModel(config.face.mesh.modelPath, { fromTFHub: config.face.mesh.modelPath.includes('tfhub.dev') }) : null,
|
|
|
|
(!faceModels[2] && config.face.iris.enabled) ? tf.loadGraphModel(config.face.iris.modelPath, { fromTFHub: config.face.iris.modelPath.includes('tfhub.dev') }) : null,
|
2020-10-13 04:01:35 +02:00
|
|
|
]);
|
2020-11-24 04:55:01 +01:00
|
|
|
const faceMesh = new MediaPipeFaceMesh(faceModels[0], faceModels[1], faceModels[2], config);
|
2021-03-02 17:27:42 +01:00
|
|
|
if (config.face.mesh.enabled && config.debug) log(`load model: ${config.face.mesh.modelPath.match(/\/(.*)\./)[1]}`);
|
|
|
|
if (config.face.iris.enabled && config.debug) log(`load model: ${config.face.iris.modelPath.match(/\/(.*)\./)[1]}`);
|
2020-10-13 04:01:35 +02:00
|
|
|
return faceMesh;
|
|
|
|
}
|
|
|
|
|
2021-03-25 13:43:51 +01:00
|
|
|
export const triangulation = coords.TRI468;
|
2021-03-29 21:59:16 +02:00
|
|
|
export const uvmap = coords.UV468;
|