human/src/face/blazeface.ts

106 lines
4.6 KiB
TypeScript
Raw Normal View History

2021-09-28 18:01:48 +02:00
/**
* BlazeFace, FaceMesh & Iris model implementation
* See `facemesh.ts` for entry point
*/
2022-01-17 17:03:21 +01:00
import { log } from '../util/util';
2021-09-28 18:01:48 +02:00
import * as tf from '../../dist/tfjs.esm.js';
import * as util from './facemeshutil';
2022-01-16 15:49:55 +01:00
import { loadModel } from '../tfjs/load';
2021-11-17 02:16:49 +01:00
import { constants } from '../tfjs/constants';
2021-09-28 18:01:48 +02:00
import type { Config } from '../config';
import type { Tensor, GraphModel } from '../tfjs/types';
import { env } from '../util/env';
2021-11-05 16:28:06 +01:00
import type { Point } from '../result';
2021-09-28 18:01:48 +02:00
const keypointsCount = 6;
2021-12-27 16:59:56 +01:00
const faceBoxScaleFactor = 1.2;
2021-09-28 18:01:48 +02:00
let model: GraphModel | null;
let anchors: Tensor | null = null;
let inputSize = 0;
2021-11-17 00:31:07 +01:00
let inputSizeT: Tensor | null = null;
2021-09-28 18:01:48 +02:00
2021-11-23 16:40:40 +01:00
type DetectBox = { startPoint: Point, endPoint: Point, landmarks: Array<Point>, confidence: number };
2021-09-28 18:01:48 +02:00
export const size = () => inputSize;
export async function load(config: Config): Promise<GraphModel> {
if (env.initial) model = null;
2022-01-17 17:03:21 +01:00
if (!model) model = await loadModel(config.face.detector?.modelPath);
else if (config.debug) log('cached model:', model['modelUrl']);
2021-09-28 18:01:48 +02:00
inputSize = model.inputs[0].shape ? model.inputs[0].shape[2] : 0;
2021-11-17 00:31:07 +01:00
inputSizeT = tf.scalar(inputSize, 'int32') as Tensor;
anchors = tf.tensor2d(util.generateAnchors(inputSize)) as Tensor;
2021-09-28 18:01:48 +02:00
return model;
}
2021-12-28 17:39:54 +01:00
function decodeBounds(boxOutputs: Tensor) {
2021-11-05 16:28:06 +01:00
const t: Record<string, Tensor> = {};
t.boxStarts = tf.slice(boxOutputs, [0, 1], [-1, 2]);
t.centers = tf.add(t.boxStarts, anchors);
t.boxSizes = tf.slice(boxOutputs, [0, 3], [-1, 2]);
2021-11-17 00:31:07 +01:00
t.boxSizesNormalized = tf.div(t.boxSizes, inputSizeT);
t.centersNormalized = tf.div(t.centers, inputSizeT);
t.halfBoxSize = tf.div(t.boxSizesNormalized, constants.tf2);
2021-11-05 16:28:06 +01:00
t.starts = tf.sub(t.centersNormalized, t.halfBoxSize);
t.ends = tf.add(t.centersNormalized, t.halfBoxSize);
2021-11-17 00:31:07 +01:00
t.startNormalized = tf.mul(t.starts, inputSizeT);
t.endNormalized = tf.mul(t.ends, inputSizeT);
2021-11-05 16:28:06 +01:00
const boxes = tf.concat2d([t.startNormalized, t.endNormalized], 1);
Object.keys(t).forEach((tensor) => tf.dispose(t[tensor]));
return boxes;
2021-09-28 18:01:48 +02:00
}
export async function getBoxes(inputImage: Tensor, config: Config) {
// sanity check on input
2021-12-27 16:59:56 +01:00
if ((!inputImage) || (inputImage['isDisposedInternal']) || (inputImage.shape.length !== 4) || (inputImage.shape[1] < 1) || (inputImage.shape[2] < 1)) return [];
2021-11-05 16:28:06 +01:00
const t: Record<string, Tensor> = {};
t.resized = tf.image.resizeBilinear(inputImage, [inputSize, inputSize]);
2021-11-17 00:31:07 +01:00
t.div = tf.div(t.resized, constants.tf127);
t.normalized = tf.sub(t.div, constants.tf05);
2021-11-05 16:28:06 +01:00
const res = model?.execute(t.normalized) as Tensor[];
if (Array.isArray(res)) { // are we using tfhub or pinto converted model?
const sorted = res.sort((a, b) => a.size - b.size);
t.concat384 = tf.concat([sorted[0], sorted[2]], 2); // dim: 384, 1 + 16
t.concat512 = tf.concat([sorted[1], sorted[3]], 2); // dim: 512, 1 + 16
t.concat = tf.concat([t.concat512, t.concat384], 1);
t.batch = tf.squeeze(t.concat, 0);
} else {
t.batch = tf.squeeze(res); // when using tfhub model
}
tf.dispose(res);
t.boxes = decodeBounds(t.batch);
t.logits = tf.slice(t.batch, [0, 0], [-1, 1]);
t.sigmoid = tf.sigmoid(t.logits);
t.scores = tf.squeeze(t.sigmoid);
t.nms = await tf.image.nonMaxSuppressionAsync(t.boxes, t.scores, (config.face.detector?.maxDetected || 0), (config.face.detector?.iouThreshold || 0), (config.face.detector?.minConfidence || 0));
const nms = await t.nms.array() as number[];
2021-11-23 16:40:40 +01:00
const boxes: Array<DetectBox> = [];
2021-11-05 16:28:06 +01:00
const scores = await t.scores.data();
2021-09-28 18:01:48 +02:00
for (let i = 0; i < nms.length; i++) {
2021-11-05 16:28:06 +01:00
const confidence = scores[nms[i]];
2021-09-28 18:01:48 +02:00
if (confidence > (config.face.detector?.minConfidence || 0)) {
2021-11-05 16:28:06 +01:00
const b: Record<string, Tensor> = {};
b.bbox = tf.slice(t.boxes, [nms[i], 0], [1, -1]);
b.slice = tf.slice(t.batch, [nms[i], keypointsCount - 1], [1, -1]);
b.squeeze = tf.squeeze(b.slice);
b.landmarks = tf.reshape(b.squeeze, [keypointsCount, -1]);
2021-11-16 19:07:44 +01:00
const points = await b.bbox.data();
2021-12-27 16:59:56 +01:00
const rawBox = {
2021-11-23 16:40:40 +01:00
startPoint: [points[0], points[1]] as Point,
endPoint: [points[2], points[3]] as Point,
2021-11-05 16:28:06 +01:00
landmarks: (await b.landmarks.array()) as Point[],
confidence,
2021-12-27 16:59:56 +01:00
};
const scaledBox = util.scaleBoxCoordinates(rawBox, [(inputImage.shape[2] || 0) / inputSize, (inputImage.shape[1] || 0) / inputSize]);
2021-12-28 15:40:32 +01:00
const enlargedBox = util.enlargeBox(scaledBox, config.face['scale'] || faceBoxScaleFactor);
2021-12-27 16:59:56 +01:00
const squaredBox = util.squarifyBox(enlargedBox);
boxes.push(squaredBox);
2021-11-05 16:28:06 +01:00
Object.keys(b).forEach((tensor) => tf.dispose(b[tensor]));
2021-09-28 18:01:48 +02:00
}
}
2021-11-05 16:28:06 +01:00
Object.keys(t).forEach((tensor) => tf.dispose(t[tensor]));
2021-12-27 16:59:56 +01:00
return boxes;
2021-09-28 18:01:48 +02:00
}