id-scanner-lib 1.6.7 → 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.
Files changed (48) hide show
  1. package/dist/id-scanner-lib.esm.js +994 -1139
  2. package/dist/id-scanner-lib.esm.js.map +1 -1
  3. package/dist/id-scanner-lib.js +995 -1144
  4. package/dist/id-scanner-lib.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/compat/index.ts +7 -0
  7. package/src/compat/v1-adapter.ts +84 -0
  8. package/src/core/camera-manager.ts +43 -76
  9. package/src/core/camera-stream-manager.ts +318 -0
  10. package/src/core/config.ts +113 -267
  11. package/src/core/errors.ts +68 -117
  12. package/src/core/logger.ts +158 -81
  13. package/src/core/resource-manager.ts +150 -0
  14. package/src/core/scanner.ts +109 -0
  15. package/src/core/utils/browser.ts +7 -0
  16. package/src/core/utils/canvas-pool.ts +171 -0
  17. package/src/core/utils/canvas.ts +7 -0
  18. package/src/core/utils/image.ts +7 -0
  19. package/src/core/utils/index.ts +9 -0
  20. package/src/core/utils/resource-manager.ts +155 -0
  21. package/src/core/utils/validate.ts +7 -0
  22. package/src/core/utils/worker.ts +130 -0
  23. package/src/modules/face/comparator/comparator.ts +45 -0
  24. package/src/modules/face/comparator/index.ts +1 -0
  25. package/src/modules/face/detector/detector.ts +83 -0
  26. package/src/modules/face/detector/index.ts +2 -0
  27. package/src/modules/face/detector/types.ts +80 -0
  28. package/src/modules/face/face-comparator.ts +150 -0
  29. package/src/modules/face/face-detector-options.ts +104 -0
  30. package/src/modules/face/face-detector.ts +121 -376
  31. package/src/modules/face/face-detector.ts.bak +991 -0
  32. package/src/modules/face/face-model-loader.ts +222 -0
  33. package/src/modules/face/face-result-converter.ts +225 -0
  34. package/src/modules/face/face-tracker.ts +207 -0
  35. package/src/modules/face/liveness/index.ts +7 -0
  36. package/src/modules/face/liveness-detector.ts +2 -2
  37. package/src/modules/face/tracker/index.ts +7 -0
  38. package/src/modules/id-card/anti-fake/index.ts +7 -0
  39. package/src/modules/id-card/detector/index.ts +7 -0
  40. package/src/modules/id-card/id-card-text-parser.ts +151 -0
  41. package/src/modules/id-card/ocr-processor.ts +20 -257
  42. package/src/modules/id-card/ocr-worker.ts +2 -183
  43. package/src/modules/id-card/parser/index.ts +7 -0
  44. package/src/modules/qr/scanner/index.ts +7 -0
  45. package/src/utils/canvas-pool.ts +273 -0
  46. package/src/utils/edge-detector.ts +232 -0
  47. package/src/utils/image-processing.ts +92 -419
  48. package/src/utils/index.ts +1 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @file Canvas 对象池
3
+ * @description 提供 OffscreenCanvas/HTMLCanvasElement 的复用机制,减少内存分配和 GC 压力
4
+ * @module core/utils/canvas-pool
5
+ */
6
+
7
+ /**
8
+ * Canvas 池条目
9
+ */
10
+ export interface PooledCanvas {
11
+ canvas: OffscreenCanvas | HTMLCanvasElement;
12
+ width: number;
13
+ height: number;
14
+ inUse: boolean;
15
+ }
16
+
17
+ interface PoolEntry {
18
+ canvas: OffscreenCanvas | HTMLCanvasElement;
19
+ width: number;
20
+ height: number;
21
+ inUse: boolean;
22
+ lastUsed: number;
23
+ }
24
+
25
+ /**
26
+ * Canvas 对象池
27
+ *
28
+ * 复用 Canvas 元素,避免频繁创建和销毁导致的内存抖动
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const pool = new CanvasPool();
33
+ * const acquired = pool.acquire(640, 480);
34
+ * // 使用 canvas 进行绘制...
35
+ * pool.release(acquired);
36
+ * ```
37
+ */
38
+ export class CanvasPool {
39
+ private _pool: PoolEntry[] = [];
40
+ private _maxSize: number = 4;
41
+ private _useOffscreen: boolean;
42
+
43
+ /**
44
+ * 创建 Canvas 池
45
+ * @param maxSize 最大池大小
46
+ * @param useOffscreen 是否使用 OffscreenCanvas
47
+ */
48
+ constructor(maxSize: number = 4, useOffscreen: boolean = true) {
49
+ this._maxSize = maxSize;
50
+ this._useOffscreen = useOffscreen && typeof OffscreenCanvas !== 'undefined';
51
+ }
52
+
53
+ /**
54
+ * 从池中获取 Canvas
55
+ *
56
+ * 1. 查找尺寸匹配的可用 canvas(允许 10% 误差)
57
+ * 2. 无匹配则创建新的
58
+ * 3. 超出 _maxSize 则回收最旧的 inUse=false 的
59
+ * 4. 返回 PooledCanvas,标记 inUse=true
60
+ *
61
+ * @param width 宽度
62
+ * @param height 高度
63
+ */
64
+ acquire(width: number, height: number): PooledCanvas {
65
+ // 1. 查找尺寸匹配的可用 canvas(允许 10% 误差)
66
+ const matched = this._pool.find(p =>
67
+ !p.inUse &&
68
+ Math.abs(p.width - width) / width <= 0.1 &&
69
+ Math.abs(p.height - height) / height <= 0.1
70
+ );
71
+
72
+ if (matched) {
73
+ matched.inUse = true;
74
+ matched.lastUsed = Date.now();
75
+ return {
76
+ canvas: matched.canvas,
77
+ width: matched.width,
78
+ height: matched.height,
79
+ inUse: true,
80
+ };
81
+ }
82
+
83
+ // 2. 无匹配则创建新的
84
+ let canvas: OffscreenCanvas | HTMLCanvasElement;
85
+ if (this._useOffscreen) {
86
+ canvas = new OffscreenCanvas(width, height);
87
+ } else {
88
+ // 在非 OffscreenCanvas 环境(如 Node.js 测试)回退到 HTMLCanvasElement
89
+ if (typeof document !== 'undefined') {
90
+ canvas = document.createElement('canvas');
91
+ canvas.width = width;
92
+ canvas.height = height;
93
+ } else {
94
+ // 无法创建 canvas
95
+ throw new Error('CanvasPool: 无法创建 canvas (OffscreenCanvas 不可用,且 document 不存在)');
96
+ }
97
+ }
98
+
99
+ const entry: PoolEntry = {
100
+ canvas,
101
+ width,
102
+ height,
103
+ inUse: true,
104
+ lastUsed: Date.now(),
105
+ };
106
+
107
+ // 3. 超出 _maxSize 则回收最旧的 inUse=false 的
108
+ if (this._pool.length >= this._maxSize) {
109
+ const oldestIdx = this._findOldestAvailableIndex();
110
+ if (oldestIdx !== -1) {
111
+ this._pool.splice(oldestIdx, 1);
112
+ }
113
+ }
114
+
115
+ this._pool.push(entry);
116
+ return {
117
+ canvas: entry.canvas,
118
+ width: entry.width,
119
+ height: entry.height,
120
+ inUse: true,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * 归还 Canvas 到池中
126
+ * @param pooled 要归还的 PooledCanvas
127
+ */
128
+ release(pooled: PooledCanvas): void {
129
+ const entry = this._pool.find(p => p.canvas === pooled.canvas);
130
+ if (entry) {
131
+ entry.inUse = false;
132
+ entry.lastUsed = Date.now();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * 清空池
138
+ */
139
+ clear(): void {
140
+ this._pool = [];
141
+ }
142
+
143
+ /**
144
+ * 获取池统计信息
145
+ */
146
+ getStats(): { total: number; inUse: number; available: number } {
147
+ return {
148
+ total: this._pool.length,
149
+ inUse: this._pool.filter(p => p.inUse).length,
150
+ available: this._pool.filter(p => !p.inUse).length,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * 查找最旧的可用条目索引
156
+ */
157
+ private _findOldestAvailableIndex(): number {
158
+ let oldestIdx = -1;
159
+ let oldestTime = Infinity;
160
+ for (let i = 0; i < this._pool.length; i++) {
161
+ if (!this._pool[i].inUse && this._pool[i].lastUsed < oldestTime) {
162
+ oldestTime = this._pool[i].lastUsed;
163
+ oldestIdx = i;
164
+ }
165
+ }
166
+ return oldestIdx;
167
+ }
168
+ }
169
+
170
+ /** 全局共享的 Canvas 池实例 */
171
+ export const globalCanvasPool = new CanvasPool();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Canvas Utilities
3
+ * @description Canvas-related utility functions
4
+ * @module core/utils/canvas
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Image Utilities
3
+ * @description Image processing utility functions
4
+ * @module core/utils/image
5
+ */
6
+
7
+ export {};
@@ -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,7 @@
1
+ /**
2
+ * @file Validation Utilities
3
+ * @description Input validation utility functions
4
+ * @module core/utils/validate
5
+ */
6
+
7
+ export {};
@@ -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,2 @@
1
+ export { FaceDetector } from './detector';
2
+ export { FaceDetectionOptions, FaceDetectionResult, FaceCompareResult, BoundingBox, FaceLandmarks, Point } from './types';
@@ -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
+ }