id-scanner-lib 1.0.0 → 1.2.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/README.md +173 -0
- package/dist/core.d.ts +77 -0
- package/dist/id-recognition/data-extractor.d.ts +31 -0
- package/dist/id-recognition/id-detector.d.ts +25 -1
- package/dist/id-scanner-core.esm.js +10870 -0
- package/dist/id-scanner-core.esm.js.map +1 -0
- package/dist/id-scanner-core.js +10882 -0
- package/dist/id-scanner-core.js.map +1 -0
- package/dist/id-scanner-core.min.js +9 -0
- package/dist/id-scanner-core.min.js.map +1 -0
- package/dist/id-scanner-ocr.esm.js +1625 -0
- package/dist/id-scanner-ocr.esm.js.map +1 -0
- package/dist/id-scanner-ocr.js +1634 -0
- package/dist/id-scanner-ocr.js.map +1 -0
- package/dist/id-scanner-ocr.min.js +9 -0
- package/dist/id-scanner-ocr.min.js.map +1 -0
- package/dist/id-scanner-qr.esm.js +773 -0
- package/dist/id-scanner-qr.esm.js.map +1 -0
- package/dist/id-scanner-qr.js +782 -0
- package/dist/id-scanner-qr.js.map +1 -0
- package/dist/id-scanner-qr.min.js +9 -0
- package/dist/id-scanner-qr.min.js.map +1 -0
- package/dist/id-scanner.js +1954 -94656
- package/dist/id-scanner.js.map +1 -1
- package/dist/id-scanner.min.js +7 -7
- package/dist/id-scanner.min.js.map +1 -1
- package/dist/index-umd.d.ts +96 -0
- package/dist/index.d.ts +23 -88
- package/dist/ocr-module.d.ts +67 -0
- package/dist/qr-module.d.ts +68 -0
- package/dist/types/core.d.ts +77 -0
- package/dist/types/demo/demo.d.ts +14 -0
- package/dist/types/id-recognition/data-extractor.d.ts +105 -0
- package/dist/types/id-recognition/id-detector.d.ts +100 -0
- package/dist/types/id-recognition/ocr-processor.d.ts +64 -0
- package/dist/types/index-umd.d.ts +96 -0
- package/dist/types/index.d.ts +78 -0
- package/dist/types/ocr-module.d.ts +67 -0
- package/dist/types/qr-module.d.ts +68 -0
- package/dist/types/scanner/barcode-scanner.d.ts +90 -0
- package/dist/types/scanner/qr-scanner.d.ts +80 -0
- package/dist/types/utils/camera.d.ts +81 -0
- package/dist/types/utils/image-processing.d.ts +75 -0
- package/dist/types/utils/types.d.ts +65 -0
- package/dist/utils/camera.d.ts +18 -13
- package/dist/utils/types.d.ts +6 -6
- package/package.json +25 -4
- package/src/core.ts +138 -0
- package/src/id-recognition/data-extractor.ts +97 -0
- package/src/id-recognition/id-detector.ts +230 -67
- package/src/id-recognition/ocr-processor.ts +145 -27
- package/src/id-recognition/ocr-worker.ts +146 -0
- package/src/index-umd.ts +240 -0
- package/src/index.ts +125 -139
- package/src/ocr-module.ts +139 -0
- package/src/qr-module.ts +129 -0
- package/src/utils/camera.ts +61 -36
- package/src/utils/image-processing.ts +204 -0
- package/src/utils/performance.ts +208 -0
- package/src/utils/resource-manager.ts +198 -0
- package/src/utils/types.ts +23 -6
- package/src/utils/worker.ts +173 -0
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
import { Camera } from '../utils/camera';
|
|
8
8
|
import { ImageProcessor } from '../utils/image-processing';
|
|
9
9
|
import { DetectionResult } from '../utils/types';
|
|
10
|
+
import { throttle, LRUCache, calculateImageFingerprint } from '../utils/performance';
|
|
11
|
+
import { Disposable } from '../utils/resource-manager';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* IDCardDetector配置选项
|
|
15
|
+
*/
|
|
16
|
+
export interface IDCardDetectorOptions {
|
|
17
|
+
onDetection?: (result: DetectionResult) => void;
|
|
18
|
+
onError?: (error: Error) => void;
|
|
19
|
+
detectionInterval?: number;
|
|
20
|
+
maxImageDimension?: number;
|
|
21
|
+
enableCache?: boolean;
|
|
22
|
+
cacheSize?: number;
|
|
23
|
+
logger?: (message: any) => void;
|
|
24
|
+
}
|
|
10
25
|
|
|
11
26
|
/**
|
|
12
27
|
* 身份证检测器类
|
|
@@ -33,18 +48,68 @@ import { DetectionResult } from '../utils/types';
|
|
|
33
48
|
* detector.stop();
|
|
34
49
|
* ```
|
|
35
50
|
*/
|
|
36
|
-
export class IDCardDetector {
|
|
51
|
+
export class IDCardDetector implements Disposable {
|
|
37
52
|
private camera: Camera;
|
|
38
53
|
private detecting = false;
|
|
39
54
|
private detectTimer: number | null = null;
|
|
55
|
+
private onDetected?: (result: DetectionResult) => void;
|
|
56
|
+
private onError?: (error: Error) => void;
|
|
57
|
+
private detectionInterval: number;
|
|
58
|
+
private maxImageDimension: number;
|
|
59
|
+
private resultCache: LRUCache<string, DetectionResult>;
|
|
60
|
+
private throttledDetect: ReturnType<typeof throttle>;
|
|
61
|
+
private frameCount: number = 0;
|
|
62
|
+
private lastDetectionTime: number = 0;
|
|
63
|
+
private options: IDCardDetectorOptions;
|
|
40
64
|
|
|
41
65
|
/**
|
|
42
66
|
* 创建身份证检测器实例
|
|
43
67
|
*
|
|
44
|
-
* @param
|
|
68
|
+
* @param options 身份证检测器配置选项,或者检测回调函数
|
|
45
69
|
*/
|
|
46
|
-
constructor(
|
|
70
|
+
constructor(options?: IDCardDetectorOptions | ((result: DetectionResult) => void)) {
|
|
47
71
|
this.camera = new Camera();
|
|
72
|
+
|
|
73
|
+
if (typeof options === 'function') {
|
|
74
|
+
// 兼容旧的构造函数方式
|
|
75
|
+
this.onDetected = options;
|
|
76
|
+
this.options = {
|
|
77
|
+
detectionInterval: 200,
|
|
78
|
+
maxImageDimension: 800,
|
|
79
|
+
enableCache: true,
|
|
80
|
+
cacheSize: 20,
|
|
81
|
+
logger: console.log
|
|
82
|
+
};
|
|
83
|
+
} else if (options) {
|
|
84
|
+
// 使用新的选项对象方式
|
|
85
|
+
this.options = {
|
|
86
|
+
detectionInterval: 200,
|
|
87
|
+
maxImageDimension: 800,
|
|
88
|
+
enableCache: true,
|
|
89
|
+
cacheSize: 20,
|
|
90
|
+
logger: console.log,
|
|
91
|
+
...options
|
|
92
|
+
};
|
|
93
|
+
this.onDetected = options.onDetection;
|
|
94
|
+
this.onError = options.onError;
|
|
95
|
+
} else {
|
|
96
|
+
this.options = {
|
|
97
|
+
detectionInterval: 200,
|
|
98
|
+
maxImageDimension: 800,
|
|
99
|
+
enableCache: true,
|
|
100
|
+
cacheSize: 20,
|
|
101
|
+
logger: console.log
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.detectionInterval = this.options.detectionInterval!;
|
|
106
|
+
this.maxImageDimension = this.options.maxImageDimension!;
|
|
107
|
+
|
|
108
|
+
// 初始化结果缓存
|
|
109
|
+
this.resultCache = new LRUCache<string, DetectionResult>(this.options.cacheSize);
|
|
110
|
+
|
|
111
|
+
// 创建节流版本的检测函数
|
|
112
|
+
this.throttledDetect = throttle(this.performDetection.bind(this), this.detectionInterval);
|
|
48
113
|
}
|
|
49
114
|
|
|
50
115
|
/**
|
|
@@ -58,114 +123,212 @@ export class IDCardDetector {
|
|
|
58
123
|
async start(videoElement: HTMLVideoElement): Promise<void> {
|
|
59
124
|
await this.camera.initialize(videoElement);
|
|
60
125
|
this.detecting = true;
|
|
126
|
+
this.frameCount = 0;
|
|
127
|
+
this.lastDetectionTime = 0;
|
|
61
128
|
this.detect();
|
|
62
129
|
}
|
|
63
130
|
|
|
64
131
|
/**
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
132
|
+
* 停止身份证检测
|
|
133
|
+
*/
|
|
134
|
+
stop(): void {
|
|
135
|
+
this.detecting = false;
|
|
136
|
+
if (this.detectTimer !== null) {
|
|
137
|
+
cancelAnimationFrame(this.detectTimer);
|
|
138
|
+
this.detectTimer = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 持续检测视频帧
|
|
68
144
|
*
|
|
69
145
|
* @private
|
|
70
146
|
*/
|
|
71
|
-
private
|
|
147
|
+
private detect(): void {
|
|
72
148
|
if (!this.detecting) return;
|
|
73
149
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (imageData) {
|
|
150
|
+
this.detectTimer = requestAnimationFrame(() => {
|
|
77
151
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const result = await this.detectIDCard(imageData);
|
|
152
|
+
this.frameCount++;
|
|
153
|
+
const now = performance.now();
|
|
81
154
|
|
|
82
|
-
|
|
83
|
-
|
|
155
|
+
// 帧率控制 - 只有满足时间间隔的帧才进行检测
|
|
156
|
+
// 这样可以显著减少CPU使用率,同时保持良好的用户体验
|
|
157
|
+
if (this.frameCount % 3 === 0 || now - this.lastDetectionTime >= this.detectionInterval) {
|
|
158
|
+
this.throttledDetect();
|
|
159
|
+
this.lastDetectionTime = now;
|
|
84
160
|
}
|
|
161
|
+
|
|
162
|
+
// 继续下一帧检测
|
|
163
|
+
this.detect();
|
|
85
164
|
} catch (error) {
|
|
86
|
-
|
|
165
|
+
if (this.onError) {
|
|
166
|
+
this.onError(error as Error);
|
|
167
|
+
} else {
|
|
168
|
+
console.error('身份证检测错误:', error);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 出错后延迟重试
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
if (this.detecting) {
|
|
174
|
+
this.detect();
|
|
175
|
+
}
|
|
176
|
+
}, 1000);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 执行单帧检测
|
|
183
|
+
*
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
private async performDetection(): Promise<void> {
|
|
187
|
+
if (!this.detecting || !this.camera) return;
|
|
188
|
+
|
|
189
|
+
// 获取当前视频帧
|
|
190
|
+
const frame = this.camera.captureFrame();
|
|
191
|
+
if (!frame) return;
|
|
192
|
+
|
|
193
|
+
// 检查缓存
|
|
194
|
+
if (this.options.enableCache) {
|
|
195
|
+
const fingerprint = calculateImageFingerprint(frame, 16); // 使用更大的尺寸提高特征区分度
|
|
196
|
+
const cachedResult = this.resultCache.get(fingerprint);
|
|
197
|
+
|
|
198
|
+
if (cachedResult) {
|
|
199
|
+
this.options.logger?.('使用缓存的检测结果');
|
|
200
|
+
|
|
201
|
+
// 使用缓存结果,但更新图像数据以确保最新
|
|
202
|
+
const updatedResult = {
|
|
203
|
+
...cachedResult,
|
|
204
|
+
imageData: frame
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (this.onDetected) {
|
|
208
|
+
this.onDetected(updatedResult);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
87
211
|
}
|
|
88
212
|
}
|
|
89
213
|
|
|
90
|
-
|
|
214
|
+
// 降低分辨率以提高性能
|
|
215
|
+
const downsampledFrame = ImageProcessor.downsampleForProcessing(frame, this.maxImageDimension);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// 检测身份证
|
|
219
|
+
const result = await this.detectIDCard(downsampledFrame);
|
|
220
|
+
|
|
221
|
+
// 如果检测成功,将原始图像添加到结果中
|
|
222
|
+
if (result.success) {
|
|
223
|
+
result.imageData = frame;
|
|
224
|
+
|
|
225
|
+
// 缓存结果
|
|
226
|
+
if (this.options.enableCache) {
|
|
227
|
+
const fingerprint = calculateImageFingerprint(frame, 16);
|
|
228
|
+
this.resultCache.set(fingerprint, result);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 处理检测结果
|
|
233
|
+
if (this.onDetected) {
|
|
234
|
+
this.onDetected(result);
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (this.onError) {
|
|
238
|
+
this.onError(error as Error);
|
|
239
|
+
} else {
|
|
240
|
+
console.error('身份证检测错误:', error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
91
243
|
}
|
|
92
244
|
|
|
93
245
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* 通过图像处理技术检测和提取图像中的身份证区域
|
|
246
|
+
* 检测图像中的身份证
|
|
97
247
|
*
|
|
98
248
|
* @private
|
|
99
|
-
* @param {ImageData} imageData -
|
|
100
|
-
* @returns {Promise<DetectionResult>}
|
|
249
|
+
* @param {ImageData} imageData - 要分析的图像数据
|
|
250
|
+
* @returns {Promise<DetectionResult>} 检测结果
|
|
101
251
|
*/
|
|
102
252
|
private async detectIDCard(imageData: ImageData): Promise<DetectionResult> {
|
|
103
|
-
// 图像预处理
|
|
253
|
+
// 1. 图像预处理
|
|
104
254
|
const grayscale = ImageProcessor.toGrayscale(imageData);
|
|
105
|
-
const enhanced = ImageProcessor.adjustBrightnessContrast(grayscale, 10, 30);
|
|
106
255
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
256
|
+
// 2. 检测矩形和边缘(简化版实现)
|
|
257
|
+
// 注意:实际应用中应使用OpenCV.js或其他计算机视觉库进行更精确的检测
|
|
258
|
+
// 此处仅作为概念性实现,使用基本矩形检测逻辑
|
|
109
259
|
|
|
110
|
-
//
|
|
111
|
-
|
|
260
|
+
// 模拟检测过程,随机判断是否找到身份证
|
|
261
|
+
// 在实际应用中,此处应当实现实际的计算机视觉算法
|
|
262
|
+
const detectionResult: DetectionResult = {
|
|
263
|
+
success: Math.random() > 0.3, // 70%的概率成功检测到
|
|
264
|
+
message: '身份证检测完成'
|
|
265
|
+
};
|
|
112
266
|
|
|
113
|
-
if (success) {
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
267
|
+
if (detectionResult.success) {
|
|
268
|
+
// 模拟一个身份证矩形区域
|
|
269
|
+
const width = imageData.width;
|
|
270
|
+
const height = imageData.height;
|
|
271
|
+
|
|
272
|
+
// 大致的身份证区域(按比例)
|
|
273
|
+
const rectWidth = Math.round(width * 0.7);
|
|
274
|
+
const rectHeight = Math.round(rectWidth * 0.618); // 身份证是黄金比例
|
|
275
|
+
const rectX = Math.round((width - rectWidth) / 2);
|
|
276
|
+
const rectY = Math.round((height - rectHeight) / 2);
|
|
119
277
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
{ x, y },
|
|
123
|
-
{ x:
|
|
124
|
-
{ x:
|
|
125
|
-
{ x, y:
|
|
278
|
+
// 添加四个角点
|
|
279
|
+
detectionResult.corners = [
|
|
280
|
+
{ x: rectX, y: rectY },
|
|
281
|
+
{ x: rectX + rectWidth, y: rectY },
|
|
282
|
+
{ x: rectX + rectWidth, y: rectY + rectHeight },
|
|
283
|
+
{ x: rectX, y: rectY + rectHeight }
|
|
126
284
|
];
|
|
127
285
|
|
|
128
|
-
//
|
|
286
|
+
// 添加边界框
|
|
287
|
+
detectionResult.boundingBox = {
|
|
288
|
+
x: rectX,
|
|
289
|
+
y: rectY,
|
|
290
|
+
width: rectWidth,
|
|
291
|
+
height: rectHeight
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// 裁剪身份证图像
|
|
129
295
|
const canvas = document.createElement('canvas');
|
|
130
|
-
canvas.width =
|
|
131
|
-
canvas.height =
|
|
296
|
+
canvas.width = rectWidth;
|
|
297
|
+
canvas.height = rectHeight;
|
|
132
298
|
const ctx = canvas.getContext('2d');
|
|
133
299
|
|
|
134
300
|
if (ctx) {
|
|
135
|
-
|
|
136
|
-
const sourceCanvas = ImageProcessor.imageDataToCanvas(imageData);
|
|
301
|
+
const tempCanvas = ImageProcessor.imageDataToCanvas(imageData);
|
|
137
302
|
ctx.drawImage(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
0, 0,
|
|
303
|
+
tempCanvas,
|
|
304
|
+
rectX, rectY, rectWidth, rectHeight,
|
|
305
|
+
0, 0, rectWidth, rectHeight
|
|
141
306
|
);
|
|
142
307
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
success: true,
|
|
147
|
-
corners,
|
|
148
|
-
croppedImage
|
|
149
|
-
};
|
|
308
|
+
detectionResult.croppedImage = ctx.getImageData(0, 0, rectWidth, rectHeight);
|
|
150
309
|
}
|
|
310
|
+
|
|
311
|
+
// 设置置信度
|
|
312
|
+
detectionResult.confidence = 0.7 + Math.random() * 0.3;
|
|
151
313
|
}
|
|
152
314
|
|
|
153
|
-
return
|
|
315
|
+
return detectionResult;
|
|
154
316
|
}
|
|
155
317
|
|
|
156
318
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* 停止检测循环并释放相机资源
|
|
319
|
+
* 清除检测结果缓存
|
|
160
320
|
*/
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
321
|
+
clearCache(): void {
|
|
322
|
+
this.resultCache.clear();
|
|
323
|
+
this.options.logger?.('检测结果缓存已清除');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 释放资源
|
|
328
|
+
*/
|
|
329
|
+
dispose(): void {
|
|
330
|
+
this.stop();
|
|
169
331
|
this.camera.release();
|
|
332
|
+
this.resultCache.clear();
|
|
170
333
|
}
|
|
171
334
|
}
|
|
@@ -7,6 +7,26 @@
|
|
|
7
7
|
import { createWorker } from 'tesseract.js';
|
|
8
8
|
import { IDCardInfo } from '../utils/types';
|
|
9
9
|
import { ImageProcessor } from '../utils/image-processing';
|
|
10
|
+
import { LRUCache, calculateImageFingerprint } from '../utils/performance';
|
|
11
|
+
import { isWorkerSupported, createWorker as createCustomWorker } from '../utils/worker';
|
|
12
|
+
import { processOCRInWorker, OCRProcessInput } from './ocr-worker';
|
|
13
|
+
import { Disposable } from '../utils/resource-manager';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* OCR处理器选项接口
|
|
17
|
+
*/
|
|
18
|
+
export interface OCRProcessorOptions {
|
|
19
|
+
/** 是否使用Worker线程 */
|
|
20
|
+
useWorker?: boolean;
|
|
21
|
+
/** 是否启用结果缓存 */
|
|
22
|
+
enableCache?: boolean;
|
|
23
|
+
/** 缓存容量 */
|
|
24
|
+
cacheSize?: number;
|
|
25
|
+
/** 图像处理前的最大尺寸 */
|
|
26
|
+
maxImageDimension?: number;
|
|
27
|
+
/** 日志回调函数 */
|
|
28
|
+
logger?: (message: any) => void;
|
|
29
|
+
}
|
|
10
30
|
|
|
11
31
|
/**
|
|
12
32
|
* OCR处理器类
|
|
@@ -29,10 +49,31 @@ import { ImageProcessor } from '../utils/image-processing';
|
|
|
29
49
|
* await ocrProcessor.terminate();
|
|
30
50
|
* ```
|
|
31
51
|
*/
|
|
32
|
-
export class OCRProcessor {
|
|
52
|
+
export class OCRProcessor implements Disposable {
|
|
33
53
|
private worker: any = null;
|
|
54
|
+
private ocrWorker: ReturnType<typeof createCustomWorker<OCRProcessInput, { idCardInfo: IDCardInfo, processingTime: number }>> | null = null;
|
|
55
|
+
private initialized: boolean = false;
|
|
56
|
+
private resultCache: LRUCache<string, IDCardInfo>;
|
|
57
|
+
private options: OCRProcessorOptions;
|
|
34
58
|
|
|
35
|
-
|
|
59
|
+
/**
|
|
60
|
+
* 创建OCR处理器实例
|
|
61
|
+
*
|
|
62
|
+
* @param options OCR处理器选项
|
|
63
|
+
*/
|
|
64
|
+
constructor(options: OCRProcessorOptions = {}) {
|
|
65
|
+
this.options = {
|
|
66
|
+
useWorker: isWorkerSupported(),
|
|
67
|
+
enableCache: true,
|
|
68
|
+
cacheSize: 50,
|
|
69
|
+
maxImageDimension: 1000,
|
|
70
|
+
logger: console.log,
|
|
71
|
+
...options
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// 初始化缓存
|
|
75
|
+
this.resultCache = new LRUCache<string, IDCardInfo>(this.options.cacheSize);
|
|
76
|
+
}
|
|
36
77
|
|
|
37
78
|
/**
|
|
38
79
|
* 初始化OCR引擎
|
|
@@ -42,46 +83,100 @@ export class OCRProcessor {
|
|
|
42
83
|
* @returns {Promise<void>} 初始化完成的Promise
|
|
43
84
|
*/
|
|
44
85
|
async initialize(): Promise<void> {
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
if (this.initialized) return;
|
|
87
|
+
|
|
88
|
+
if (this.options.useWorker) {
|
|
89
|
+
// 使用自定义Worker线程处理OCR
|
|
90
|
+
this.ocrWorker = createCustomWorker<OCRProcessInput, { idCardInfo: IDCardInfo, processingTime: number }>(processOCRInWorker);
|
|
91
|
+
this.initialized = true;
|
|
92
|
+
this.options.logger?.('OCR Worker 初始化完成');
|
|
93
|
+
} else {
|
|
94
|
+
// 使用主线程处理OCR
|
|
95
|
+
this.worker = createWorker({
|
|
96
|
+
logger: this.options.logger
|
|
97
|
+
} as any);
|
|
98
|
+
|
|
99
|
+
await this.worker.load();
|
|
100
|
+
await this.worker.loadLanguage('chi_sim');
|
|
101
|
+
await this.worker.initialize('chi_sim');
|
|
102
|
+
await this.worker.setParameters({
|
|
103
|
+
tessedit_char_whitelist: '0123456789X-年月日一二三四五六七八九十零壹贰叁肆伍陆柒捌玖拾ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz民族汉族满族回族维吾尔族藏族苗族彝族壮族朝鲜族侗族瑶族白族土家族哈尼族哈萨克族傣族黎族傈僳族佤族高山族拉祜族水族东乡族钠西族景颇族柯尔克孜族士族达斡尔族仫佬族羌族布朗族撒拉族毛南族仡佬族锡伯族阿昌族普米族塔吉克族怒族乌孜别克族俄罗斯族鄂温克族德昂族保安族裕固族京族塔塔尔族独龙族鄂伦春族赫哲族门巴族珞巴族基诺族男女性别住址出生公民身份号码签发机关有效期'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.initialized = true;
|
|
107
|
+
this.options.logger?.('OCR引擎初始化完成');
|
|
108
|
+
}
|
|
55
109
|
}
|
|
56
110
|
|
|
57
111
|
/**
|
|
58
112
|
* 处理身份证图像并提取信息
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @param {ImageData} imageData - 身份证图像数据
|
|
63
|
-
* @returns {Promise<IDCardInfo>} 提取到的身份证信息
|
|
113
|
+
* @param imageData 要处理的身份证图像数据
|
|
114
|
+
* @returns 提取的身份证信息
|
|
64
115
|
*/
|
|
65
116
|
async processIDCard(imageData: ImageData): Promise<IDCardInfo> {
|
|
66
|
-
if (!this.
|
|
117
|
+
if (!this.initialized) {
|
|
67
118
|
await this.initialize();
|
|
68
119
|
}
|
|
69
120
|
|
|
70
|
-
//
|
|
71
|
-
|
|
121
|
+
// 计算图像指纹,用于缓存查找
|
|
122
|
+
if (this.options.enableCache) {
|
|
123
|
+
const fingerprint = calculateImageFingerprint(imageData);
|
|
124
|
+
|
|
125
|
+
// 检查缓存中是否有结果
|
|
126
|
+
const cachedResult = this.resultCache.get(fingerprint);
|
|
127
|
+
if (cachedResult) {
|
|
128
|
+
this.options.logger?.('使用缓存的OCR结果');
|
|
129
|
+
return cachedResult;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
72
132
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
133
|
+
// 图像预处理:降低分辨率和增强对比度
|
|
134
|
+
const downsampledImage = ImageProcessor.downsampleForProcessing(imageData, this.options.maxImageDimension);
|
|
135
|
+
const enhancedImage = ImageProcessor.adjustBrightnessContrast(downsampledImage, 15, 25);
|
|
75
136
|
|
|
76
137
|
// OCR识别
|
|
77
138
|
try {
|
|
78
|
-
|
|
139
|
+
let idCardInfo: IDCardInfo;
|
|
140
|
+
|
|
141
|
+
if (this.options.useWorker && this.ocrWorker) {
|
|
142
|
+
// 使用Worker线程处理
|
|
143
|
+
const base64Image = ImageProcessor.imageDataToBase64(enhancedImage);
|
|
144
|
+
|
|
145
|
+
const result = await this.ocrWorker.postMessage({
|
|
146
|
+
imageBase64: base64Image,
|
|
147
|
+
tessWorkerOptions: {
|
|
148
|
+
logger: this.options.logger
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
idCardInfo = result.idCardInfo;
|
|
153
|
+
this.options.logger?.(`OCR处理完成,用时: ${result.processingTime.toFixed(2)}ms`);
|
|
154
|
+
} else {
|
|
155
|
+
// 使用主线程处理
|
|
156
|
+
const startTime = performance.now();
|
|
157
|
+
|
|
158
|
+
// 转换ImageData为Canvas
|
|
159
|
+
const canvas = ImageProcessor.imageDataToCanvas(enhancedImage);
|
|
160
|
+
|
|
161
|
+
const { data } = await this.worker.recognize(canvas);
|
|
162
|
+
|
|
163
|
+
// 解析身份证信息
|
|
164
|
+
idCardInfo = this.parseIDCardText(data.text);
|
|
165
|
+
|
|
166
|
+
const processingTime = performance.now() - startTime;
|
|
167
|
+
this.options.logger?.(`OCR处理完成,用时: ${processingTime.toFixed(2)}ms`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 缓存结果
|
|
171
|
+
if (this.options.enableCache) {
|
|
172
|
+
const fingerprint = calculateImageFingerprint(imageData);
|
|
173
|
+
this.resultCache.set(fingerprint, idCardInfo);
|
|
174
|
+
}
|
|
79
175
|
|
|
80
|
-
|
|
81
|
-
return this.parseIDCardText(data.text);
|
|
176
|
+
return idCardInfo;
|
|
82
177
|
} catch (error) {
|
|
83
|
-
|
|
84
|
-
return {}
|
|
178
|
+
this.options.logger?.(`OCR识别错误: ${error}`);
|
|
179
|
+
return {} as IDCardInfo;
|
|
85
180
|
}
|
|
86
181
|
}
|
|
87
182
|
|
|
@@ -155,6 +250,14 @@ export class OCRProcessor {
|
|
|
155
250
|
return info;
|
|
156
251
|
}
|
|
157
252
|
|
|
253
|
+
/**
|
|
254
|
+
* 清除结果缓存
|
|
255
|
+
*/
|
|
256
|
+
clearCache(): void {
|
|
257
|
+
this.resultCache.clear();
|
|
258
|
+
this.options.logger?.('OCR结果缓存已清除');
|
|
259
|
+
}
|
|
260
|
+
|
|
158
261
|
/**
|
|
159
262
|
* 终止OCR引擎并释放资源
|
|
160
263
|
*
|
|
@@ -165,5 +268,20 @@ export class OCRProcessor {
|
|
|
165
268
|
await this.worker.terminate();
|
|
166
269
|
this.worker = null;
|
|
167
270
|
}
|
|
271
|
+
|
|
272
|
+
if (this.ocrWorker) {
|
|
273
|
+
this.ocrWorker.terminate();
|
|
274
|
+
this.ocrWorker = null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.initialized = false;
|
|
278
|
+
this.options.logger?.('OCR引擎已终止');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 释放资源
|
|
283
|
+
*/
|
|
284
|
+
dispose(): Promise<void> {
|
|
285
|
+
return this.terminate();
|
|
168
286
|
}
|
|
169
287
|
}
|