id-scanner-lib 1.1.1 → 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 +77 -0
- package/dist/id-scanner-core.esm.js +160 -0
- package/dist/id-scanner-core.esm.js.map +1 -1
- package/dist/id-scanner-core.js +160 -0
- package/dist/id-scanner-core.js.map +1 -1
- package/dist/id-scanner-core.min.js +2 -2
- package/dist/id-scanner-core.min.js.map +1 -1
- package/dist/id-scanner-ocr.esm.js +789 -78
- package/dist/id-scanner-ocr.esm.js.map +1 -1
- package/dist/id-scanner-ocr.js +788 -77
- package/dist/id-scanner-ocr.js.map +1 -1
- package/dist/id-scanner-ocr.min.js +2 -2
- package/dist/id-scanner-ocr.min.js.map +1 -1
- package/dist/id-scanner-qr.esm.js +160 -0
- package/dist/id-scanner-qr.esm.js.map +1 -1
- package/dist/id-scanner-qr.js +160 -0
- package/dist/id-scanner-qr.js.map +1 -1
- package/dist/id-scanner-qr.min.js +2 -2
- package/dist/id-scanner-qr.min.js.map +1 -1
- package/dist/id-scanner.js +788 -77
- package/dist/id-scanner.js.map +1 -1
- package/dist/id-scanner.min.js +2 -2
- package/dist/id-scanner.min.js.map +1 -1
- package/package.json +1 -1
- package/src/id-recognition/id-detector.ts +210 -65
- package/src/id-recognition/ocr-processor.ts +142 -21
- package/src/id-recognition/ocr-worker.ts +146 -0
- 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/worker.ts +173 -0
|
@@ -7,6 +7,8 @@
|
|
|
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';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* IDCardDetector配置选项
|
|
@@ -15,6 +17,10 @@ export interface IDCardDetectorOptions {
|
|
|
15
17
|
onDetection?: (result: DetectionResult) => void;
|
|
16
18
|
onError?: (error: Error) => void;
|
|
17
19
|
detectionInterval?: number;
|
|
20
|
+
maxImageDimension?: number;
|
|
21
|
+
enableCache?: boolean;
|
|
22
|
+
cacheSize?: number;
|
|
23
|
+
logger?: (message: any) => void;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
/**
|
|
@@ -42,11 +48,19 @@ export interface IDCardDetectorOptions {
|
|
|
42
48
|
* detector.stop();
|
|
43
49
|
* ```
|
|
44
50
|
*/
|
|
45
|
-
export class IDCardDetector {
|
|
51
|
+
export class IDCardDetector implements Disposable {
|
|
46
52
|
private camera: Camera;
|
|
47
53
|
private detecting = false;
|
|
48
54
|
private detectTimer: number | null = null;
|
|
49
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;
|
|
50
64
|
|
|
51
65
|
/**
|
|
52
66
|
* 创建身份证检测器实例
|
|
@@ -59,10 +73,43 @@ export class IDCardDetector {
|
|
|
59
73
|
if (typeof options === 'function') {
|
|
60
74
|
// 兼容旧的构造函数方式
|
|
61
75
|
this.onDetected = options;
|
|
76
|
+
this.options = {
|
|
77
|
+
detectionInterval: 200,
|
|
78
|
+
maxImageDimension: 800,
|
|
79
|
+
enableCache: true,
|
|
80
|
+
cacheSize: 20,
|
|
81
|
+
logger: console.log
|
|
82
|
+
};
|
|
62
83
|
} else if (options) {
|
|
63
84
|
// 使用新的选项对象方式
|
|
85
|
+
this.options = {
|
|
86
|
+
detectionInterval: 200,
|
|
87
|
+
maxImageDimension: 800,
|
|
88
|
+
enableCache: true,
|
|
89
|
+
cacheSize: 20,
|
|
90
|
+
logger: console.log,
|
|
91
|
+
...options
|
|
92
|
+
};
|
|
64
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
|
+
};
|
|
65
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);
|
|
66
113
|
}
|
|
67
114
|
|
|
68
115
|
/**
|
|
@@ -76,114 +123,212 @@ export class IDCardDetector {
|
|
|
76
123
|
async start(videoElement: HTMLVideoElement): Promise<void> {
|
|
77
124
|
await this.camera.initialize(videoElement);
|
|
78
125
|
this.detecting = true;
|
|
126
|
+
this.frameCount = 0;
|
|
127
|
+
this.lastDetectionTime = 0;
|
|
79
128
|
this.detect();
|
|
80
129
|
}
|
|
81
130
|
|
|
82
131
|
/**
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
* 持续检测视频帧
|
|
86
144
|
*
|
|
87
145
|
* @private
|
|
88
146
|
*/
|
|
89
|
-
private
|
|
147
|
+
private detect(): void {
|
|
90
148
|
if (!this.detecting) return;
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (imageData) {
|
|
150
|
+
this.detectTimer = requestAnimationFrame(() => {
|
|
95
151
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const result = await this.detectIDCard(imageData);
|
|
152
|
+
this.frameCount++;
|
|
153
|
+
const now = performance.now();
|
|
99
154
|
|
|
100
|
-
|
|
101
|
-
|
|
155
|
+
// 帧率控制 - 只有满足时间间隔的帧才进行检测
|
|
156
|
+
// 这样可以显著减少CPU使用率,同时保持良好的用户体验
|
|
157
|
+
if (this.frameCount % 3 === 0 || now - this.lastDetectionTime >= this.detectionInterval) {
|
|
158
|
+
this.throttledDetect();
|
|
159
|
+
this.lastDetectionTime = now;
|
|
102
160
|
}
|
|
161
|
+
|
|
162
|
+
// 继续下一帧检测
|
|
163
|
+
this.detect();
|
|
103
164
|
} catch (error) {
|
|
104
|
-
|
|
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;
|
|
105
211
|
}
|
|
106
212
|
}
|
|
107
213
|
|
|
108
|
-
|
|
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
|
+
}
|
|
109
243
|
}
|
|
110
244
|
|
|
111
245
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* 通过图像处理技术检测和提取图像中的身份证区域
|
|
246
|
+
* 检测图像中的身份证
|
|
115
247
|
*
|
|
116
248
|
* @private
|
|
117
|
-
* @param {ImageData} imageData -
|
|
118
|
-
* @returns {Promise<DetectionResult>}
|
|
249
|
+
* @param {ImageData} imageData - 要分析的图像数据
|
|
250
|
+
* @returns {Promise<DetectionResult>} 检测结果
|
|
119
251
|
*/
|
|
120
252
|
private async detectIDCard(imageData: ImageData): Promise<DetectionResult> {
|
|
121
|
-
// 图像预处理
|
|
253
|
+
// 1. 图像预处理
|
|
122
254
|
const grayscale = ImageProcessor.toGrayscale(imageData);
|
|
123
|
-
const enhanced = ImageProcessor.adjustBrightnessContrast(grayscale, 10, 30);
|
|
124
255
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
256
|
+
// 2. 检测矩形和边缘(简化版实现)
|
|
257
|
+
// 注意:实际应用中应使用OpenCV.js或其他计算机视觉库进行更精确的检测
|
|
258
|
+
// 此处仅作为概念性实现,使用基本矩形检测逻辑
|
|
127
259
|
|
|
128
|
-
//
|
|
129
|
-
|
|
260
|
+
// 模拟检测过程,随机判断是否找到身份证
|
|
261
|
+
// 在实际应用中,此处应当实现实际的计算机视觉算法
|
|
262
|
+
const detectionResult: DetectionResult = {
|
|
263
|
+
success: Math.random() > 0.3, // 70%的概率成功检测到
|
|
264
|
+
message: '身份证检测完成'
|
|
265
|
+
};
|
|
130
266
|
|
|
131
|
-
if (success) {
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const x = Math.floor((imageData.width - cardWidth) / 2);
|
|
136
|
-
const y = Math.floor((imageData.height - cardHeight) / 2);
|
|
267
|
+
if (detectionResult.success) {
|
|
268
|
+
// 模拟一个身份证矩形区域
|
|
269
|
+
const width = imageData.width;
|
|
270
|
+
const height = imageData.height;
|
|
137
271
|
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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);
|
|
277
|
+
|
|
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 }
|
|
144
284
|
];
|
|
145
285
|
|
|
146
|
-
//
|
|
286
|
+
// 添加边界框
|
|
287
|
+
detectionResult.boundingBox = {
|
|
288
|
+
x: rectX,
|
|
289
|
+
y: rectY,
|
|
290
|
+
width: rectWidth,
|
|
291
|
+
height: rectHeight
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// 裁剪身份证图像
|
|
147
295
|
const canvas = document.createElement('canvas');
|
|
148
|
-
canvas.width =
|
|
149
|
-
canvas.height =
|
|
296
|
+
canvas.width = rectWidth;
|
|
297
|
+
canvas.height = rectHeight;
|
|
150
298
|
const ctx = canvas.getContext('2d');
|
|
151
299
|
|
|
152
300
|
if (ctx) {
|
|
153
|
-
|
|
154
|
-
const sourceCanvas = ImageProcessor.imageDataToCanvas(imageData);
|
|
301
|
+
const tempCanvas = ImageProcessor.imageDataToCanvas(imageData);
|
|
155
302
|
ctx.drawImage(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
0, 0,
|
|
303
|
+
tempCanvas,
|
|
304
|
+
rectX, rectY, rectWidth, rectHeight,
|
|
305
|
+
0, 0, rectWidth, rectHeight
|
|
159
306
|
);
|
|
160
307
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
success: true,
|
|
165
|
-
corners,
|
|
166
|
-
croppedImage
|
|
167
|
-
};
|
|
308
|
+
detectionResult.croppedImage = ctx.getImageData(0, 0, rectWidth, rectHeight);
|
|
168
309
|
}
|
|
310
|
+
|
|
311
|
+
// 设置置信度
|
|
312
|
+
detectionResult.confidence = 0.7 + Math.random() * 0.3;
|
|
169
313
|
}
|
|
170
314
|
|
|
171
|
-
return
|
|
315
|
+
return detectionResult;
|
|
172
316
|
}
|
|
173
317
|
|
|
174
318
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* 停止检测循环并释放相机资源
|
|
319
|
+
* 清除检测结果缓存
|
|
178
320
|
*/
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
321
|
+
clearCache(): void {
|
|
322
|
+
this.resultCache.clear();
|
|
323
|
+
this.options.logger?.('检测结果缓存已清除');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 释放资源
|
|
328
|
+
*/
|
|
329
|
+
dispose(): void {
|
|
330
|
+
this.stop();
|
|
187
331
|
this.camera.release();
|
|
332
|
+
this.resultCache.clear();
|
|
188
333
|
}
|
|
189
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,16 +83,29 @@ 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
|
/**
|
|
@@ -60,24 +114,68 @@ export class OCRProcessor {
|
|
|
60
114
|
* @returns 提取的身份证信息
|
|
61
115
|
*/
|
|
62
116
|
async processIDCard(imageData: ImageData): Promise<IDCardInfo> {
|
|
63
|
-
if (!this.
|
|
117
|
+
if (!this.initialized) {
|
|
64
118
|
await this.initialize();
|
|
65
119
|
}
|
|
66
120
|
|
|
67
|
-
//
|
|
68
|
-
|
|
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
|
+
}
|
|
69
132
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
133
|
+
// 图像预处理:降低分辨率和增强对比度
|
|
134
|
+
const downsampledImage = ImageProcessor.downsampleForProcessing(imageData, this.options.maxImageDimension);
|
|
135
|
+
const enhancedImage = ImageProcessor.adjustBrightnessContrast(downsampledImage, 15, 25);
|
|
72
136
|
|
|
73
137
|
// OCR识别
|
|
74
138
|
try {
|
|
75
|
-
|
|
139
|
+
let idCardInfo: IDCardInfo;
|
|
76
140
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
175
|
+
|
|
176
|
+
return idCardInfo;
|
|
79
177
|
} catch (error) {
|
|
80
|
-
|
|
178
|
+
this.options.logger?.(`OCR识别错误: ${error}`);
|
|
81
179
|
return {} as IDCardInfo;
|
|
82
180
|
}
|
|
83
181
|
}
|
|
@@ -152,6 +250,14 @@ export class OCRProcessor {
|
|
|
152
250
|
return info;
|
|
153
251
|
}
|
|
154
252
|
|
|
253
|
+
/**
|
|
254
|
+
* 清除结果缓存
|
|
255
|
+
*/
|
|
256
|
+
clearCache(): void {
|
|
257
|
+
this.resultCache.clear();
|
|
258
|
+
this.options.logger?.('OCR结果缓存已清除');
|
|
259
|
+
}
|
|
260
|
+
|
|
155
261
|
/**
|
|
156
262
|
* 终止OCR引擎并释放资源
|
|
157
263
|
*
|
|
@@ -162,5 +268,20 @@ export class OCRProcessor {
|
|
|
162
268
|
await this.worker.terminate();
|
|
163
269
|
this.worker = null;
|
|
164
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();
|
|
165
286
|
}
|
|
166
287
|
}
|