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.
- package/dist/id-scanner-lib.esm.js +994 -1139
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +995 -1144
- 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/camera-manager.ts +43 -76
- package/src/core/camera-stream-manager.ts +318 -0
- package/src/core/config.ts +113 -267
- package/src/core/errors.ts +68 -117
- package/src/core/logger.ts +158 -81
- 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/face-comparator.ts +150 -0
- package/src/modules/face/face-detector-options.ts +104 -0
- package/src/modules/face/face-detector.ts +121 -376
- package/src/modules/face/face-detector.ts.bak +991 -0
- package/src/modules/face/face-model-loader.ts +222 -0
- package/src/modules/face/face-result-converter.ts +225 -0
- package/src/modules/face/face-tracker.ts +207 -0
- package/src/modules/face/liveness/index.ts +7 -0
- package/src/modules/face/liveness-detector.ts +2 -2
- 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/id-card-text-parser.ts +151 -0
- package/src/modules/id-card/ocr-processor.ts +20 -257
- package/src/modules/id-card/ocr-worker.ts +2 -183
- package/src/modules/id-card/parser/index.ts +7 -0
- package/src/modules/qr/scanner/index.ts +7 -0
- package/src/utils/canvas-pool.ts +273 -0
- package/src/utils/edge-detector.ts +232 -0
- package/src/utils/image-processing.ts +92 -419
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 人脸比对器
|
|
3
|
+
* @description 提供人脸特征向量比对功能
|
|
4
|
+
* @module modules/face/face-comparator
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FaceDetectionResult } from '../../interfaces/face-detection';
|
|
8
|
+
import { Result } from '../../core/result';
|
|
9
|
+
import { FaceComparisonError } from '../../core/errors';
|
|
10
|
+
import { Logger } from '../../core/logger';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 人脸比对结果
|
|
14
|
+
*/
|
|
15
|
+
export interface ComparisonResult {
|
|
16
|
+
/** 相似度 (0-1) */
|
|
17
|
+
similarity: number;
|
|
18
|
+
/** 是否匹配(基于阈值) */
|
|
19
|
+
isMatch: boolean;
|
|
20
|
+
/** 使用的阈值 */
|
|
21
|
+
threshold: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 人脸比对器配置
|
|
26
|
+
*/
|
|
27
|
+
export interface FaceComparatorConfig {
|
|
28
|
+
/** 人脸匹配阈值 (0-1) */
|
|
29
|
+
matchThreshold?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 人脸比对器
|
|
34
|
+
*
|
|
35
|
+
* 负责计算两个人脸特征向量的相似度
|
|
36
|
+
*/
|
|
37
|
+
export class FaceComparator {
|
|
38
|
+
/** 日志记录器 */
|
|
39
|
+
private logger: Logger;
|
|
40
|
+
|
|
41
|
+
/** 匹配阈值 */
|
|
42
|
+
private matchThreshold: number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 构造函数
|
|
46
|
+
* @param config 比对器配置
|
|
47
|
+
*/
|
|
48
|
+
constructor(config: FaceComparatorConfig = {}) {
|
|
49
|
+
this.logger = Logger.getInstance();
|
|
50
|
+
this.matchThreshold = config.matchThreshold ?? 0.6;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 计算两个特征向量的余弦相似度
|
|
55
|
+
*
|
|
56
|
+
* @param v1 特征向量1
|
|
57
|
+
* @param v2 特征向量2
|
|
58
|
+
* @returns 相似度 (0-1)
|
|
59
|
+
*/
|
|
60
|
+
computeSimilarity(v1: number[], v2: number[]): number {
|
|
61
|
+
if (v1.length !== v2.length) {
|
|
62
|
+
throw new Error('特征向量维度不匹配');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let dotProduct = 0;
|
|
66
|
+
let norm1 = 0;
|
|
67
|
+
let norm2 = 0;
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < v1.length; i++) {
|
|
70
|
+
dotProduct += v1[i] * v2[i];
|
|
71
|
+
norm1 += v1[i] * v1[i];
|
|
72
|
+
norm2 += v2[i] * v2[i];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 确保长度非零
|
|
76
|
+
if (norm1 === 0 || norm2 === 0) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 比对两个人脸
|
|
85
|
+
*
|
|
86
|
+
* @param source 源人脸(特征向量或检测结果)
|
|
87
|
+
* @param target 目标人脸(特征向量或检测结果)
|
|
88
|
+
* @returns 比对结果
|
|
89
|
+
*/
|
|
90
|
+
compare(
|
|
91
|
+
source: number[] | FaceDetectionResult,
|
|
92
|
+
target: number[] | FaceDetectionResult
|
|
93
|
+
): Result<ComparisonResult> {
|
|
94
|
+
try {
|
|
95
|
+
// 提取特征向量
|
|
96
|
+
const sourceEmbedding = this.extractEmbedding(source);
|
|
97
|
+
const targetEmbedding = this.extractEmbedding(target);
|
|
98
|
+
|
|
99
|
+
if (!sourceEmbedding || !targetEmbedding) {
|
|
100
|
+
return Result.failure(new FaceComparisonError('无法获取特征向量'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 计算相似度
|
|
104
|
+
const similarity = this.computeSimilarity(sourceEmbedding, targetEmbedding);
|
|
105
|
+
const isMatch = similarity >= this.matchThreshold;
|
|
106
|
+
|
|
107
|
+
return Result.success({
|
|
108
|
+
similarity,
|
|
109
|
+
isMatch,
|
|
110
|
+
threshold: this.matchThreshold
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
114
|
+
this.logger.error('FaceComparator', `人脸比对失败: ${errorMessage}`, error as Error);
|
|
115
|
+
return Result.failure(new FaceComparisonError(`人脸比对失败: ${errorMessage}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 从输入中提取特征向量
|
|
121
|
+
*/
|
|
122
|
+
private extractEmbedding(input: number[] | FaceDetectionResult): number[] | null {
|
|
123
|
+
if (Array.isArray(input)) {
|
|
124
|
+
return input;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (input.embedding?.vector) {
|
|
128
|
+
return input.embedding.vector;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 设置匹配阈值
|
|
136
|
+
*/
|
|
137
|
+
setThreshold(threshold: number): void {
|
|
138
|
+
if (threshold < 0 || threshold > 1) {
|
|
139
|
+
throw new Error('阈值必须在 0-1 范围内');
|
|
140
|
+
}
|
|
141
|
+
this.matchThreshold = threshold;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 获取当前阈值
|
|
146
|
+
*/
|
|
147
|
+
getThreshold(): number {
|
|
148
|
+
return this.matchThreshold;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 人脸检测选项工厂
|
|
3
|
+
* @description 创建 face-api 检测选项和处理选项
|
|
4
|
+
* @module modules/face/face-detector-options
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as faceapi from '@vladmandic/face-api';
|
|
8
|
+
import { FaceDetectionOptions } from '../../interfaces/face-detection';
|
|
9
|
+
import { FaceModelType, FaceDetectorConfig } from './face-detector';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 人脸检测选项工厂
|
|
13
|
+
*
|
|
14
|
+
* 负责创建 face-api 兼容的检测选项和处理选项
|
|
15
|
+
*/
|
|
16
|
+
export class FaceDetectorOptionsFactory {
|
|
17
|
+
/**
|
|
18
|
+
* 创建 face-api 检测选项
|
|
19
|
+
* @param detectionModel 检测模型类型
|
|
20
|
+
* @param minConfidence 最小置信度
|
|
21
|
+
* @returns face-api 检测选项
|
|
22
|
+
*/
|
|
23
|
+
static createFaceAPIOptions(
|
|
24
|
+
detectionModel: FaceModelType,
|
|
25
|
+
minConfidence: number
|
|
26
|
+
): faceapi.SsdMobilenetv1Options | faceapi.TinyFaceDetectorOptions | faceapi.MtcnnOptions {
|
|
27
|
+
switch (detectionModel) {
|
|
28
|
+
case FaceModelType.SSD_MOBILENET:
|
|
29
|
+
return new faceapi.SsdMobilenetv1Options({ minConfidence });
|
|
30
|
+
case FaceModelType.TINY_FACE:
|
|
31
|
+
return new faceapi.TinyFaceDetectorOptions({ scoreThreshold: minConfidence });
|
|
32
|
+
case FaceModelType.MTCNN:
|
|
33
|
+
return new faceapi.MtcnnOptions({ minConfidence });
|
|
34
|
+
default:
|
|
35
|
+
return new faceapi.SsdMobilenetv1Options({ minConfidence });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 合并检测选项和配置
|
|
41
|
+
* @param config 人脸检测器配置
|
|
42
|
+
* @param options 用户提供的选项
|
|
43
|
+
* @returns 合并后的检测选项
|
|
44
|
+
*/
|
|
45
|
+
static mergeOptions(
|
|
46
|
+
config: FaceDetectorConfig,
|
|
47
|
+
options: FaceDetectionOptions = {}
|
|
48
|
+
): FaceDetectionOptions {
|
|
49
|
+
return {
|
|
50
|
+
minConfidence: config.minConfidence,
|
|
51
|
+
maxFaces: config.maxFaces,
|
|
52
|
+
withLandmarks: config.detectLandmarks,
|
|
53
|
+
withAttributes: config.detectExpressions || config.detectAgeGender,
|
|
54
|
+
withEmbedding: config.extractEmbeddings,
|
|
55
|
+
enableTracking: config.enableTracking,
|
|
56
|
+
...options
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 执行人脸检测
|
|
62
|
+
* @param input 输入图像/视频/画布
|
|
63
|
+
* @param faceapiOptions face-api 检测选项
|
|
64
|
+
* @param detectOptions 检测选项
|
|
65
|
+
* @param landmarksModel 关键点模型类型
|
|
66
|
+
* @returns face-api 检测结果
|
|
67
|
+
*/
|
|
68
|
+
static async detect(
|
|
69
|
+
input: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement,
|
|
70
|
+
faceapiOptions: faceapi.SsdMobilenetv1Options | faceapi.TinyFaceDetectorOptions | faceapi.MtcnnOptions,
|
|
71
|
+
detectOptions: FaceDetectionOptions,
|
|
72
|
+
landmarksModel: 'tiny' | '68_points'
|
|
73
|
+
): Promise<any> {
|
|
74
|
+
const withLandmarks = detectOptions.withLandmarks;
|
|
75
|
+
const withAttributes = detectOptions.withAttributes;
|
|
76
|
+
const withEmbedding = detectOptions.withEmbedding;
|
|
77
|
+
|
|
78
|
+
// 根据选项组合选择检测方法链
|
|
79
|
+
if (withLandmarks && withAttributes && withEmbedding) {
|
|
80
|
+
// 全功能检测
|
|
81
|
+
return await faceapi
|
|
82
|
+
.detectAllFaces(input, faceapiOptions)
|
|
83
|
+
.withFaceLandmarks(landmarksModel === 'tiny')
|
|
84
|
+
.withFaceExpressions()
|
|
85
|
+
.withAgeAndGender()
|
|
86
|
+
.withFaceDescriptors();
|
|
87
|
+
} else if (withLandmarks && withAttributes) {
|
|
88
|
+
// 检测带关键点和属性
|
|
89
|
+
return await faceapi
|
|
90
|
+
.detectAllFaces(input, faceapiOptions)
|
|
91
|
+
.withFaceLandmarks(landmarksModel === 'tiny')
|
|
92
|
+
.withFaceExpressions()
|
|
93
|
+
.withAgeAndGender();
|
|
94
|
+
} else if (withLandmarks) {
|
|
95
|
+
// 检测带关键点
|
|
96
|
+
return await faceapi
|
|
97
|
+
.detectAllFaces(input, faceapiOptions)
|
|
98
|
+
.withFaceLandmarks(landmarksModel === 'tiny');
|
|
99
|
+
} else {
|
|
100
|
+
// 仅检测
|
|
101
|
+
return await faceapi.detectAllFaces(input, faceapiOptions);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|