id-scanner-lib 1.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/src/index.ts ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * @file ID扫描识别库主入口文件
3
+ * @description 提供身份证识别与二维码、条形码扫描功能的纯前端TypeScript库
4
+ * @module IDScannerLib
5
+ * @version 1.0.0
6
+ * @license MIT
7
+ */
8
+
9
+ import { QRScanner, QRScannerOptions } from './scanner/qr-scanner';
10
+ import { BarcodeScanner, BarcodeScannerOptions } from './scanner/barcode-scanner';
11
+ import { IDCardDetector } from './id-recognition/id-detector';
12
+ import { OCRProcessor } from './id-recognition/ocr-processor';
13
+ import { DataExtractor } from './id-recognition/data-extractor';
14
+ import { Camera, CameraOptions } from './utils/camera';
15
+ import { ImageProcessor } from './utils/image-processing';
16
+ import { IDCardInfo, DetectionResult } from './utils/types';
17
+
18
+ /**
19
+ * IDScanner配置选项接口
20
+ * @interface IDScannerOptions
21
+ * @property {CameraOptions} [cameraOptions] - 相机配置选项
22
+ * @property {QRScannerOptions} [qrScannerOptions] - 二维码扫描配置选项
23
+ * @property {BarcodeScannerOptions} [barcodeScannerOptions] - 条形码扫描配置选项
24
+ * @property {Function} [onQRCodeScanned] - 二维码识别成功回调
25
+ * @property {Function} [onBarcodeScanned] - 条形码识别成功回调
26
+ * @property {Function} [onIDCardScanned] - 身份证识别成功回调
27
+ * @property {Function} [onError] - 错误处理回调
28
+ */
29
+ export interface IDScannerOptions {
30
+ cameraOptions?: CameraOptions;
31
+ qrScannerOptions?: QRScannerOptions;
32
+ barcodeScannerOptions?: BarcodeScannerOptions;
33
+ onQRCodeScanned?: (result: string) => void;
34
+ onBarcodeScanned?: (result: string) => void;
35
+ onIDCardScanned?: (info: IDCardInfo) => void;
36
+ onError?: (error: Error) => void;
37
+ }
38
+
39
+ /**
40
+ * IDScanner 主类
41
+ *
42
+ * 整合二维码、条形码扫描和身份证识别功能,提供统一的接口
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // 创建扫描器实例
47
+ * const scanner = new IDScanner({
48
+ * onQRCodeScanned: (result) => {
49
+ * console.log('扫描到二维码:', result);
50
+ * },
51
+ * onIDCardScanned: (info) => {
52
+ * console.log('识别到身份证信息:', info);
53
+ * }
54
+ * });
55
+ *
56
+ * // 初始化OCR引擎和相关资源
57
+ * await scanner.initialize();
58
+ *
59
+ * // 启动二维码扫描
60
+ * const videoElement = document.getElementById('video');
61
+ * await scanner.startQRScanner(videoElement);
62
+ *
63
+ * // 停止扫描
64
+ * scanner.stop();
65
+ *
66
+ * // 使用结束后释放资源
67
+ * scanner.terminate();
68
+ * ```
69
+ */
70
+ export class IDScanner {
71
+ private qrScanner: QRScanner;
72
+ private barcodeScanner: BarcodeScanner;
73
+ private idDetector: IDCardDetector;
74
+ private ocrProcessor: OCRProcessor;
75
+ private scanMode: 'qr' | 'barcode' | 'idcard' = 'qr';
76
+
77
+ /**
78
+ * 创建IDScanner实例
79
+ * @param {IDScannerOptions} [options] - 配置选项
80
+ */
81
+ constructor(private options: IDScannerOptions = {}) {
82
+ this.qrScanner = new QRScanner({
83
+ ...options.qrScannerOptions,
84
+ onScan: this.handleQRScan.bind(this),
85
+ onError: this.handleError.bind(this)
86
+ });
87
+
88
+ this.barcodeScanner = new BarcodeScanner({
89
+ ...options.barcodeScannerOptions,
90
+ onScan: this.handleBarcodeScan.bind(this),
91
+ onError: this.handleError.bind(this)
92
+ });
93
+
94
+ this.idDetector = new IDCardDetector(this.handleIDDetection.bind(this));
95
+ this.ocrProcessor = new OCRProcessor();
96
+ }
97
+
98
+ /**
99
+ * 初始化OCR引擎和相关资源
100
+ *
101
+ * @returns {Promise<void>} 初始化完成的Promise
102
+ * @throws 如果初始化失败,将抛出错误
103
+ */
104
+ async initialize(): Promise<void> {
105
+ try {
106
+ await this.ocrProcessor.initialize();
107
+ } catch (error) {
108
+ this.handleError(error instanceof Error ? error : new Error(String(error)));
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 启动二维码扫描
114
+ *
115
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
116
+ * @returns {Promise<void>} 启动完成的Promise
117
+ */
118
+ async startQRScanner(videoElement: HTMLVideoElement): Promise<void> {
119
+ this.scanMode = 'qr';
120
+ await this.qrScanner.start(videoElement);
121
+ }
122
+
123
+ /**
124
+ * 启动条形码扫描
125
+ *
126
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
127
+ * @returns {Promise<void>} 启动完成的Promise
128
+ */
129
+ async startBarcodeScanner(videoElement: HTMLVideoElement): Promise<void> {
130
+ this.scanMode = 'barcode';
131
+ await this.barcodeScanner.start(videoElement);
132
+ }
133
+
134
+ /**
135
+ * 启动身份证扫描识别
136
+ *
137
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
138
+ * @returns {Promise<void>} 启动完成的Promise
139
+ */
140
+ async startIDCardScanner(videoElement: HTMLVideoElement): Promise<void> {
141
+ this.scanMode = 'idcard';
142
+ await this.idDetector.start(videoElement);
143
+ }
144
+
145
+ /**
146
+ * 停止当前扫描
147
+ */
148
+ stop(): void {
149
+ if (this.scanMode === 'qr') {
150
+ this.qrScanner.stop();
151
+ } else if (this.scanMode === 'barcode') {
152
+ this.barcodeScanner.stop();
153
+ } else {
154
+ this.idDetector.stop();
155
+ }
156
+ }
157
+
158
+ /**
159
+ * 处理二维码扫描结果
160
+ * @private
161
+ * @param {string} result - 扫描到的二维码内容
162
+ */
163
+ private handleQRScan(result: string): void {
164
+ if (this.options.onQRCodeScanned) {
165
+ this.options.onQRCodeScanned(result);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 处理条形码扫描结果
171
+ * @private
172
+ * @param {string} result - 扫描到的条形码内容
173
+ */
174
+ private handleBarcodeScan(result: string): void {
175
+ if (this.options.onBarcodeScanned) {
176
+ this.options.onBarcodeScanned(result);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 处理身份证检测结果
182
+ * @private
183
+ * @param {DetectionResult} result - 身份证检测结果
184
+ */
185
+ private async handleIDDetection(result: DetectionResult): Promise<void> {
186
+ if (result.success && result.croppedImage) {
187
+ try {
188
+ const idInfo = await this.ocrProcessor.processIDCard(result.croppedImage);
189
+
190
+ // 使用数据提取工具增强身份证信息
191
+ const enhancedInfo = DataExtractor.enhanceIDCardInfo(idInfo);
192
+
193
+ if (this.options.onIDCardScanned) {
194
+ this.options.onIDCardScanned(enhancedInfo);
195
+ }
196
+ } catch (error) {
197
+ this.handleError(error instanceof Error ? error : new Error(String(error)));
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * 处理错误
204
+ * @private
205
+ * @param {Error} error - 错误对象
206
+ */
207
+ private handleError(error: Error): void {
208
+ if (this.options.onError) {
209
+ this.options.onError(error);
210
+ } else {
211
+ console.error('ID扫描器错误:', error);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * 终止所有扫描并释放资源
217
+ *
218
+ * @returns {Promise<void>} 资源释放完成的Promise
219
+ */
220
+ async terminate(): Promise<void> {
221
+ this.stop();
222
+ await this.ocrProcessor.terminate();
223
+ }
224
+ }
225
+
226
+ // 导出公共API
227
+ export { Camera, QRScanner, BarcodeScanner, IDCardDetector, OCRProcessor, DataExtractor, ImageProcessor };
228
+ export type { CameraOptions, QRScannerOptions, BarcodeScannerOptions, IDCardInfo, DetectionResult };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @file 条形码扫描模块
3
+ * @description 提供实时条形码扫描和识别功能
4
+ * @module BarcodeScanner
5
+ */
6
+
7
+ import { Camera } from '../utils/camera';
8
+ import { ImageProcessor } from '../utils/image-processing';
9
+
10
+ /**
11
+ * 条形码扫描器配置选项
12
+ *
13
+ * @interface BarcodeScannerOptions
14
+ * @property {number} [scanInterval] - 扫描间隔时间(毫秒),默认为200ms
15
+ * @property {Function} [onScan] - 扫描成功回调函数
16
+ * @property {Function} [onError] - 错误处理回调函数
17
+ */
18
+ export interface BarcodeScannerOptions {
19
+ scanInterval?: number;
20
+ onScan?: (result: string) => void;
21
+ onError?: (error: Error) => void;
22
+ }
23
+
24
+ /**
25
+ * 条形码扫描器类
26
+ *
27
+ * 提供实时扫描和识别摄像头中的条形码的功能
28
+ * 注意:当前实现是简化版,实际项目中建议集成专门的条形码识别库如ZXing或Quagga.js
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // 创建条形码扫描器
33
+ * const barcodeScanner = new BarcodeScanner({
34
+ * scanInterval: 100, // 每100ms扫描一次
35
+ * onScan: (result) => {
36
+ * console.log('扫描到条形码:', result);
37
+ * },
38
+ * onError: (error) => {
39
+ * console.error('扫描错误:', error);
40
+ * }
41
+ * });
42
+ *
43
+ * // 启动扫描
44
+ * const videoElement = document.getElementById('video') as HTMLVideoElement;
45
+ * await barcodeScanner.start(videoElement);
46
+ *
47
+ * // 停止扫描
48
+ * barcodeScanner.stop();
49
+ * ```
50
+ */
51
+ export class BarcodeScanner {
52
+ private camera: Camera;
53
+ private scanning = false;
54
+ private scanTimer: number | null = null;
55
+
56
+ /**
57
+ * 创建条形码扫描器实例
58
+ *
59
+ * @param {BarcodeScannerOptions} [options] - 扫描器配置选项
60
+ */
61
+ constructor(private options: BarcodeScannerOptions = {}) {
62
+ this.options = {
63
+ scanInterval: 200,
64
+ ...options
65
+ };
66
+
67
+ this.camera = new Camera();
68
+ }
69
+
70
+ /**
71
+ * 启动条形码扫描
72
+ *
73
+ * 初始化相机并开始连续扫描视频帧中的条形码
74
+ *
75
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
76
+ * @returns {Promise<void>} 启动完成的Promise
77
+ * @throws 如果无法访问相机,将通过onError回调报告错误
78
+ */
79
+ async start(videoElement: HTMLVideoElement): Promise<void> {
80
+ try {
81
+ await this.camera.initialize(videoElement);
82
+ this.scanning = true;
83
+ this.scan();
84
+ } catch (error) {
85
+ if (this.options.onError) {
86
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 执行一次条形码扫描
93
+ *
94
+ * 内部方法,捕获当前视频帧并尝试识别其中的条形码
95
+ *
96
+ * @private
97
+ */
98
+ private scan(): void {
99
+ if (!this.scanning) return;
100
+
101
+ const imageData = this.camera.captureFrame();
102
+
103
+ if (imageData) {
104
+ try {
105
+ // 图像预处理,提高识别率
106
+ const enhancedImage = ImageProcessor.adjustBrightnessContrast(
107
+ ImageProcessor.toGrayscale(imageData),
108
+ 10, // 亮度
109
+ 20 // 对比度
110
+ );
111
+
112
+ // 这里实际项目中可以集成第三方条形码扫描库
113
+ // 如 ZXing 或 QuaggaJS
114
+ // 简化实现,这里仅为示例
115
+ this.detectBarcode(enhancedImage);
116
+ } catch (error) {
117
+ console.error('条形码扫描错误:', error);
118
+ }
119
+ }
120
+
121
+ this.scanTimer = window.setTimeout(() => this.scan(), this.options.scanInterval);
122
+ }
123
+
124
+ /**
125
+ * 条形码检测方法
126
+ *
127
+ * 注意:这是一个简化实现,实际需要集成专门的条形码识别库
128
+ *
129
+ * @private
130
+ * @param {ImageData} imageData - 要检测条形码的图像数据
131
+ */
132
+ private detectBarcode(imageData: ImageData): void {
133
+ // 这里应集成条形码识别库
134
+ // 如 ZXing 或 QuaggaJS
135
+
136
+ // 简化示例,实际项目中请替换为真实实现
137
+ console.log('正在扫描条形码...');
138
+
139
+ // 模拟找到条形码
140
+ if (Math.random() > 0.95) {
141
+ const mockResult = '6901234567890'; // 模拟条形码结果
142
+
143
+ if (this.options.onScan) {
144
+ this.options.onScan(mockResult);
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 停止条形码扫描
151
+ *
152
+ * 停止扫描循环并释放相机资源
153
+ */
154
+ stop(): void {
155
+ this.scanning = false;
156
+
157
+ if (this.scanTimer) {
158
+ clearTimeout(this.scanTimer);
159
+ this.scanTimer = null;
160
+ }
161
+
162
+ this.camera.release();
163
+ }
164
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @file 二维码扫描模块
3
+ * @description 提供实时二维码扫描和识别功能
4
+ * @module QRScanner
5
+ */
6
+
7
+ import jsQR from 'jsqr';
8
+ import { Camera } from '../utils/camera';
9
+
10
+ /**
11
+ * 二维码扫描器配置选项
12
+ *
13
+ * @interface QRScannerOptions
14
+ * @property {number} [scanInterval] - 扫描间隔时间(毫秒),默认为200ms
15
+ * @property {Function} [onScan] - 扫描成功回调函数
16
+ * @property {Function} [onError] - 错误处理回调函数
17
+ */
18
+ export interface QRScannerOptions {
19
+ scanInterval?: number;
20
+ onScan?: (result: string) => void;
21
+ onError?: (error: Error) => void;
22
+ }
23
+
24
+ /**
25
+ * 二维码扫描器类
26
+ *
27
+ * 提供实时扫描和识别摄像头中的二维码的功能
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // 创建二维码扫描器
32
+ * const qrScanner = new QRScanner({
33
+ * scanInterval: 100, // 每100ms扫描一次
34
+ * onScan: (result) => {
35
+ * console.log('扫描到二维码:', result);
36
+ * },
37
+ * onError: (error) => {
38
+ * console.error('扫描错误:', error);
39
+ * }
40
+ * });
41
+ *
42
+ * // 启动扫描
43
+ * const videoElement = document.getElementById('video') as HTMLVideoElement;
44
+ * await qrScanner.start(videoElement);
45
+ *
46
+ * // 停止扫描
47
+ * qrScanner.stop();
48
+ * ```
49
+ */
50
+ export class QRScanner {
51
+ private camera: Camera;
52
+ private scanning = false;
53
+ private scanTimer: number | null = null;
54
+
55
+ /**
56
+ * 创建二维码扫描器实例
57
+ *
58
+ * @param {QRScannerOptions} [options] - 扫描器配置选项
59
+ */
60
+ constructor(private options: QRScannerOptions = {}) {
61
+ this.options = {
62
+ scanInterval: 200,
63
+ ...options
64
+ };
65
+
66
+ this.camera = new Camera();
67
+ }
68
+
69
+ /**
70
+ * 启动二维码扫描
71
+ *
72
+ * 初始化相机并开始连续扫描视频帧中的二维码
73
+ *
74
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
75
+ * @returns {Promise<void>} 启动完成的Promise
76
+ * @throws 如果无法访问相机,将通过onError回调报告错误
77
+ */
78
+ async start(videoElement: HTMLVideoElement): Promise<void> {
79
+ try {
80
+ await this.camera.initialize(videoElement);
81
+ this.scanning = true;
82
+ this.scan();
83
+ } catch (error) {
84
+ if (this.options.onError) {
85
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 执行一次二维码扫描
92
+ *
93
+ * 内部方法,捕获当前视频帧并尝试识别其中的二维码
94
+ *
95
+ * @private
96
+ */
97
+ private scan(): void {
98
+ if (!this.scanning) return;
99
+
100
+ const imageData = this.camera.captureFrame();
101
+
102
+ if (imageData) {
103
+ const code = jsQR(imageData.data, imageData.width, imageData.height);
104
+
105
+ if (code && this.options.onScan) {
106
+ this.options.onScan(code.data);
107
+ }
108
+ }
109
+
110
+ this.scanTimer = window.setTimeout(() => this.scan(), this.options.scanInterval);
111
+ }
112
+
113
+ /**
114
+ * 停止二维码扫描
115
+ *
116
+ * 停止扫描循环并释放相机资源
117
+ */
118
+ stop(): void {
119
+ this.scanning = false;
120
+
121
+ if (this.scanTimer) {
122
+ clearTimeout(this.scanTimer);
123
+ this.scanTimer = null;
124
+ }
125
+
126
+ this.camera.release();
127
+ }
128
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @file 相机工具类
3
+ * @description 提供访问和控制设备摄像头的功能
4
+ * @module Camera
5
+ */
6
+
7
+ /**
8
+ * 相机配置选项接口
9
+ *
10
+ * @interface CameraOptions
11
+ * @property {number} [width] - 视频宽度,默认为640
12
+ * @property {number} [height] - 视频高度,默认为480
13
+ * @property {string} [facingMode] - 摄像头朝向,'user'为前置摄像头,'environment'为后置摄像头,默认为'environment'
14
+ */
15
+ export interface CameraOptions {
16
+ width?: number;
17
+ height?: number;
18
+ facingMode?: 'user' | 'environment';
19
+ }
20
+
21
+ /**
22
+ * 相机工具类
23
+ *
24
+ * 提供访问设备摄像头、获取视频流以及捕获图像帧的功能
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // 创建相机实例
29
+ * const camera = new Camera({
30
+ * width: 1280,
31
+ * height: 720,
32
+ * facingMode: 'environment' // 使用后置摄像头
33
+ * });
34
+ *
35
+ * // 初始化相机
36
+ * const videoElement = document.getElementById('video') as HTMLVideoElement;
37
+ * await camera.initialize(videoElement);
38
+ *
39
+ * // 捕获当前视频帧
40
+ * const imageData = camera.captureFrame();
41
+ *
42
+ * // 使用结束后释放资源
43
+ * camera.release();
44
+ * ```
45
+ */
46
+ export class Camera {
47
+ private stream: MediaStream | null = null;
48
+ private videoElement: HTMLVideoElement | null = null;
49
+
50
+ /**
51
+ * 创建相机实例
52
+ *
53
+ * @param {CameraOptions} [options] - 相机配置选项
54
+ */
55
+ constructor(private options: CameraOptions = {}) {
56
+ this.options = {
57
+ width: 640,
58
+ height: 480,
59
+ facingMode: 'environment',
60
+ ...options
61
+ };
62
+ }
63
+
64
+ /**
65
+ * 初始化相机,请求摄像头权限并设置视频流
66
+ *
67
+ * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
68
+ * @returns {Promise<void>} 初始化完成的Promise
69
+ * @throws 如果无法访问相机,将抛出错误
70
+ */
71
+ async initialize(videoElement: HTMLVideoElement): Promise<void> {
72
+ try {
73
+ this.videoElement = videoElement;
74
+
75
+ const constraints: MediaStreamConstraints = {
76
+ video: {
77
+ width: this.options.width,
78
+ height: this.options.height,
79
+ facingMode: this.options.facingMode
80
+ }
81
+ };
82
+
83
+ this.stream = await navigator.mediaDevices.getUserMedia(constraints);
84
+ this.videoElement.srcObject = this.stream;
85
+
86
+ return new Promise((resolve) => {
87
+ if (this.videoElement) {
88
+ this.videoElement.onloadedmetadata = () => {
89
+ if (this.videoElement) {
90
+ this.videoElement.play();
91
+ resolve();
92
+ }
93
+ };
94
+ }
95
+ });
96
+ } catch (error) {
97
+ console.error('相机初始化失败:', error);
98
+ throw new Error('无法访问相机');
99
+ }
100
+ }
101
+
102
+ /**
103
+ * 获取当前视频帧
104
+ *
105
+ * 捕获当前视频画面并转换为ImageData对象,可用于图像处理和分析
106
+ *
107
+ * @returns {ImageData|null} 当前视频帧的ImageData对象,如果未初始化视频则返回null
108
+ */
109
+ captureFrame(): ImageData | null {
110
+ if (!this.videoElement) return null;
111
+
112
+ const canvas = document.createElement('canvas');
113
+ const ctx = canvas.getContext('2d');
114
+ if (!ctx) return null;
115
+
116
+ canvas.width = this.videoElement.videoWidth;
117
+ canvas.height = this.videoElement.videoHeight;
118
+ ctx.drawImage(this.videoElement, 0, 0);
119
+
120
+ return ctx.getImageData(0, 0, canvas.width, canvas.height);
121
+ }
122
+
123
+ /**
124
+ * 释放相机资源
125
+ *
126
+ * 停止所有视频流轨道并清理资源。在不再需要相机时应调用此方法。
127
+ */
128
+ release(): void {
129
+ if (this.stream) {
130
+ this.stream.getTracks().forEach(track => track.stop());
131
+ this.stream = null;
132
+ }
133
+
134
+ if (this.videoElement) {
135
+ this.videoElement.srcObject = null;
136
+ this.videoElement = null;
137
+ }
138
+ }
139
+ }