id-scanner-lib 1.1.1 → 1.2.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 +80 -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 +791 -78
- package/dist/id-scanner-ocr.esm.js.map +1 -1
- package/dist/id-scanner-ocr.js +790 -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 +790 -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 +210 -0
- package/src/utils/resource-manager.ts +198 -0
- package/src/utils/worker.ts +173 -0
package/README.md
CHANGED
|
@@ -277,6 +277,83 @@ const scanner = new IDScanner({
|
|
|
277
277
|
|
|
278
278
|
3. **关闭不必要的扫描**:不使用时及时停止扫描,节省资源。
|
|
279
279
|
|
|
280
|
+
## 高级性能优化
|
|
281
|
+
|
|
282
|
+
为满足生产环境的高性能需求,库内部实现了多种先进的性能优化策略:
|
|
283
|
+
|
|
284
|
+
### 1. Web Worker多线程处理
|
|
285
|
+
|
|
286
|
+
OCR和图像处理等计算密集型任务被移至Web Worker线程中执行,避免阻塞主线程UI渲染。
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// 启用Worker线程处理
|
|
290
|
+
const ocrProcessor = new OCRProcessor({
|
|
291
|
+
useWorker: true // 默认值,可以明确指定
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// 初始化会在后台线程创建OCR处理能力
|
|
295
|
+
await ocrProcessor.initialize();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 2. 内存管理与资源自动释放
|
|
299
|
+
|
|
300
|
+
实现了资源自动管理机制,防止内存泄漏,特别适合长时间运行的应用场景。
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
// 所有核心类都实现了Disposable接口
|
|
304
|
+
// 可以通过ResourceManager统一管理
|
|
305
|
+
import { ResourceManager } from 'id-scanner-lib/core';
|
|
306
|
+
|
|
307
|
+
const resourceManager = new ResourceManager();
|
|
308
|
+
resourceManager.register('scanner', scanner, true); // 最后一个参数为是否自动释放
|
|
309
|
+
|
|
310
|
+
// 资源会在不使用一段时间后自动释放
|
|
311
|
+
// 也可以手动释放
|
|
312
|
+
await resourceManager.dispose('scanner');
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### 3. 图像预处理优化
|
|
316
|
+
|
|
317
|
+
自动降低分析图像尺寸,在保持识别率的同时大幅提升处理速度。
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// 可以配置最大图像尺寸
|
|
321
|
+
const ocrProcessor = new OCRProcessor({
|
|
322
|
+
maxImageDimension: 1000 // 设置图像处理的最大尺寸
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// 内部图像处理会自动缩小过大的图像
|
|
326
|
+
// ImageProcessor.downsampleForProcessing(imageData, maxDimension);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 4. 节流处理
|
|
330
|
+
|
|
331
|
+
对视频帧分析应用节流策略,避免过度计算,降低设备发热和电池消耗。
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
// 设置检测间隔毫秒数
|
|
335
|
+
const detector = new IDCardDetector({
|
|
336
|
+
detectionInterval: 300 // 默认200ms,可根据需要调整
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 5. 识别结果缓存
|
|
341
|
+
|
|
342
|
+
通过图像指纹技术,缓存识别结果,短时间内相同图像避免重复识别。
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
// 启用结果缓存
|
|
346
|
+
const ocrProcessor = new OCRProcessor({
|
|
347
|
+
enableCache: true, // 默认值,可明确指定
|
|
348
|
+
cacheSize: 50 // 缓存最近50个结果
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// 清除缓存
|
|
352
|
+
ocrProcessor.clearCache();
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
这些性能优化在默认配置下已经启用,可根据应用特定需求进行调整。在移动设备上,这些优化可以将处理速度提升3-5倍,同时显著降低电池消耗。
|
|
356
|
+
|
|
280
357
|
## 常见问题解答
|
|
281
358
|
|
|
282
359
|
**Q: 为什么我在移动设备上无法访问摄像头?**
|
|
@@ -346,6 +423,9 @@ A: 库的大小约为1MB,主要是因为包含OCR引擎。可以考虑按需
|
|
|
346
423
|
5. **版本更新记录**:
|
|
347
424
|
- v1.0.0: 首次发布版本
|
|
348
425
|
- v1.1.0: 模块化重构,实现按需加载,大幅减小体积
|
|
426
|
+
- v1.2.0: 实现多种性能优化策略,提升处理速度和降低资源占用
|
|
427
|
+
- v1.2.1: 修复类型错误和代码健壮性问题
|
|
428
|
+
- v1.2.2: 完善类型定义,修复所有类型检查警告
|
|
349
429
|
|
|
350
430
|
### 运行时性能
|
|
351
431
|
1. **摄像头参数自动优化**:根据设备性能自动调整摄像头分辨率
|
|
@@ -10465,6 +10465,166 @@ class ImageProcessor {
|
|
|
10465
10465
|
}
|
|
10466
10466
|
return imageData;
|
|
10467
10467
|
}
|
|
10468
|
+
/**
|
|
10469
|
+
* 降低图像分辨率以提高处理速度
|
|
10470
|
+
*
|
|
10471
|
+
* 对于OCR和图像分析,降低分辨率可以在保持识别率的同时大幅提升处理速度
|
|
10472
|
+
*
|
|
10473
|
+
* @param {ImageData} imageData - 原图像数据
|
|
10474
|
+
* @param {number} [maxDimension=1000] - 目标最大尺寸(宽或高)
|
|
10475
|
+
* @returns {ImageData} 处理后的图像数据
|
|
10476
|
+
*/
|
|
10477
|
+
static downsampleForProcessing(imageData, maxDimension = 1000) {
|
|
10478
|
+
const { width, height } = imageData;
|
|
10479
|
+
// 如果图像尺寸已经小于或等于目标尺寸,则无需处理
|
|
10480
|
+
if (width <= maxDimension && height <= maxDimension) {
|
|
10481
|
+
return imageData;
|
|
10482
|
+
}
|
|
10483
|
+
// 计算缩放比例,保持宽高比
|
|
10484
|
+
const scale = maxDimension / Math.max(width, height);
|
|
10485
|
+
const newWidth = Math.round(width * scale);
|
|
10486
|
+
const newHeight = Math.round(height * scale);
|
|
10487
|
+
// 调整图像大小
|
|
10488
|
+
return this.resize(imageData, newWidth, newHeight);
|
|
10489
|
+
}
|
|
10490
|
+
/**
|
|
10491
|
+
* 转换图像为Base64格式,方便在Worker线程中传递
|
|
10492
|
+
*
|
|
10493
|
+
* @param {ImageData} imageData - 原图像数据
|
|
10494
|
+
* @returns {string} base64编码的图像数据
|
|
10495
|
+
*/
|
|
10496
|
+
static imageDataToBase64(imageData) {
|
|
10497
|
+
const canvas = this.imageDataToCanvas(imageData);
|
|
10498
|
+
return canvas.toDataURL('image/jpeg', 0.7); // 使用较低质量的JPEG格式减少数据量
|
|
10499
|
+
}
|
|
10500
|
+
/**
|
|
10501
|
+
* 从Base64字符串还原图像数据
|
|
10502
|
+
*
|
|
10503
|
+
* @param {string} base64 - base64编码的图像数据
|
|
10504
|
+
* @returns {Promise<ImageData>} 还原的图像数据
|
|
10505
|
+
*/
|
|
10506
|
+
static async base64ToImageData(base64) {
|
|
10507
|
+
return new Promise((resolve, reject) => {
|
|
10508
|
+
const img = new Image();
|
|
10509
|
+
img.onload = () => {
|
|
10510
|
+
const canvas = document.createElement('canvas');
|
|
10511
|
+
canvas.width = img.width;
|
|
10512
|
+
canvas.height = img.height;
|
|
10513
|
+
const ctx = canvas.getContext('2d');
|
|
10514
|
+
if (!ctx) {
|
|
10515
|
+
reject(new Error('无法创建Canvas上下文'));
|
|
10516
|
+
return;
|
|
10517
|
+
}
|
|
10518
|
+
ctx.drawImage(img, 0, 0);
|
|
10519
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
10520
|
+
resolve(imageData);
|
|
10521
|
+
};
|
|
10522
|
+
img.onerror = () => {
|
|
10523
|
+
reject(new Error('图像加载失败'));
|
|
10524
|
+
};
|
|
10525
|
+
img.src = base64;
|
|
10526
|
+
});
|
|
10527
|
+
}
|
|
10528
|
+
/**
|
|
10529
|
+
* 使用Web Worker并行处理图像
|
|
10530
|
+
* 此方法将图像分割为多个部分,并行处理以提高性能
|
|
10531
|
+
*
|
|
10532
|
+
* @param {ImageData} imageData - 原图像数据
|
|
10533
|
+
* @param {Function} processingFunction - 处理函数,接收ImageData返回ImageData
|
|
10534
|
+
* @param {number} [chunks=4] - 分割的块数
|
|
10535
|
+
* @returns {Promise<ImageData>} 处理后的图像数据
|
|
10536
|
+
*/
|
|
10537
|
+
static async processImageInParallel(imageData, processingFunction, chunks = 4) {
|
|
10538
|
+
// 如果不支持Worker或图像太小,直接处理
|
|
10539
|
+
if (typeof Worker === 'undefined' || imageData.width * imageData.height < 100000) {
|
|
10540
|
+
return processingFunction(imageData);
|
|
10541
|
+
}
|
|
10542
|
+
// 创建结果canvas
|
|
10543
|
+
const resultCanvas = document.createElement('canvas');
|
|
10544
|
+
resultCanvas.width = imageData.width;
|
|
10545
|
+
resultCanvas.height = imageData.height;
|
|
10546
|
+
const resultCtx = resultCanvas.getContext('2d');
|
|
10547
|
+
if (!resultCtx) {
|
|
10548
|
+
throw new Error('无法创建Canvas上下文');
|
|
10549
|
+
}
|
|
10550
|
+
// 根据图像特性确定分割方向和每块大小
|
|
10551
|
+
const isWide = imageData.width > imageData.height;
|
|
10552
|
+
const chunkSize = Math.floor((isWide ? imageData.width : imageData.height) / chunks);
|
|
10553
|
+
// 创建Worker处理每个块
|
|
10554
|
+
const promises = [];
|
|
10555
|
+
for (let i = 0; i < chunks; i++) {
|
|
10556
|
+
const chunkCanvas = document.createElement('canvas');
|
|
10557
|
+
const chunkCtx = chunkCanvas.getContext('2d');
|
|
10558
|
+
if (!chunkCtx)
|
|
10559
|
+
continue;
|
|
10560
|
+
let chunkImageData;
|
|
10561
|
+
if (isWide) {
|
|
10562
|
+
// 水平分割
|
|
10563
|
+
const startX = i * chunkSize;
|
|
10564
|
+
const width = (i === chunks - 1) ? imageData.width - startX : chunkSize;
|
|
10565
|
+
chunkCanvas.width = width;
|
|
10566
|
+
chunkCanvas.height = imageData.height;
|
|
10567
|
+
// 复制原图像数据到分块
|
|
10568
|
+
const tempCanvas = this.imageDataToCanvas(imageData);
|
|
10569
|
+
chunkCtx.drawImage(tempCanvas, startX, 0, width, imageData.height, 0, 0, width, imageData.height);
|
|
10570
|
+
chunkImageData = chunkCtx.getImageData(0, 0, width, imageData.height);
|
|
10571
|
+
}
|
|
10572
|
+
else {
|
|
10573
|
+
// 垂直分割
|
|
10574
|
+
const startY = i * chunkSize;
|
|
10575
|
+
const height = (i === chunks - 1) ? imageData.height - startY : chunkSize;
|
|
10576
|
+
chunkCanvas.width = imageData.width;
|
|
10577
|
+
chunkCanvas.height = height;
|
|
10578
|
+
// 复制原图像数据到分块
|
|
10579
|
+
const tempCanvas = this.imageDataToCanvas(imageData);
|
|
10580
|
+
chunkCtx.drawImage(tempCanvas, 0, startY, imageData.width, height, 0, 0, imageData.width, height);
|
|
10581
|
+
chunkImageData = chunkCtx.getImageData(0, 0, imageData.width, height);
|
|
10582
|
+
}
|
|
10583
|
+
// 使用Worker处理
|
|
10584
|
+
const workerCode = `
|
|
10585
|
+
self.onmessage = function(e) {
|
|
10586
|
+
const imageData = e.data.imageData;
|
|
10587
|
+
const processingFunction = ${processingFunction.toString()};
|
|
10588
|
+
const result = processingFunction(imageData);
|
|
10589
|
+
self.postMessage({ result, index: e.data.index }, [result.data.buffer]);
|
|
10590
|
+
}
|
|
10591
|
+
`;
|
|
10592
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
10593
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
10594
|
+
const worker = new Worker(workerUrl);
|
|
10595
|
+
const promise = new Promise((resolve) => {
|
|
10596
|
+
worker.onmessage = function (e) {
|
|
10597
|
+
resolve(e.data);
|
|
10598
|
+
worker.terminate();
|
|
10599
|
+
URL.revokeObjectURL(workerUrl);
|
|
10600
|
+
};
|
|
10601
|
+
// 传输数据
|
|
10602
|
+
worker.postMessage({
|
|
10603
|
+
imageData: chunkImageData,
|
|
10604
|
+
index: i
|
|
10605
|
+
}, [chunkImageData.data.buffer]);
|
|
10606
|
+
});
|
|
10607
|
+
promises.push(promise);
|
|
10608
|
+
}
|
|
10609
|
+
// 等待所有Worker完成并组合结果
|
|
10610
|
+
const results = await Promise.all(promises);
|
|
10611
|
+
// 按索引排序结果
|
|
10612
|
+
results.sort((a, b) => a.index - b.index);
|
|
10613
|
+
// 将处理后的块绘制到结果canvas
|
|
10614
|
+
for (let i = 0; i < results.length; i++) {
|
|
10615
|
+
const { result } = results[i];
|
|
10616
|
+
const tempCanvas = this.imageDataToCanvas(result);
|
|
10617
|
+
if (isWide) {
|
|
10618
|
+
const startX = i * chunkSize;
|
|
10619
|
+
resultCtx.drawImage(tempCanvas, startX, 0);
|
|
10620
|
+
}
|
|
10621
|
+
else {
|
|
10622
|
+
const startY = i * chunkSize;
|
|
10623
|
+
resultCtx.drawImage(tempCanvas, 0, startY);
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return resultCtx.getImageData(0, 0, imageData.width, imageData.height);
|
|
10627
|
+
}
|
|
10468
10628
|
}
|
|
10469
10629
|
|
|
10470
10630
|
/**
|