gx
chenyc
2025-06-12 7b72ac13a83764a662159d4a49b7fffb90476ecb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import * as tf from '../../dist/tfjs.esm';
 
import { IDimensions, Point } from '../classes/index';
import { FaceLandmarks68 } from '../classes/FaceLandmarks68';
import { NetInput, TNetInput, toNetInput } from '../dom/index';
import { FaceFeatureExtractorParams, TinyFaceFeatureExtractorParams } from '../faceFeatureExtractor/types';
import { FaceProcessor } from '../faceProcessor/FaceProcessor';
import { isEven } from '../utils/index';
 
export abstract class FaceLandmark68NetBase<
  TExtractorParams extends FaceFeatureExtractorParams | TinyFaceFeatureExtractorParams
>
  extends FaceProcessor<TExtractorParams> {
  public postProcess(output: tf.Tensor2D, inputSize: number, originalDimensions: IDimensions[]): tf.Tensor2D {
    const inputDimensions = originalDimensions.map(({ width, height }) => {
      const scale = inputSize / Math.max(height, width);
      return {
        width: width * scale,
        height: height * scale,
      };
    });
 
    const batchSize = inputDimensions.length;
 
    return tf.tidy(() => {
      const createInterleavedTensor = (fillX: number, fillY: number) => tf.stack([tf.fill([68], fillX, 'float32'), tf.fill([68], fillY, 'float32')], 1).as2D(1, 136).as1D();
 
      // eslint-disable-next-line no-unused-vars
      const getPadding = (batchIdx: number, cond: (w: number, h: number) => boolean): number => {
        const { width, height } = inputDimensions[batchIdx];
        return cond(width, height) ? Math.abs(width - height) / 2 : 0;
      };
 
      const getPaddingX = (batchIdx: number) => getPadding(batchIdx, (w, h) => w < h);
      const getPaddingY = (batchIdx: number) => getPadding(batchIdx, (w, h) => h < w);
 
      const landmarkTensors = output
        .mul(tf.fill([batchSize, 136], inputSize, 'float32'))
        .sub(tf.stack(Array.from(Array(batchSize), (_, batchIdx) => createInterleavedTensor(
          getPaddingX(batchIdx),
          getPaddingY(batchIdx),
        ))))
        .div(tf.stack(Array.from(Array(batchSize), (_, batchIdx) => createInterleavedTensor(
          inputDimensions[batchIdx].width,
          inputDimensions[batchIdx].height,
        ))));
 
      return landmarkTensors as tf.Tensor2D;
    });
  }
 
  public forwardInput(input: NetInput): tf.Tensor2D {
    return tf.tidy(() => {
      const out = this.runNet(input);
      return this.postProcess(
        out,
        input.inputSize as number,
        input.inputDimensions.map(([height, width]) => ({ height, width })),
      );
    });
  }
 
  public async forward(input: TNetInput): Promise<tf.Tensor2D> {
    return this.forwardInput(await toNetInput(input));
  }
 
  public async detectLandmarks(input: TNetInput): Promise<FaceLandmarks68 | FaceLandmarks68[]> {
    const netInput = await toNetInput(input);
    const landmarkTensors = tf.tidy(
      () => tf.unstack(this.forwardInput(netInput)),
    );
 
    const landmarksForBatch = await Promise.all(landmarkTensors.map(
      async (landmarkTensor, batchIdx) => {
        const landmarksArray = Array.from(landmarkTensor.dataSync());
        const xCoords = landmarksArray.filter((_, i) => isEven(i));
        const yCoords = landmarksArray.filter((_, i) => !isEven(i));
 
        return new FaceLandmarks68(
          Array(68).fill(0).map((_, i) => new Point(xCoords[i] as number, yCoords[i] as number)),
          {
            height: netInput.getInputHeight(batchIdx),
            width: netInput.getInputWidth(batchIdx),
          },
        );
      },
    ));
 
    landmarkTensors.forEach((t) => t.dispose());
 
    return netInput.isBatchInput ? landmarksForBatch as FaceLandmarks68[] : landmarksForBatch[0] as FaceLandmarks68;
  }
 
  protected getClassifierChannelsOut(): number {
    return 136;
  }
}