UNPKG

id-scanner-lib

Version:

Browser-based ID card, QR code, and face recognition scanner with liveness detection

474 lines (413 loc) 13.4 kB
/** * @file 身份证检测器 * @description 提供身份证检测和解析功能 * @module modules/id-card/id-card-detector */ import { EventEmitter } from '../../core/event-emitter'; import { Logger } from '../../core/logger'; import { Result } from '../../core/result'; import { IDCardType, IDCardEdge, IDCardInfo } from './types'; /** * 身份证检测器配置选项 */ export interface IDCardDetectorOptions { /** 是否启用 */ enabled?: boolean; /** 最小置信度 */ minConfidence?: number; /** 是否检测身份证类型 */ detectType?: boolean; /** 是否检测边缘 */ detectEdge?: boolean; /** 是否启用边缘检测(用于更精确的边缘检测) */ enableEdgeDetection?: boolean; /** 是否启用OCR识别 */ enableOCR?: boolean; /** 是否裁剪并校正图像 */ cropAndAlign?: boolean; /** 是否启用防伪检测 */ enableAntiFake?: boolean; /** 是否返回原始图像 */ returnImage?: boolean; /** 模型路径 */ modelPath?: string; } /** * 图像处理配置选项 */ export interface ImageProcessOptions { /** 是否进行预处理 */ preprocess?: boolean; /** 是否校正图像 */ correctPerspective?: boolean; /** 是否增强图像 */ enhance?: boolean; /** 是否去噪 */ denoise?: boolean; /** 是否二值化 */ binarize?: boolean; } /** * 身份证检测器类 */ export class IDCardDetector extends EventEmitter { private options: IDCardDetectorOptions; private logger: Logger; private initialized: boolean = false; private models: { detection?: any; ocr?: any; antiFake?: any; } = {}; /** * 构造函数 * @param options 配置选项 */ constructor(options: IDCardDetectorOptions = {}) { super(); this.options = { enabled: true, minConfidence: 0.7, detectType: true, detectEdge: true, enableEdgeDetection: false, enableOCR: true, cropAndAlign: true, enableAntiFake: false, returnImage: false, modelPath: '/models/id-card', ...options }; this.logger = Logger.getInstance(); } /** * 初始化检测器 */ public async initialize(): Promise<void> { if (this.initialized || !this.options.enabled) { return; } this.logger.debug('IDCardDetector', '初始化身份证检测器'); try { // 加载检测模型 await this.loadDetectionModel(); // 如果启用OCR,加载OCR模型 if (this.options.enableOCR) { await this.loadOCRModel(); } // 如果启用防伪检测,加载防伪模型 if (this.options.enableAntiFake) { await this.loadAntiFakeModel(); } this.initialized = true; this.emit('detector:initialized', {}); this.logger.debug('IDCardDetector', '身份证检测器初始化完成'); } catch (error) { this.logger.error('IDCardDetector', '身份证检测器初始化失败', error as Error); throw error; } } /** * 加载检测模型 * @private */ private async loadDetectionModel(): Promise<void> { // 实际项目中,这里应该加载检测模型 this.logger.debug('IDCardDetector', '加载身份证检测模型'); // 模拟加载模型的延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 设置模型 this.models.detection = { loaded: true, name: 'id-card-detection' }; } /** * 加载OCR模型 * @private */ private async loadOCRModel(): Promise<void> { // 实际项目中,这里应该加载OCR模型 this.logger.debug('IDCardDetector', '加载身份证OCR模型'); // 模拟加载模型的延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 设置模型 this.models.ocr = { loaded: true, name: 'id-card-ocr' }; } /** * 加载防伪模型 * @private */ private async loadAntiFakeModel(): Promise<void> { // 实际项目中,这里应该加载防伪模型 this.logger.debug('IDCardDetector', '加载身份证防伪模型'); // 模拟加载模型的延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 设置模型 this.models.antiFake = { loaded: true, name: 'id-card-anti-fake' }; } /** * 处理图像 * @param image 图像源(可以是ImageData、HTMLImageElement、HTMLCanvasElement等) * @param processOptions 图像处理选项 * @returns 处理结果 */ public async processImage( image: ImageData | HTMLImageElement | HTMLCanvasElement, processOptions: ImageProcessOptions = {} ): Promise<Result<IDCardInfo>> { if (!this.initialized) { return Result.failure(new Error('身份证检测器未初始化')); } try { this.logger.debug('IDCardDetector', '开始处理图像'); // 预处理图像 const processedImage = await this.preprocessImage(image, processOptions); // 检测身份证 const detectionResult = await this.detectIDCard(processedImage); if (!detectionResult || detectionResult.confidence < (this.options.minConfidence || 0.7)) { return Result.failure(new Error('未检测到身份证或置信度过低')); } let idCardInfo: IDCardInfo = { type: detectionResult.type, edge: detectionResult.edge, confidence: detectionResult.confidence }; // 如果启用OCR识别,提取文字信息 if (this.options.enableOCR && this.models.ocr) { // 裁剪并校正图像 const alignedImage = this.options.cropAndAlign ? await this.cropAndAlign(processedImage, detectionResult.edge) : processedImage; // 识别文字 const ocrResult = await this.recognizeText(alignedImage, detectionResult.type); // 合并结果 idCardInfo = { ...idCardInfo, ...ocrResult }; } // 如果启用防伪检测,进行防伪检测 if (this.options.enableAntiFake && this.models.antiFake) { const antiFakeResult = await this.detectAntiFake(processedImage, detectionResult); idCardInfo.antiFake = antiFakeResult; } // 如果需要返回原始图像 if (this.options.returnImage) { // 根据图像类型获取ImageData if (image instanceof ImageData) { idCardInfo.image = image; } else if (image instanceof HTMLCanvasElement) { const context = image.getContext('2d'); if (context) { idCardInfo.image = context.getImageData(0, 0, image.width, image.height); } } else if (image instanceof HTMLImageElement && image.complete) { const canvas = document.createElement('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; const context = canvas.getContext('2d'); if (context) { context.drawImage(image, 0, 0); idCardInfo.image = context.getImageData(0, 0, canvas.width, canvas.height); } } } this.logger.debug('IDCardDetector', '图像处理完成'); this.emit('detector:result', { result: idCardInfo }); return Result.success(idCardInfo); } catch (error) { this.logger.error('IDCardDetector', '图像处理失败', error as Error); return Result.failure(error as Error); } } /** * 预处理图像 * @param image 图像源 * @param options 处理选项 * @returns 处理后的图像 * @private */ private async preprocessImage( image: ImageData | HTMLImageElement | HTMLCanvasElement, options: ImageProcessOptions ): Promise<ImageData> { // 实际项目中,这里应该对图像进行预处理 this.logger.debug('IDCardDetector', '预处理图像'); // 创建ImageData对象 let imageData: ImageData; if (image instanceof ImageData) { imageData = image; } else { const canvas = document.createElement('canvas'); const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width; const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height; canvas.width = width; canvas.height = height; const context = canvas.getContext('2d'); if (!context) { throw new Error('无法获取Canvas上下文'); } if (image instanceof HTMLImageElement) { context.drawImage(image, 0, 0); } else { context.drawImage(image, 0, 0); } imageData = context.getImageData(0, 0, width, height); } // 应用图像处理选项 // 实际项目中,这里应该根据options进行相应的图像处理 return imageData; } /** * 检测身份证 * @param image 图像数据 * @returns 检测结果 * @private */ private async detectIDCard(image: ImageData): Promise<{ type: IDCardType; edge: IDCardEdge; confidence: number; } | null> { // 实际项目中,这里应该调用模型进行身份证检测 this.logger.debug('IDCardDetector', '检测身份证'); // 模拟检测结果 // 在实际应用中,这里应该使用机器学习模型进行推理 return { type: IDCardType.FRONT, edge: { topLeft: { x: 10, y: 10 }, topRight: { x: image.width - 10, y: 10 }, bottomRight: { x: image.width - 10, y: image.height - 10 }, bottomLeft: { x: 10, y: image.height - 10 } }, confidence: 0.95 }; } /** * 裁剪并校正图像 * @param image 图像数据 * @param edge 边缘信息 * @returns 校正后的图像 * @private */ private async cropAndAlign(image: ImageData, edge: IDCardEdge): Promise<ImageData> { // 实际项目中,这里应该进行透视变换以校正图像 this.logger.debug('IDCardDetector', '裁剪并校正图像'); // 创建Canvas const canvas = document.createElement('canvas'); // 设置标准身份证尺寸比例 canvas.width = 428; canvas.height = 270; const context = canvas.getContext('2d'); if (!context) { throw new Error('无法获取Canvas上下文'); } // 创建临时Canvas const tempCanvas = document.createElement('canvas'); tempCanvas.width = image.width; tempCanvas.height = image.height; const tempContext = tempCanvas.getContext('2d'); if (!tempContext) { throw new Error('无法获取临时Canvas上下文'); } // 将ImageData绘制到临时Canvas tempContext.putImageData(image, 0, 0); // 在实际应用中,这里应该使用透视变换算法 // 例如使用Canvas的transform或WebGL进行变换 // 简化处理:直接裁剪 context.drawImage( tempCanvas, edge.topLeft.x, edge.topLeft.y, edge.topRight.x - edge.topLeft.x, edge.bottomLeft.y - edge.topLeft.y, 0, 0, canvas.width, canvas.height ); return context.getImageData(0, 0, canvas.width, canvas.height); } /** * 识别文字 * @param image 图像数据 * @param type 身份证类型 * @returns 识别结果 * @private */ private async recognizeText(image: ImageData, type: IDCardType): Promise<Partial<IDCardInfo>> { // 实际项目中,这里应该调用OCR模型进行文字识别 this.logger.debug('IDCardDetector', '识别文字'); // 模拟OCR结果 // 在实际应用中,这里应该使用OCR模型进行文字识别 if (type === IDCardType.FRONT) { return { name: '张三', gender: '男', ethnicity: '汉', birthDate: '1990-01-01', address: '北京市朝阳区某某街道某某社区1号楼1单元101', idNumber: '110101199001010001', photoRegion: { x: 300, y: 40, width: 100, height: 130 } }; } else if (type === IDCardType.BACK) { return { issueAuthority: '北京市公安局朝阳分局', validFrom: '2015-01-01', validTo: '2035-01-01' }; } return {}; } /** * 检测防伪特征 * @param image 图像数据 * @param detectionResult 检测结果 * @returns 防伪检测结果 * @private */ private async detectAntiFake( image: ImageData, detectionResult: { type: IDCardType; edge: IDCardEdge; confidence: number } ): Promise<IDCardInfo['antiFake']> { // 实际项目中,这里应该调用防伪模型进行特征检测 this.logger.debug('IDCardDetector', '检测防伪特征'); // 模拟防伪检测结果 // 在实际应用中,这里应该使用机器学习模型检测防伪特征 return { passed: true, score: 0.92, features: { fluorescent: true, microtext: true, opticalVariable: true, texture: true, watermark: true } }; } /** * 释放资源 */ public dispose(): void { this.logger.debug('IDCardDetector', '释放资源'); // 清理模型 this.models = {}; this.initialized = false; // 清理事件监听 this.removeAllListeners(); } }