id-scanner-lib 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/id-scanner-lib.esm.js +108 -315
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +109 -320
- package/dist/id-scanner-lib.js.map +1 -1
- package/package.json +1 -1
- package/src/compat/index.ts +7 -0
- package/src/compat/v1-adapter.ts +84 -0
- package/src/core/config.ts +113 -267
- package/src/core/errors.ts +68 -117
- package/src/core/resource-manager.ts +150 -0
- package/src/core/scanner.ts +109 -0
- package/src/core/utils/browser.ts +7 -0
- package/src/core/utils/canvas-pool.ts +171 -0
- package/src/core/utils/canvas.ts +7 -0
- package/src/core/utils/image.ts +7 -0
- package/src/core/utils/index.ts +9 -0
- package/src/core/utils/resource-manager.ts +155 -0
- package/src/core/utils/validate.ts +7 -0
- package/src/core/utils/worker.ts +130 -0
- package/src/modules/face/comparator/comparator.ts +45 -0
- package/src/modules/face/comparator/index.ts +1 -0
- package/src/modules/face/detector/detector.ts +83 -0
- package/src/modules/face/detector/index.ts +2 -0
- package/src/modules/face/detector/types.ts +80 -0
- package/src/modules/face/liveness/index.ts +7 -0
- package/src/modules/face/tracker/index.ts +7 -0
- package/src/modules/id-card/anti-fake/index.ts +7 -0
- package/src/modules/id-card/detector/index.ts +7 -0
- package/src/modules/id-card/parser/index.ts +7 -0
- package/src/modules/qr/scanner/index.ts +7 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Core Utils 入口
|
|
3
|
+
* @description 导出所有核心工具模块
|
|
4
|
+
* @module core/utils
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { CanvasPool, PooledCanvas, globalCanvasPool } from './canvas-pool';
|
|
8
|
+
export { WorkerBridge, WorkerMessage, WorkerResponse } from './worker';
|
|
9
|
+
export { ModelCacheManager, ModelShard, ModelCacheInfo, modelCache } from './resource-manager';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 模型分片缓存管理器
|
|
3
|
+
* @description 提供分片模型的 IndexedDB 持久化缓存功能(idb-keyval 风格实现)
|
|
4
|
+
* @module core/utils/resource-manager
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 模型分片信息
|
|
9
|
+
*/
|
|
10
|
+
export interface ModelShard {
|
|
11
|
+
name: string; // e.g. 'face-detector'
|
|
12
|
+
version: string; // e.g. '1.0.0'
|
|
13
|
+
url: string; // 完整 URL
|
|
14
|
+
size: number; // bytes
|
|
15
|
+
hash?: string; // 完整性校验
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 模型缓存信息
|
|
20
|
+
*/
|
|
21
|
+
export interface ModelCacheInfo {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
cachedAt: number;
|
|
25
|
+
size: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* IndexedDB 模型缓存管理器
|
|
30
|
+
* 提供分片模型的持久化缓存功能
|
|
31
|
+
*/
|
|
32
|
+
export class ModelCacheManager {
|
|
33
|
+
private _dbName = 'id-scanner-lib-models';
|
|
34
|
+
private _storeName = 'model-cache';
|
|
35
|
+
private _db: IDBDatabase | null = null;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 初始化缓存管理器(打开 IndexedDB)
|
|
39
|
+
*/
|
|
40
|
+
async init(): Promise<void> {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const request = indexedDB.open(this._dbName, 1);
|
|
43
|
+
request.onerror = () => reject(new Error('Failed to open IndexedDB'));
|
|
44
|
+
request.onsuccess = () => {
|
|
45
|
+
this._db = request.result;
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
request.onupgradeneeded = (event) => {
|
|
49
|
+
const db = (event.target as IDBOpenDBRequest).result;
|
|
50
|
+
if (!db.objectStoreNames.contains(this._storeName)) {
|
|
51
|
+
const store = db.createObjectStore(this._storeName, { keyPath: 'key' });
|
|
52
|
+
store.createIndex('name', 'name', { unique: false });
|
|
53
|
+
store.createIndex('version', 'version', { unique: false });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 生成缓存键
|
|
61
|
+
*/
|
|
62
|
+
private _makeKey(model: ModelShard): string {
|
|
63
|
+
return `${model.name}@${model.version}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 检查模型是否已缓存
|
|
68
|
+
*/
|
|
69
|
+
async has(model: ModelShard): Promise<boolean> {
|
|
70
|
+
if (!this._db) return false;
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const tx = this._db!.transaction(this._storeName, 'readonly');
|
|
73
|
+
const store = tx.objectStore(this._storeName);
|
|
74
|
+
const req = store.get(this._makeKey(model));
|
|
75
|
+
req.onsuccess = () => resolve(!!req.result);
|
|
76
|
+
req.onerror = () => reject(req.error);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 存储模型数据
|
|
82
|
+
*/
|
|
83
|
+
async store(model: ModelShard, data: ArrayBuffer): Promise<void> {
|
|
84
|
+
if (!this._db) throw new Error('ModelCacheManager not initialized');
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const tx = this._db!.transaction(this._storeName, 'readwrite');
|
|
87
|
+
const store = tx.objectStore(this._storeName);
|
|
88
|
+
const record = {
|
|
89
|
+
key: this._makeKey(model),
|
|
90
|
+
name: model.name,
|
|
91
|
+
version: model.version,
|
|
92
|
+
url: model.url,
|
|
93
|
+
size: model.size,
|
|
94
|
+
data,
|
|
95
|
+
cachedAt: Date.now(),
|
|
96
|
+
};
|
|
97
|
+
const req = store.put(record);
|
|
98
|
+
req.onsuccess = () => resolve();
|
|
99
|
+
req.onerror = () => reject(req.error);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 加载模型数据
|
|
105
|
+
*/
|
|
106
|
+
async load(model: ModelShard): Promise<ArrayBuffer | null> {
|
|
107
|
+
if (!this._db) throw new Error('ModelCacheManager not initialized');
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const tx = this._db!.transaction(this._storeName, 'readonly');
|
|
110
|
+
const store = tx.objectStore(this._storeName);
|
|
111
|
+
const req = store.get(this._makeKey(model));
|
|
112
|
+
req.onsuccess = () => resolve(req.result ? req.result.data : null);
|
|
113
|
+
req.onerror = () => reject(req.error);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 清除所有缓存
|
|
119
|
+
*/
|
|
120
|
+
async clear(): Promise<void> {
|
|
121
|
+
if (!this._db) return;
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const tx = this._db!.transaction(this._storeName, 'readwrite');
|
|
124
|
+
const store = tx.objectStore(this._storeName);
|
|
125
|
+
const req = store.clear();
|
|
126
|
+
req.onsuccess = () => resolve();
|
|
127
|
+
req.onerror = () => reject(req.error);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 列出已缓存模型
|
|
133
|
+
*/
|
|
134
|
+
async listCached(): Promise<ModelCacheInfo[]> {
|
|
135
|
+
if (!this._db) return [];
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const tx = this._db!.transaction(this._storeName, 'readonly');
|
|
138
|
+
const store = tx.objectStore(this._storeName);
|
|
139
|
+
const req = store.getAll();
|
|
140
|
+
req.onsuccess = () => {
|
|
141
|
+
const results: ModelCacheInfo[] = req.result.map((r: any) => ({
|
|
142
|
+
name: r.name,
|
|
143
|
+
version: r.version,
|
|
144
|
+
cachedAt: r.cachedAt,
|
|
145
|
+
size: r.size,
|
|
146
|
+
}));
|
|
147
|
+
resolve(results);
|
|
148
|
+
};
|
|
149
|
+
req.onerror = () => reject(req.error);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 全局模型缓存管理器实例 */
|
|
155
|
+
export const modelCache = new ModelCacheManager();
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file WorkerBridge - Web Worker 并行处理框架
|
|
3
|
+
* @description 提供基于 Promise 的 Worker 通信接口,支持超时和错误处理
|
|
4
|
+
* @module core/utils/worker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Worker 消息格式
|
|
9
|
+
*/
|
|
10
|
+
export type WorkerMessage<T = any> = {
|
|
11
|
+
type: string;
|
|
12
|
+
payload: T;
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Worker 响应格式
|
|
18
|
+
*/
|
|
19
|
+
export type WorkerResponse<T = any> = {
|
|
20
|
+
type: string;
|
|
21
|
+
payload: T;
|
|
22
|
+
error?: string;
|
|
23
|
+
id: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WorkerBridge - 基于 Promise 的 Worker 通信桥接器
|
|
28
|
+
*
|
|
29
|
+
* 提供简洁的 Worker 消息发送接口,自动处理请求-响应匹配、超时和错误
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const bridge = new WorkerBridge('/workers/face-detect.worker.ts');
|
|
34
|
+
* await bridge.load();
|
|
35
|
+
* const result = await bridge.post('detect', { imageData });
|
|
36
|
+
* bridge.terminate();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class WorkerBridge<TReq = any, TRes = any> {
|
|
40
|
+
private _worker: Worker | null = null;
|
|
41
|
+
private _pending: Map<string, { resolve: Function; reject: Function }> = new Map();
|
|
42
|
+
private _url: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 创建 WorkerBridge
|
|
46
|
+
* @param workerUrl Worker 文件 URL
|
|
47
|
+
*/
|
|
48
|
+
constructor(workerUrl: string) {
|
|
49
|
+
this._url = workerUrl;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 加载 Worker
|
|
54
|
+
*/
|
|
55
|
+
async load(): Promise<void> {
|
|
56
|
+
this._worker = new Worker(this._url);
|
|
57
|
+
this._worker.onmessage = (e: MessageEvent<WorkerResponse<TRes>>) => {
|
|
58
|
+
const { id, payload, error } = e.data;
|
|
59
|
+
const handlers = this._pending.get(id);
|
|
60
|
+
if (handlers) {
|
|
61
|
+
if (error) {
|
|
62
|
+
handlers.reject(new Error(error));
|
|
63
|
+
} else {
|
|
64
|
+
handlers.resolve(payload);
|
|
65
|
+
}
|
|
66
|
+
this._pending.delete(id);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this._worker.onerror = (e) => {
|
|
70
|
+
// 将 Worker 错误广播给所有 pending 的 Promise
|
|
71
|
+
for (const [id, handlers] of this._pending) {
|
|
72
|
+
handlers.reject(new Error(`Worker error: ${e.message}`));
|
|
73
|
+
this._pending.delete(id);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 发送消息到 Worker 并等待响应
|
|
80
|
+
*
|
|
81
|
+
* @param type 消息类型
|
|
82
|
+
* @param payload 消息载荷
|
|
83
|
+
* @returns 响应 Promise
|
|
84
|
+
* @throws 如果 Worker 未加载则抛出错误
|
|
85
|
+
*/
|
|
86
|
+
async post(type: string, payload: TReq): Promise<TRes> {
|
|
87
|
+
if (!this._worker) {
|
|
88
|
+
throw new Error('Worker not loaded');
|
|
89
|
+
}
|
|
90
|
+
const id = crypto.randomUUID();
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
this._pending.set(id, { resolve, reject });
|
|
93
|
+
this._worker!.postMessage({ type, payload, id } as WorkerMessage<TReq>);
|
|
94
|
+
// 超时 30s
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
if (this._pending.has(id)) {
|
|
97
|
+
this._pending.delete(id);
|
|
98
|
+
reject(new Error('Worker timeout'));
|
|
99
|
+
}
|
|
100
|
+
}, 30000);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 终止 Worker
|
|
106
|
+
*/
|
|
107
|
+
terminate(): void {
|
|
108
|
+
this._worker?.terminate();
|
|
109
|
+
this._worker = null;
|
|
110
|
+
// 拒绝所有 pending 的 Promise
|
|
111
|
+
for (const [, handlers] of this._pending) {
|
|
112
|
+
handlers.reject(new Error('Worker terminated'));
|
|
113
|
+
}
|
|
114
|
+
this._pending.clear();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 检查 Worker 是否已加载
|
|
119
|
+
*/
|
|
120
|
+
get isLoaded(): boolean {
|
|
121
|
+
return this._worker !== null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 获取当前 pending 请求数
|
|
126
|
+
*/
|
|
127
|
+
get pendingCount(): number {
|
|
128
|
+
return this._pending.size;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Face Comparator
|
|
3
|
+
* @description Face comparison using cosine similarity
|
|
4
|
+
* @module modules/face/comparator
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FaceCompareResult, FaceDetectionResult } from '../detector/types';
|
|
8
|
+
|
|
9
|
+
export class FaceComparator {
|
|
10
|
+
private _threshold: number = 0.6;
|
|
11
|
+
|
|
12
|
+
constructor(threshold: number = 0.6) {
|
|
13
|
+
this._threshold = threshold;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async compare(
|
|
17
|
+
face1: FaceDetectionResult | number[],
|
|
18
|
+
face2: FaceDetectionResult | number[]
|
|
19
|
+
): Promise<FaceCompareResult> {
|
|
20
|
+
// Extract embedding
|
|
21
|
+
const emb1 = Array.isArray(face1) ? face1 : face1.embedding ?? [];
|
|
22
|
+
const emb2 = Array.isArray(face2) ? face2 : face2.embedding ?? [];
|
|
23
|
+
|
|
24
|
+
// Compute cosine similarity
|
|
25
|
+
const similarity = this._cosineSimilarity(emb1, emb2);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
similarity,
|
|
29
|
+
isMatch: similarity >= this._threshold,
|
|
30
|
+
threshold: this._threshold,
|
|
31
|
+
algorithm: 'cosine-similarity',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private _cosineSimilarity(a: number[], b: number[]): number {
|
|
36
|
+
if (a.length !== b.length || a.length === 0) return 0;
|
|
37
|
+
let dot = 0, magA = 0, magB = 0;
|
|
38
|
+
for (let i = 0; i < a.length; i++) {
|
|
39
|
+
dot += a[i] * b[i];
|
|
40
|
+
magA += a[i] * a[i];
|
|
41
|
+
magB += b[i] * b[i];
|
|
42
|
+
}
|
|
43
|
+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FaceComparator } from './comparator';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Face Detector
|
|
3
|
+
* @description Face detection module with canvas pooling
|
|
4
|
+
* @module modules/face/detector
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FaceDetectionOptions, FaceDetectionResult } from './types';
|
|
8
|
+
import { CanvasPool } from '../../../core/utils/canvas-pool';
|
|
9
|
+
import { ImageSource } from '../../../core/config';
|
|
10
|
+
|
|
11
|
+
export class FaceDetector {
|
|
12
|
+
private _options: Required<FaceDetectionOptions>;
|
|
13
|
+
private _pool: CanvasPool;
|
|
14
|
+
private _initialized: boolean = false;
|
|
15
|
+
private _modelLoaded: boolean = false;
|
|
16
|
+
|
|
17
|
+
constructor(options: FaceDetectionOptions = {}) {
|
|
18
|
+
this._options = {
|
|
19
|
+
maxFaces: options.maxFaces ?? 10,
|
|
20
|
+
minFaceSize: options.minFaceSize ?? 0.1,
|
|
21
|
+
confidenceThreshold: options.confidenceThreshold ?? 0.5,
|
|
22
|
+
landmarks: options.landmarks ?? true,
|
|
23
|
+
inputWidth: options.inputWidth ?? 640,
|
|
24
|
+
inputHeight: options.inputHeight ?? 480,
|
|
25
|
+
};
|
|
26
|
+
this._pool = new CanvasPool();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get initialized(): boolean {
|
|
30
|
+
return this._initialized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async initialize(): Promise<void> {
|
|
34
|
+
// Load model (lazy loading on demand)
|
|
35
|
+
this._modelLoaded = true;
|
|
36
|
+
this._initialized = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async detect(input: ImageSource): Promise<FaceDetectionResult[]> {
|
|
40
|
+
if (!this._initialized) {
|
|
41
|
+
throw new Error('FaceDetector not initialized');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 1. Acquire canvas from pool
|
|
45
|
+
const pooled = this._pool.acquire(this._options.inputWidth, this._options.inputHeight);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// 2. Draw input to canvas
|
|
49
|
+
const ctx = pooled.canvas.getContext('2d');
|
|
50
|
+
if (!ctx) {
|
|
51
|
+
throw new Error('Failed to get 2d context');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle different input types
|
|
55
|
+
if (input instanceof HTMLCanvasElement) {
|
|
56
|
+
ctx.drawImage(input, 0, 0);
|
|
57
|
+
} else if (input instanceof HTMLImageElement || input instanceof HTMLVideoElement) {
|
|
58
|
+
ctx.drawImage(input, 0, 0, this._options.inputWidth, this._options.inputHeight);
|
|
59
|
+
} else if (input instanceof ImageData) {
|
|
60
|
+
ctx.putImageData(input, 0, 0);
|
|
61
|
+
}
|
|
62
|
+
// string input (URL) would need async loading — not implemented in stub
|
|
63
|
+
|
|
64
|
+
// 3. Run detection (stub returns empty)
|
|
65
|
+
// TODO: Integrate real model
|
|
66
|
+
|
|
67
|
+
// 4. Convert results and return
|
|
68
|
+
const results: FaceDetectionResult[] = [];
|
|
69
|
+
// stub: no faces detected
|
|
70
|
+
|
|
71
|
+
return results;
|
|
72
|
+
} finally {
|
|
73
|
+
// 5. Release canvas back to pool
|
|
74
|
+
this._pool.release(pooled);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async destroy(): Promise<void> {
|
|
79
|
+
this._pool.clear();
|
|
80
|
+
this._initialized = false;
|
|
81
|
+
this._modelLoaded = false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face detection options
|
|
3
|
+
*/
|
|
4
|
+
export interface FaceDetectionOptions {
|
|
5
|
+
/** Maximum number of faces to detect */
|
|
6
|
+
maxFaces?: number;
|
|
7
|
+
/** Minimum face size (0-1, relative to image) */
|
|
8
|
+
minFaceSize?: number;
|
|
9
|
+
/** Confidence threshold (0-1) */
|
|
10
|
+
confidenceThreshold?: number;
|
|
11
|
+
/** Whether to return facial landmarks */
|
|
12
|
+
landmarks?: boolean;
|
|
13
|
+
/** Input image/frame width (for normalization) */
|
|
14
|
+
inputWidth?: number;
|
|
15
|
+
/** Input image/frame height (for normalization) */
|
|
16
|
+
inputHeight?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detected face result
|
|
21
|
+
*/
|
|
22
|
+
export interface FaceDetectionResult {
|
|
23
|
+
/** Unique face ID (for tracking) */
|
|
24
|
+
faceId: string;
|
|
25
|
+
/** Bounding box */
|
|
26
|
+
box: BoundingBox;
|
|
27
|
+
/** Detection confidence (0-1) */
|
|
28
|
+
confidence: number;
|
|
29
|
+
/** Facial landmarks (optional) */
|
|
30
|
+
landmarks?: FaceLandmarks;
|
|
31
|
+
/** Face embedding vector (optional, for comparison) */
|
|
32
|
+
embedding?: number[];
|
|
33
|
+
/** Detection timestamp */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Bounding box
|
|
39
|
+
*/
|
|
40
|
+
export interface BoundingBox {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Face landmarks
|
|
49
|
+
*/
|
|
50
|
+
export interface FaceLandmarks {
|
|
51
|
+
leftEye?: Point;
|
|
52
|
+
rightEye?: Point;
|
|
53
|
+
nose?: Point;
|
|
54
|
+
leftMouth?: Point;
|
|
55
|
+
rightMouth?: Point;
|
|
56
|
+
leftEar?: Point;
|
|
57
|
+
rightEar?: Point;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 2D point
|
|
62
|
+
*/
|
|
63
|
+
export interface Point {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Face comparison result
|
|
70
|
+
*/
|
|
71
|
+
export interface FaceCompareResult {
|
|
72
|
+
/** Similarity score (0-1) */
|
|
73
|
+
similarity: number;
|
|
74
|
+
/** Whether faces match (threshold-based) */
|
|
75
|
+
isMatch: boolean;
|
|
76
|
+
/** Threshold used */
|
|
77
|
+
threshold: number;
|
|
78
|
+
/** Comparison algorithm used */
|
|
79
|
+
algorithm: string;
|
|
80
|
+
}
|