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
import { FaceMatch } from '../classes/FaceMatch';
import { LabeledFaceDescriptors } from '../classes/LabeledFaceDescriptors';
import { euclideanDistance } from '../euclideanDistance';
import { WithFaceDescriptor } from '../factories/index';
 
export class FaceMatcher {
  private _labeledDescriptors: LabeledFaceDescriptors[];
  private _distanceThreshold: number;
 
  constructor(inputs: LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array | Array<LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array>, distanceThreshold = 0.6) {
    this._distanceThreshold = distanceThreshold;
    const inputArray = Array.isArray(inputs) ? inputs : [inputs];
    if (!inputArray.length) throw new Error('FaceRecognizer.constructor - expected atleast one input');
    let count = 1;
    const createUniqueLabel = () => `person ${count++}`;
    this._labeledDescriptors = inputArray.map((desc) => {
      if (desc instanceof LabeledFaceDescriptors) return desc;
      if (desc instanceof Float32Array) return new LabeledFaceDescriptors(createUniqueLabel(), [desc]);
      if (desc.descriptor && desc.descriptor instanceof Float32Array) return new LabeledFaceDescriptors(createUniqueLabel(), [desc.descriptor]);
      throw new Error('FaceRecognizer.constructor - expected inputs to be of type LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array | Array<LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array>');
    });
  }
 
  public get labeledDescriptors(): LabeledFaceDescriptors[] { return this._labeledDescriptors; }
 
  public get distanceThreshold(): number { return this._distanceThreshold; }
 
  public computeMeanDistance(queryDescriptor: Float32Array, descriptors: Float32Array[]): number {
    return descriptors
      .map((d) => euclideanDistance(d, queryDescriptor))
      .reduce((d1, d2) => d1 + d2, 0) / (descriptors.length || 1);
  }
 
  public matchDescriptor(queryDescriptor: Float32Array): FaceMatch {
    return this.labeledDescriptors
      .map(({ descriptors, label }) => new FaceMatch(label, this.computeMeanDistance(queryDescriptor, descriptors)))
      .reduce((best, curr) => (best.distance < curr.distance ? best : curr));
  }
 
  public findBestMatch(queryDescriptor: Float32Array): FaceMatch {
    const bestMatch = this.matchDescriptor(queryDescriptor);
    return (bestMatch.distance < this._distanceThreshold) ? bestMatch : new FaceMatch('unknown', bestMatch.distance);
  }
 
  public toJSON(): any {
    return {
      distanceThreshold: this._distanceThreshold,
      labeledDescriptors: this._labeledDescriptors.map((ld) => ld.toJSON()),
    };
  }
 
  public static fromJSON(json: any): FaceMatcher {
    const labeledDescriptors = json.labeledDescriptors.map((ld: any) => LabeledFaceDescriptors.fromJSON(ld));
    return new FaceMatcher(labeledDescriptors, json.distanceThreshold);
  }
}