in blazeface/src/face.ts [228:325]
async getBoundingBoxes(
inputImage: tf.Tensor4D, returnTensors: boolean,
annotateBoxes = true): Promise<{
boxes: Array<BlazeFacePrediction|Box>,
scaleFactor: tf.Tensor|[number, number]
}> {
const [detectedOutputs, boxes, scores] = tf.tidy((): [
tf.Tensor2D, tf.Tensor2D, tf.Tensor1D
] => {
const resizedImage = tf.image.resizeBilinear(inputImage,
[this.width, this.height]);
const normalizedImage = tf.mul(tf.sub(tf.div(resizedImage, 255), 0.5), 2);
// [1, 897, 17] 1 = batch, 897 = number of anchors
const batchedPrediction = this.blazeFaceModel.predict(normalizedImage);
const prediction = tf.squeeze((batchedPrediction as tf.Tensor3D));
const decodedBounds =
decodeBounds(prediction as tf.Tensor2D, this.anchors, this.inputSize);
const logits = tf.slice(prediction as tf.Tensor2D, [0, 0], [-1, 1]);
const scores = tf.squeeze(tf.sigmoid(logits));
return [prediction as tf.Tensor2D, decodedBounds, scores as tf.Tensor1D];
});
// TODO: Once tf.image.nonMaxSuppression includes a flag to suppress console
// warnings for not using async version, pass that flag in.
const savedConsoleWarnFn = console.warn;
console.warn = () => {};
const boxIndicesTensor = tf.image.nonMaxSuppression(
boxes, scores, this.maxFaces, this.iouThreshold, this.scoreThreshold);
console.warn = savedConsoleWarnFn;
const boxIndices = await boxIndicesTensor.array();
boxIndicesTensor.dispose();
let boundingBoxes: tf.Tensor[]|number[][][] = boxIndices.map(
(boxIndex: number) => tf.slice(boxes, [boxIndex, 0], [1, -1]));
if (!returnTensors) {
boundingBoxes = await Promise.all(
boundingBoxes.map(async (boundingBox: tf.Tensor2D) => {
const vals = await boundingBox.array();
boundingBox.dispose();
return vals;
}));
}
const originalHeight = inputImage.shape[1];
const originalWidth = inputImage.shape[2];
let scaleFactor: tf.Tensor|[number, number];
if (returnTensors) {
scaleFactor = tf.div([originalWidth, originalHeight], this.inputSize);
} else {
scaleFactor = [
originalWidth / this.inputSizeData[0],
originalHeight / this.inputSizeData[1]
];
}
const annotatedBoxes = [];
for (let i = 0; i < boundingBoxes.length; i++) {
const boundingBox = boundingBoxes[i] as tf.Tensor2D | number[][];
const annotatedBox = tf.tidy(() => {
const box = boundingBox instanceof tf.Tensor ?
createBox(boundingBox) :
createBox(tf.tensor2d(boundingBox));
if (!annotateBoxes) {
return box;
}
const boxIndex = boxIndices[i];
let anchor;
if (returnTensors) {
anchor = tf.slice(this.anchors, [boxIndex, 0], [1, 2]);
} else {
anchor = this.anchorsData[boxIndex] as [number, number];
}
const landmarks = tf.reshape(tf.squeeze(tf.slice(detectedOutputs,
[boxIndex, NUM_LANDMARKS - 1], [1, -1])), [NUM_LANDMARKS, -1]);
const probability = tf.slice(scores, [boxIndex], [1]);
return {box, landmarks, probability, anchor};
});
annotatedBoxes.push(annotatedBox);
}
boxes.dispose();
scores.dispose();
detectedOutputs.dispose();
return {
boxes: annotatedBoxes as Array<BlazeFacePrediction|Box>,
scaleFactor
};
}