id-scanner-lib 1.5.0 → 1.6.2
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/README.md +378 -59
- package/dist/id-scanner-lib.esm.js +195 -10
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +4812 -14709
- package/dist/id-scanner-lib.js.map +1 -1
- package/dist/types/browser-image-compression.d.ts +19 -0
- package/dist/types/tesseract.d.ts +280 -0
- package/package.json +21 -11
- package/src/core/camera-manager.ts +16 -1
- package/src/core/config.ts +37 -0
- package/src/core/errors.ts +3 -3
- package/src/core/event-emitter.test.ts +42 -0
- package/src/core/loading-state.test.ts +67 -0
- package/src/core/loading-state.ts +156 -0
- package/src/core/logger.test.ts +49 -0
- package/src/core/module-manager.ts +2 -4
- package/src/core/scanner-factory.ts +8 -9
- package/src/index.ts +3 -2
- package/src/modules/face/face-detector.ts +123 -66
- package/src/modules/id-card/anti-fake-detector.ts +2 -2
- package/src/modules/id-card/ocr-worker.ts +1 -1
- package/src/modules/qrcode/qr-code-scanner.ts +2 -1
- package/src/modules/qrcode/types.ts +111 -7
- package/src/types/common.test.ts +99 -0
- package/src/types/common.ts +166 -0
- package/src/utils/camera.test.ts +30 -0
- package/src/utils/camera.ts +4 -1
- package/src/utils/error-handler.test.ts +137 -0
- package/src/utils/error-handler.ts +110 -0
- package/src/utils/index.test.ts +186 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/retry.test.ts +142 -0
- package/src/utils/retry.ts +282 -0
- package/src/utils/utils.test.ts +171 -0
- package/src/version.ts +1 -1
- package/dist/types/core/base-module.d.ts +0 -44
- package/dist/types/core/camera-manager.d.ts +0 -258
- package/dist/types/core/config.d.ts +0 -88
- package/dist/types/core/errors.d.ts +0 -111
- package/dist/types/core/event-emitter.d.ts +0 -55
- package/dist/types/core/logger.d.ts +0 -277
- package/dist/types/core/module-manager.d.ts +0 -78
- package/dist/types/core/plugin-manager.d.ts +0 -158
- package/dist/types/core/resource-manager.d.ts +0 -246
- package/dist/types/core/result.d.ts +0 -83
- package/dist/types/core/scanner-factory.d.ts +0 -93
- package/dist/types/index.bundle.d.ts +0 -1303
- package/dist/types/index.d.ts +0 -86
- package/dist/types/interfaces/external-types.d.ts +0 -174
- package/dist/types/interfaces/face-detection.d.ts +0 -293
- package/dist/types/interfaces/scanner-module.d.ts +0 -280
- package/dist/types/modules/face/face-detector.d.ts +0 -170
- package/dist/types/modules/face/index.d.ts +0 -56
- package/dist/types/modules/face/liveness-detector.d.ts +0 -177
- package/dist/types/modules/face/types.d.ts +0 -136
- package/dist/types/modules/id-card/anti-fake-detector.d.ts +0 -170
- package/dist/types/modules/id-card/id-card-detector.d.ts +0 -131
- package/dist/types/modules/id-card/index.d.ts +0 -89
- package/dist/types/modules/id-card/ocr-processor.d.ts +0 -110
- package/dist/types/modules/id-card/ocr-worker.d.ts +0 -31
- package/dist/types/modules/id-card/types.d.ts +0 -181
- package/dist/types/modules/qrcode/index.d.ts +0 -51
- package/dist/types/modules/qrcode/qr-code-scanner.d.ts +0 -64
- package/dist/types/modules/qrcode/types.d.ts +0 -67
- package/dist/types/utils/camera.d.ts +0 -81
- package/dist/types/utils/image-processing.d.ts +0 -176
- package/dist/types/utils/index.d.ts +0 -175
- package/dist/types/utils/performance.d.ts +0 -81
- package/dist/types/utils/resource-manager.d.ts +0 -53
- package/dist/types/utils/types.d.ts +0 -166
- package/dist/types/utils/worker.d.ts +0 -52
- package/dist/types/version.d.ts +0 -7
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 加载状态管理
|
|
3
|
+
* @description 提供模块加载状态跟踪功能
|
|
4
|
+
* @module core/loading-state
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from './event-emitter';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 加载状态类型
|
|
11
|
+
*/
|
|
12
|
+
export enum LoadingState {
|
|
13
|
+
/** 空闲 */
|
|
14
|
+
IDLE = 'idle',
|
|
15
|
+
/** 加载中 */
|
|
16
|
+
LOADING = 'loading',
|
|
17
|
+
/** 就绪 */
|
|
18
|
+
READY = 'ready',
|
|
19
|
+
/** 错误 */
|
|
20
|
+
ERROR = 'error',
|
|
21
|
+
/** 已释放 */
|
|
22
|
+
DISPOSED = 'disposed'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 加载进度信息
|
|
27
|
+
*/
|
|
28
|
+
export interface LoadingProgress {
|
|
29
|
+
/** 当前状态 */
|
|
30
|
+
state: LoadingState;
|
|
31
|
+
/** 已加载的模型 */
|
|
32
|
+
loadedModels: string[];
|
|
33
|
+
/** 正在加载的模型 */
|
|
34
|
+
loadingModel?: string;
|
|
35
|
+
/** 进度百分比 (0-100) */
|
|
36
|
+
progress: number;
|
|
37
|
+
/** 错误信息 */
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 加载状态管理器
|
|
43
|
+
* 用于跟踪和管理模块的加载状态
|
|
44
|
+
*/
|
|
45
|
+
export class LoadingStateManager extends EventEmitter {
|
|
46
|
+
private state: LoadingState = LoadingState.IDLE;
|
|
47
|
+
private loadedModels: Set<string> = new Set();
|
|
48
|
+
private loadingModel?: string;
|
|
49
|
+
private totalModels: number = 0;
|
|
50
|
+
private error?: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 开始加载
|
|
54
|
+
* @param totalModels 总模型数
|
|
55
|
+
*/
|
|
56
|
+
startLoading(totalModels: number): void {
|
|
57
|
+
this.totalModels = totalModels;
|
|
58
|
+
this.state = LoadingState.LOADING;
|
|
59
|
+
this.error = undefined;
|
|
60
|
+
this.emit('stateChange', this.getProgress());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 模型开始加载
|
|
65
|
+
* @param modelName 模型名称
|
|
66
|
+
*/
|
|
67
|
+
startModelLoading(modelName: string): void {
|
|
68
|
+
this.loadingModel = modelName;
|
|
69
|
+
this.emit('progress', this.getProgress());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 模型加载完成
|
|
74
|
+
* @param modelName 模型名称
|
|
75
|
+
*/
|
|
76
|
+
completeModelLoading(modelName: string): void {
|
|
77
|
+
this.loadedModels.add(modelName);
|
|
78
|
+
this.loadingModel = undefined;
|
|
79
|
+
this.emit('progress', this.getProgress());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 加载完成
|
|
84
|
+
*/
|
|
85
|
+
complete(): void {
|
|
86
|
+
this.state = LoadingState.READY;
|
|
87
|
+
this.totalModels = 0;
|
|
88
|
+
this.emit('stateChange', this.getProgress());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 加载失败
|
|
93
|
+
* @param error 错误信息
|
|
94
|
+
*/
|
|
95
|
+
fail(error: string): void {
|
|
96
|
+
this.state = LoadingState.ERROR;
|
|
97
|
+
this.error = error;
|
|
98
|
+
this.emit('error', { error, progress: this.getProgress() });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 释放
|
|
103
|
+
*/
|
|
104
|
+
dispose(): void {
|
|
105
|
+
this.state = LoadingState.DISPOSED;
|
|
106
|
+
this.loadedModels.clear();
|
|
107
|
+
this.totalModels = 0;
|
|
108
|
+
this.error = undefined;
|
|
109
|
+
this.emit('stateChange', this.getProgress());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取当前进度
|
|
114
|
+
*/
|
|
115
|
+
getProgress(): LoadingProgress {
|
|
116
|
+
const progress = this.totalModels > 0
|
|
117
|
+
? Math.round((this.loadedModels.size / this.totalModels) * 100)
|
|
118
|
+
: 0;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
state: this.state,
|
|
122
|
+
loadedModels: Array.from(this.loadedModels),
|
|
123
|
+
loadingModel: this.loadingModel,
|
|
124
|
+
progress,
|
|
125
|
+
error: this.error
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 获取当前状态
|
|
131
|
+
*/
|
|
132
|
+
getState(): LoadingState {
|
|
133
|
+
return this.state;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 是否已就绪
|
|
138
|
+
*/
|
|
139
|
+
isReady(): boolean {
|
|
140
|
+
return this.state === LoadingState.READY;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 是否有错误
|
|
145
|
+
*/
|
|
146
|
+
hasError(): boolean {
|
|
147
|
+
return this.state === LoadingState.ERROR;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 创建加载状态管理器
|
|
153
|
+
*/
|
|
154
|
+
export function createLoadingStateManager(): LoadingStateManager {
|
|
155
|
+
return new LoadingStateManager();
|
|
156
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Logger 测试
|
|
3
|
+
* @description 测试日志系统
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger, LogLevel } from './logger';
|
|
7
|
+
|
|
8
|
+
describe('Logger', () => {
|
|
9
|
+
let logger: Logger;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
logger = Logger.getInstance();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should get singleton instance', () => {
|
|
16
|
+
const logger2 = Logger.getInstance();
|
|
17
|
+
expect(logger).toBe(logger2);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should log debug message', () => {
|
|
21
|
+
expect(() => {
|
|
22
|
+
logger.debug('Test', 'Debug message');
|
|
23
|
+
}).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should log info message', () => {
|
|
27
|
+
expect(() => {
|
|
28
|
+
logger.info('Test', 'Info message');
|
|
29
|
+
}).not.toThrow();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should log warn message', () => {
|
|
33
|
+
expect(() => {
|
|
34
|
+
logger.warn('Test', 'Warning message');
|
|
35
|
+
}).not.toThrow();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should log error message', () => {
|
|
39
|
+
expect(() => {
|
|
40
|
+
logger.error('Test', 'Error message', new Error('test error'));
|
|
41
|
+
}).not.toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should set log level', () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
logger.setLevel(LogLevel.WARN);
|
|
47
|
+
}).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -112,8 +112,7 @@ export class ModuleManager extends EventEmitter {
|
|
|
112
112
|
this.emit('module:initialized', { name });
|
|
113
113
|
this.logger.debug('ModuleManager', `模块 ${name} 初始化完成`);
|
|
114
114
|
} catch (error) {
|
|
115
|
-
|
|
116
|
-
this.logger.error('ModuleManager', `模块 ${name} 初始化失败`, errorObj);
|
|
115
|
+
this.logger.error('ModuleManager', `模块 ${name} 初始化失败`, error instanceof Error ? error : undefined);
|
|
117
116
|
this.emit('module:error', { name, error });
|
|
118
117
|
throw new Error(`模块 ${name} 初始化失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
118
|
}
|
|
@@ -136,8 +135,7 @@ export class ModuleManager extends EventEmitter {
|
|
|
136
135
|
await module.dispose();
|
|
137
136
|
this.emit('module:disposed', { name });
|
|
138
137
|
} catch (error) {
|
|
139
|
-
|
|
140
|
-
this.logger.error('ModuleManager', `模块 ${name} 资源释放失败`, errorObj);
|
|
138
|
+
this.logger.error('ModuleManager', `模块 ${name} 资源释放失败`, error instanceof Error ? error : undefined);
|
|
141
139
|
this.emit('module:error', { name, error });
|
|
142
140
|
}
|
|
143
141
|
}
|
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
* @module core/scanner-factory
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { ConfigManager } from './config';
|
|
7
|
+
import { ConfigManager, GlobalConfig } from './config';
|
|
8
8
|
import { Logger } from './logger';
|
|
9
9
|
import { ResourceManager } from './resource-manager';
|
|
10
10
|
import { EventEmitter } from './event-emitter';
|
|
11
11
|
import { InitializationError } from './errors';
|
|
12
|
-
import { VERSION } from '../version';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* 扫描器初始化选项
|
|
16
15
|
*/
|
|
17
16
|
export interface ScannerFactoryOptions {
|
|
18
17
|
/** 配置选项 */
|
|
19
|
-
config?:
|
|
18
|
+
config?: Partial<GlobalConfig>;
|
|
20
19
|
/** 资源基础路径 */
|
|
21
20
|
resourceBasePath?: string;
|
|
22
21
|
/** 调试模式 */
|
|
@@ -102,7 +101,7 @@ export class ScannerFactory extends EventEmitter {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
// 记录初始化日志
|
|
105
|
-
this.logger.info('ScannerFactory', `Initializing ID Scanner Library
|
|
104
|
+
this.logger.info('ScannerFactory', `Initializing ID Scanner Library v1.4.0, debug: ${this.config.get('debug', false)}`);
|
|
106
105
|
|
|
107
106
|
// 如果启用了自动初始化模块,则加载相应模块
|
|
108
107
|
if (autoInitModules) {
|
|
@@ -120,7 +119,7 @@ export class ScannerFactory extends EventEmitter {
|
|
|
120
119
|
this.initializing = false;
|
|
121
120
|
|
|
122
121
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
-
this.logger.error('ScannerFactory', `Initialization failed: ${errorMessage}`, error instanceof Error ? error :
|
|
122
|
+
this.logger.error('ScannerFactory', `Initialization failed: ${errorMessage}`, error instanceof Error ? error : undefined);
|
|
124
123
|
|
|
125
124
|
this.emit('initialized', { success: false, error });
|
|
126
125
|
|
|
@@ -138,19 +137,19 @@ export class ScannerFactory extends EventEmitter {
|
|
|
138
137
|
const enabledModules = [];
|
|
139
138
|
|
|
140
139
|
// 检查每个模块的启用状态
|
|
141
|
-
if (this.config.
|
|
140
|
+
if (this.config.isModuleEnabled('face')) {
|
|
142
141
|
enabledModules.push(this.initFaceModule());
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
if (this.config.
|
|
144
|
+
if (this.config.isModuleEnabled('qr')) {
|
|
146
145
|
enabledModules.push(this.initQRModule());
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
if (this.config.
|
|
148
|
+
if (this.config.isModuleEnabled('idcard')) {
|
|
150
149
|
enabledModules.push(this.initIDCardModule());
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
if (this.config.
|
|
152
|
+
if (this.config.isModuleEnabled('ocr')) {
|
|
154
153
|
enabledModules.push(this.initOCRModule());
|
|
155
154
|
}
|
|
156
155
|
|
package/src/index.ts
CHANGED
|
@@ -130,7 +130,7 @@ export class IDScanner {
|
|
|
130
130
|
public async dispose(): Promise<void> {
|
|
131
131
|
if (!this.initialized) {
|
|
132
132
|
return;
|
|
133
|
-
|
|
133
|
+
}
|
|
134
134
|
|
|
135
135
|
this.logger.info('IDScanner', '释放IDScanner资源');
|
|
136
136
|
|
|
@@ -147,6 +147,7 @@ export class IDScanner {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// 导出核心模块
|
|
150
|
+
export * from './core/loading-state';
|
|
150
151
|
export * from './core/module-manager';
|
|
151
152
|
export * from './core/logger';
|
|
152
153
|
export * from './core/errors';
|
|
@@ -159,7 +160,7 @@ export { FaceModule } from './modules/face';
|
|
|
159
160
|
// 导出类型
|
|
160
161
|
export * from './utils/types';
|
|
161
162
|
export { IDCardModuleOptions, IDCardInfo, IDCardType, IDCardVerificationResult } from './modules/id-card/types';
|
|
162
|
-
export { QRCodeModuleOptions, QRCodeResult } from './modules/qrcode/types';
|
|
163
|
+
export { QRCodeModuleOptions, QRCodeResult, BarcodeFormat, DEFAULT_FORMATS } from './modules/qrcode/types';
|
|
163
164
|
export { FaceModuleOptions, FaceDetectionResult, FaceComparisonResult } from './modules/face/types';
|
|
164
165
|
|
|
165
166
|
// 默认导出IDScanner类
|
|
@@ -31,6 +31,21 @@ export enum FaceModelType {
|
|
|
31
31
|
BLAZE_FACE = 'blazeface'
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 68点人脸关键点索引 (face-api 68-point model)
|
|
36
|
+
* 参考: https://github.com/justadudewhohacks/face-api.js/issues/175
|
|
37
|
+
*/
|
|
38
|
+
const FaceLandmarkIndex = {
|
|
39
|
+
LEFT_EYE: 36,
|
|
40
|
+
RIGHT_EYE: 45,
|
|
41
|
+
NOSE: 30,
|
|
42
|
+
MOUTH: 48, // 上唇中心
|
|
43
|
+
LEFT_EYE_CORNER: 36,
|
|
44
|
+
RIGHT_EYE_CORNER: 45,
|
|
45
|
+
NOSE_TIP: 30,
|
|
46
|
+
MOUTH_CENTER: 57,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
34
49
|
/**
|
|
35
50
|
* 人脸检测模块配置
|
|
36
51
|
*/
|
|
@@ -92,6 +107,7 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
92
107
|
|
|
93
108
|
/** 模型加载状态 */
|
|
94
109
|
private modelsLoaded: boolean = false;
|
|
110
|
+
private loadedModels: Set<string> = new Set();
|
|
95
111
|
|
|
96
112
|
/** 处理计时器ID */
|
|
97
113
|
private processingTimerId: number | null = null;
|
|
@@ -236,59 +252,97 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
/**
|
|
239
|
-
*
|
|
255
|
+
* 懒加载模型 - 仅在需要时加载特定模型
|
|
256
|
+
* @param modelType 模型类型
|
|
240
257
|
* @param modelPath 模型路径
|
|
241
258
|
*/
|
|
242
|
-
private async
|
|
259
|
+
private async lazyLoadModel(modelType: string, modelPath: string): Promise<void> {
|
|
260
|
+
// 检查模型是否已加载
|
|
261
|
+
const loadedModels = this.loadedModels || new Set();
|
|
262
|
+
if (loadedModels.has(modelType)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.logger.info('FaceDetector', `懒加载模型: ${modelType}`);
|
|
267
|
+
|
|
243
268
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// 根据配置加载检测模型
|
|
248
|
-
switch (this.config.detectionModel) {
|
|
249
|
-
case FaceModelType.SSD_MOBILENET:
|
|
269
|
+
switch (modelType) {
|
|
270
|
+
case 'ssdMobilenetv1':
|
|
250
271
|
await faceapi.nets.ssdMobilenetv1.loadFromUri(modelPath);
|
|
251
272
|
break;
|
|
252
|
-
case
|
|
273
|
+
case 'tinyFaceDetector':
|
|
253
274
|
await faceapi.nets.tinyFaceDetector.loadFromUri(modelPath);
|
|
254
275
|
break;
|
|
255
|
-
case
|
|
256
|
-
await faceapi.nets.mtcnn.loadFromUri(modelPath);
|
|
257
|
-
break;
|
|
258
|
-
default:
|
|
259
|
-
await faceapi.nets.ssdMobilenetv1.loadFromUri(modelPath);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// 加载关键点检测模型
|
|
263
|
-
if (this.config.detectLandmarks) {
|
|
264
|
-
if (this.config.landmarksModel === '68_points') {
|
|
276
|
+
case 'faceLandmark68Net':
|
|
265
277
|
await faceapi.nets.faceLandmark68Net.loadFromUri(modelPath);
|
|
266
|
-
|
|
278
|
+
break;
|
|
279
|
+
case 'faceLandmark68TinyNet':
|
|
267
280
|
await faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelPath);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// 加载人脸识别模型
|
|
282
|
-
if (this.config.extractEmbeddings) {
|
|
283
|
-
await faceapi.nets.faceRecognitionNet.loadFromUri(modelPath);
|
|
281
|
+
break;
|
|
282
|
+
case 'faceExpressionNet':
|
|
283
|
+
await faceapi.nets.faceExpressionNet.loadFromUri(modelPath);
|
|
284
|
+
break;
|
|
285
|
+
case 'ageGenderNet':
|
|
286
|
+
await faceapi.nets.ageGenderNet.loadFromUri(modelPath);
|
|
287
|
+
break;
|
|
288
|
+
case 'faceRecognitionNet':
|
|
289
|
+
await faceapi.nets.faceRecognitionNet.loadFromUri(modelPath);
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
this.logger.warn('FaceDetector', `未知模型类型: ${modelType}`);
|
|
293
|
+
return;
|
|
284
294
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
this.
|
|
295
|
+
|
|
296
|
+
loadedModels.add(modelType);
|
|
297
|
+
this.loadedModels = loadedModels;
|
|
298
|
+
this.logger.info('FaceDetector', `模型加载完成: ${modelType}`);
|
|
288
299
|
} catch (error) {
|
|
289
|
-
this.logger.error('FaceDetector', `模型加载失败: ${error
|
|
290
|
-
throw new ResourceLoadError(
|
|
300
|
+
this.logger.error('FaceDetector', `模型加载失败: ${modelType}`, error instanceof Error ? error : undefined);
|
|
301
|
+
throw new ResourceLoadError(modelType, `模型加载失败: ${error}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 根据需求加载模型
|
|
307
|
+
* @param options 检测选项
|
|
308
|
+
* @param modelPath 模型路径
|
|
309
|
+
*/
|
|
310
|
+
private async loadModelsOnDemand(options: FaceDetectionOptions, modelPath: string): Promise<void> {
|
|
311
|
+
// 基础检测模型
|
|
312
|
+
await this.lazyLoadModel(this.config.detectionModel, modelPath);
|
|
313
|
+
|
|
314
|
+
// 关键点模型
|
|
315
|
+
if (options.withLandmarks || this.config.detectLandmarks) {
|
|
316
|
+
await this.lazyLoadModel(
|
|
317
|
+
this.config.landmarksModel === '68_points' ? 'faceLandmark68Net' : 'faceLandmark68TinyNet',
|
|
318
|
+
modelPath
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 表情模型
|
|
323
|
+
if (options.withExpressions || this.config.detectExpressions) {
|
|
324
|
+
await this.lazyLoadModel('faceExpressionNet', modelPath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 年龄性别模型
|
|
328
|
+
if (options.withAgeAndGender || this.config.detectAgeGender) {
|
|
329
|
+
await this.lazyLoadModel('ageGenderNet', modelPath);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 人脸识别模型
|
|
333
|
+
if (options.withEmbedding || this.config.extractEmbeddings) {
|
|
334
|
+
await this.lazyLoadModel('faceRecognitionNet', modelPath);
|
|
291
335
|
}
|
|
336
|
+
|
|
337
|
+
this.modelsLoaded = true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 加载人脸检测模型 (旧版 - 保留兼容性)
|
|
342
|
+
* @param modelPath 模型路径
|
|
343
|
+
*/
|
|
344
|
+
private async loadModels(modelPath: string): Promise<void> {
|
|
345
|
+
await this.loadModelsOnDemand({}, modelPath);
|
|
292
346
|
}
|
|
293
347
|
|
|
294
348
|
/**
|
|
@@ -308,8 +362,12 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
308
362
|
|
|
309
363
|
this.setStatus(ModuleStatus.PROCESSING);
|
|
310
364
|
this.emit(ModuleEvent.PROCESS_START);
|
|
311
|
-
|
|
365
|
+
|
|
312
366
|
try {
|
|
367
|
+
// 懒加载所需的模型
|
|
368
|
+
const modelPath = this.config.modelPath || '/models';
|
|
369
|
+
await this.loadModelsOnDemand(options, modelPath);
|
|
370
|
+
|
|
313
371
|
// 合并选项和配置
|
|
314
372
|
const processOptions: FaceDetectionOptions = {
|
|
315
373
|
minConfidence: this.config.minConfidence,
|
|
@@ -638,15 +696,16 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
638
696
|
|
|
639
697
|
// 限制检测数量
|
|
640
698
|
const maxFaces = detectOptions.maxFaces || this.config.maxFaces;
|
|
641
|
-
|
|
642
|
-
|
|
699
|
+
const detectionsArray: any[] = Array.isArray(detections) ? detections : [detections];
|
|
700
|
+
if (detectionsArray.length > maxFaces) {
|
|
701
|
+
detectionsArray.length = maxFaces;
|
|
643
702
|
}
|
|
644
703
|
|
|
645
704
|
// 将结果转换为标准格式
|
|
646
705
|
const results: FaceDetectionResult[] = [];
|
|
647
706
|
const processingTime = Date.now() - startTime;
|
|
648
707
|
|
|
649
|
-
for (const detection of
|
|
708
|
+
for (const detection of detectionsArray) {
|
|
650
709
|
const boundingBox: Rect = {
|
|
651
710
|
x: detection.detection?.box.x || 0,
|
|
652
711
|
y: detection.detection?.box.y || 0,
|
|
@@ -667,27 +726,23 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
667
726
|
// 添加关键点
|
|
668
727
|
if (detection.landmarks) {
|
|
669
728
|
const positions = detection.landmarks.positions;
|
|
670
|
-
const leftEyeIdx = 36; // 面部68点模型中左眼的索引
|
|
671
|
-
const rightEyeIdx = 45; // 面部68点模型中右眼的索引
|
|
672
|
-
const noseIdx = 30; // 鼻尖
|
|
673
|
-
const mouthIdx = 57; // 嘴巴中心
|
|
674
729
|
|
|
675
730
|
result.landmarks = {
|
|
676
731
|
leftEye: {
|
|
677
|
-
x: positions[
|
|
678
|
-
y: positions[
|
|
732
|
+
x: positions[FaceLandmarkIndex.LEFT_EYE].x,
|
|
733
|
+
y: positions[FaceLandmarkIndex.LEFT_EYE].y
|
|
679
734
|
},
|
|
680
735
|
rightEye: {
|
|
681
|
-
x: positions[
|
|
682
|
-
y: positions[
|
|
736
|
+
x: positions[FaceLandmarkIndex.RIGHT_EYE].x,
|
|
737
|
+
y: positions[FaceLandmarkIndex.RIGHT_EYE].y
|
|
683
738
|
},
|
|
684
739
|
nose: {
|
|
685
|
-
x: positions[
|
|
686
|
-
y: positions[
|
|
740
|
+
x: positions[FaceLandmarkIndex.NOSE].x,
|
|
741
|
+
y: positions[FaceLandmarkIndex.NOSE].y
|
|
687
742
|
},
|
|
688
743
|
mouth: {
|
|
689
|
-
x: positions[
|
|
690
|
-
y: positions[
|
|
744
|
+
x: positions[FaceLandmarkIndex.MOUTH_CENTER].x,
|
|
745
|
+
y: positions[FaceLandmarkIndex.MOUTH_CENTER].y
|
|
691
746
|
},
|
|
692
747
|
points: positions.map((p: { x: any; y: any; }) => ({ x: p.x, y: p.y }))
|
|
693
748
|
};
|
|
@@ -695,16 +750,18 @@ export class FaceDetector extends BaseScannerModule {
|
|
|
695
750
|
|
|
696
751
|
// 添加表情属性
|
|
697
752
|
if (detection.expressions) {
|
|
753
|
+
const emotion = {
|
|
754
|
+
angry: detection.expressions.angry,
|
|
755
|
+
disgust: detection.expressions.disgusted,
|
|
756
|
+
fear: detection.expressions.fearful,
|
|
757
|
+
happy: detection.expressions.happy,
|
|
758
|
+
neutral: detection.expressions.neutral,
|
|
759
|
+
sad: detection.expressions.sad,
|
|
760
|
+
surprise: detection.expressions.surprised
|
|
761
|
+
};
|
|
698
762
|
result.attributes = {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
disgust: detection.expressions.disgusted,
|
|
702
|
-
fear: detection.expressions.fearful,
|
|
703
|
-
happy: detection.expressions.happy,
|
|
704
|
-
neutral: detection.expressions.neutral,
|
|
705
|
-
sad: detection.expressions.sad,
|
|
706
|
-
surprise: detection.expressions.surprised
|
|
707
|
-
}
|
|
763
|
+
...result.attributes,
|
|
764
|
+
emotion
|
|
708
765
|
};
|
|
709
766
|
}
|
|
710
767
|
|
|
@@ -136,7 +136,7 @@ export class AntiFakeDetector implements Disposable {
|
|
|
136
136
|
detectedFeatures.length >= 2
|
|
137
137
|
|
|
138
138
|
// 生成结果消息
|
|
139
|
-
|
|
139
|
+
const message = isAuthentic
|
|
140
140
|
? `身份证真实,检测到${detectedFeatures.length}个防伪特征`
|
|
141
141
|
: detectedFeatures.length > 0
|
|
142
142
|
? `可疑身份证,仅检测到${detectedFeatures.length}个防伪特征,置信度不足`
|
|
@@ -456,7 +456,7 @@ export class AntiFakeDetector implements Disposable {
|
|
|
456
456
|
} {
|
|
457
457
|
const { data, width, height } = edgeData
|
|
458
458
|
let edgeCount = 0
|
|
459
|
-
|
|
459
|
+
const totalPixels = width * height
|
|
460
460
|
|
|
461
461
|
// 计算边缘像素的数量
|
|
462
462
|
for (let i = 0; i < data.length; i += 4) {
|
|
@@ -149,7 +149,7 @@ function parseIDCardText(text: string): IDCardInfo {
|
|
|
149
149
|
const birthDateRegex2 = /出生[\s\:]*(\d{4})[-\/\.](\d{1,2})[-\/\.](\d{1,2})/;
|
|
150
150
|
const birthDateRegex3 = /出生日期[\s\:]*(\d{4})[-\/\.\u5e74](\d{1,2})[-\/\.\u6708](\d{1,2})[日号]?/;
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
const birthDateMatch =
|
|
153
153
|
processedText.match(birthDateRegex1) ||
|
|
154
154
|
processedText.match(birthDateRegex2) ||
|
|
155
155
|
processedText.match(birthDateRegex3);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { EventEmitter } from '../../core/event-emitter';
|
|
8
8
|
import { Logger } from '../../core/logger';
|
|
9
|
-
import { QRCodeResult } from './types';
|
|
9
|
+
import { QRCodeResult, BarcodeFormat } from './types';
|
|
10
10
|
import jsQR from 'jsqr';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -112,6 +112,7 @@ export class QRCodeScanner extends EventEmitter {
|
|
|
112
112
|
// 构建结果
|
|
113
113
|
const result: QRCodeResult = {
|
|
114
114
|
data: code.data,
|
|
115
|
+
barcodeFormat: BarcodeFormat.QR_CODE,
|
|
115
116
|
boundingBox: {
|
|
116
117
|
topLeft: code.location.topLeftCorner,
|
|
117
118
|
topRight: code.location.topRightCorner,
|