async getBoundingBoxes()

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
    };
  }