UNPKG

id-scanner-lib

Version:

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

1,621 lines (1,609 loc) 488 kB
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document); (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tesseract.js'), require('browser-image-compression')) : typeof define === 'function' && define.amd ? define(['exports', 'tesseract.js', 'browser-image-compression'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IDScannerLib = {}, global.Tesseract, global.imageCompression)); })(this, (function (exports, tesseract_js, imageCompression) { 'use strict'; /** * @file 配置管理器 * @description 提供全局配置管理功能 * @module core/config */ /** * 配置管理器 * 负责存储和管理应用程序的配置 */ class ConfigManager { /** * 私有构造函数 */ constructor() { /** 配置存储 */ this.config = {}; /** 配置变更回调 */ this.changeCallbacks = new Map(); // 设置默认配置 this.config = { debug: false, logLevel: 'info', camera: { resolution: { width: 1280, height: 720 }, frameRate: 30, facingMode: 'environment' }, performance: { useCache: true } }; } /** * 获取单例实例 */ static getInstance() { if (!ConfigManager.instance) { ConfigManager.instance = new ConfigManager(); } return ConfigManager.instance; } /** * 获取配置值 * @param key 配置键,支持点号分隔的路径 * @param defaultValue 默认值 */ get(key, defaultValue) { const value = this.getNestedValue(this.config, key); return (value !== undefined) ? value : defaultValue; } /** * 设置配置值 * @param key 配置键,支持点号分隔的路径 * @param value 配置值 */ set(key, value) { const oldValue = this.get(key); // 如果值相同,不做任何事 if (oldValue === value) { return; } this.setNestedValue(this.config, key, value); // 触发变更回调 this.triggerChangeCallbacks(key, value, oldValue); } /** * 批量更新配置 * @param config 配置对象 */ updateConfig(config) { Object.entries(config).forEach(([key, value]) => { this.set(key, value); }); } /** * 重置为默认配置 */ reset() { const oldConfig = { ...this.config }; // 重新创建默认配置 this.config = { debug: false, logLevel: 'info', camera: { resolution: { width: 1280, height: 720 }, frameRate: 30, facingMode: 'environment' }, performance: { useCache: true } }; // 触发所有回调 Object.keys(oldConfig).forEach(key => { this.triggerChangeCallbacks(key, this.get(key), oldConfig[key]); }); } /** * 注册配置变更回调 * @param key 配置键 * @param callback 回调函数 */ onConfigChange(key, callback) { if (!this.changeCallbacks.has(key)) { this.changeCallbacks.set(key, []); } this.changeCallbacks.get(key).push(callback); } /** * 移除配置变更回调 * @param key 配置键 * @param callback 特定回调函数,如不提供则移除所有 */ offConfigChange(key, callback) { if (!this.changeCallbacks.has(key)) { return; } if (callback) { // 移除特定回调 const callbacks = this.changeCallbacks.get(key); const index = callbacks.indexOf(callback); if (index !== -1) { callbacks.splice(index, 1); } // 如果没有回调,删除键 if (callbacks.length === 0) { this.changeCallbacks.delete(key); } } else { // 移除所有回调 this.changeCallbacks.delete(key); } } /** * 获取嵌套值 * @param obj 对象 * @param path 路径 */ getNestedValue(obj, path) { // 处理根路径 if (!path) { return obj; } // 处理嵌套路径 const parts = path.split('.'); let current = obj; for (const part of parts) { if (current === undefined || current === null) { return undefined; } current = current[part]; } return current; } /** * 设置嵌套值 * @param obj 对象 * @param path 路径 * @param value 值 */ setNestedValue(obj, path, value) { // 处理根路径 if (!path) { return; } // 处理嵌套路径 const parts = path.split('.'); let current = obj; // 遍历路径,直到倒数第二部分 for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; // 如果不存在,创建新对象 if (current[part] === undefined || current[part] === null || typeof current[part] !== 'object') { current[part] = {}; } current = current[part]; } // 设置最终值 current[parts[parts.length - 1]] = value; } /** * 触发变更回调 * @param key 配置键 * @param value 新值 * @param oldValue 旧值 */ triggerChangeCallbacks(key, value, oldValue) { // 触发特定键的回调 if (this.changeCallbacks.has(key)) { const callbacks = this.changeCallbacks.get(key); callbacks.forEach(callback => { try { callback(value, oldValue); } catch (error) { console.error(`Error in config change callback for key ${key}:`, error); } }); } // 触发父路径的回调 const parts = key.split('.'); while (parts.length > 1) { parts.pop(); const parentKey = parts.join('.'); if (this.changeCallbacks.has(parentKey)) { const parentValue = this.get(parentKey); this.changeCallbacks.get(parentKey).forEach(callback => { try { callback(parentValue, parentValue); } catch (error) { console.error(`Error in config change callback for parent key ${parentKey}:`, error); } }); } } } } /** * @file 日志系统 * @description 提供统一的日志记录与管理功能 * @module core/logger */ /** * 日志级别枚举 */ exports.LogLevel = void 0; (function (LogLevel) { LogLevel["DEBUG"] = "debug"; LogLevel["INFO"] = "info"; LogLevel["WARN"] = "warn"; LogLevel["ERROR"] = "error"; })(exports.LogLevel || (exports.LogLevel = {})); /** * 控制台日志处理器 * 将日志输出到浏览器控制台 */ class ConsoleLogHandler { /** * 处理日志条目 * @param entry 日志条目 */ handle(entry) { const timestamp = new Date(entry.timestamp).toISOString(); const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`; switch (entry.level) { case exports.LogLevel.DEBUG: console.debug(prefix, entry.message, entry.error || ''); break; case exports.LogLevel.INFO: console.info(prefix, entry.message, entry.error || ''); break; case exports.LogLevel.WARN: console.warn(prefix, entry.message, entry.error || ''); break; case exports.LogLevel.ERROR: console.error(prefix, entry.message, entry.error || ''); break; // 输出什么也不做 } } } /** * 内存日志处理器 * 将日志保存在内存中,用于后续分析或显示 */ class MemoryLogHandler { /** * 构造函数 * @param maxEntries 最大日志条目数,默认为1000 */ constructor(maxEntries = 1000) { /** 日志条目数组 */ this.entries = []; this.maxEntries = maxEntries; } /** * 处理日志条目 * @param entry 日志条目 */ handle(entry) { this.entries.push(entry); // 如果超过最大条目数,移除最老的 if (this.entries.length > this.maxEntries) { this.entries.shift(); } } /** * 获取所有日志条目 */ getEntries() { return [...this.entries]; } /** * 根据级别过滤日志条目 * @param level 日志级别 */ getEntriesByLevel(level) { return this.entries.filter(entry => entry.level === level); } /** * 根据标签过滤日志条目 * @param tag 日志标签 */ getEntriesByTag(tag) { return this.entries.filter(entry => entry.tag === tag); } /** * 清空日志 */ clear() { this.entries = []; } } /** * 远程日志处理器 * 将日志发送到远程服务器 */ class RemoteLogHandler { /** * 构造函数 * @param endpoint 远程服务器URL * @param maxQueueSize 最大队列长度,默认为100 * @param flushInterval 发送间隔(毫秒),默认为5000 */ constructor(endpoint, maxQueueSize = 100, flushInterval = 5000) { /** 批量发送的队列 */ this.queue = []; /** 定时发送的计时器ID */ this.timerId = null; this.endpoint = endpoint; this.maxQueueSize = maxQueueSize; this.flushInterval = flushInterval; // 设置定时发送 this.startTimer(); // 页面卸载前尝试发送剩余日志 window.addEventListener('beforeunload', () => { this.flush(); }); } /** * 处理日志条目 * @param entry 日志条目 */ handle(entry) { // 只处理INFO以上级别的日志 if (entry.level >= exports.LogLevel.INFO) { this.queue.push(entry); // 如果队列满了,立即发送 if (this.queue.length >= this.maxQueueSize) { this.flush(); } } } /** * 发送队列中的日志 */ flush() { if (this.queue.length === 0) return; const entriesToSend = [...this.queue]; this.queue = []; try { fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entriesToSend), // 不等待响应,避免阻塞 keepalive: true }).catch(err => { console.error('Failed to send logs to remote server:', err); // 失败时把日志放回队列,但防止无限增长 if (this.queue.length < this.maxQueueSize) { this.queue = [...entriesToSend.slice(0, this.maxQueueSize - this.queue.length), ...this.queue]; } }); } catch (error) { console.error('Error sending logs:', error); } } /** * 开始定时发送 */ startTimer() { if (this.timerId !== null) return; this.timerId = window.setInterval(() => { this.flush(); }, this.flushInterval); } /** * 停止定时发送 */ stopTimer() { if (this.timerId !== null) { window.clearInterval(this.timerId); this.timerId = null; } } } /** * 日志管理类 * 中央日志管理器,提供统一的日志记录接口 */ class Logger { /** * 私有构造函数,防止直接实例化 */ constructor() { /** 日志处理器 */ this.handlers = []; /** 默认标签 */ this.defaultTag = 'IDScanner'; /** 日志级别 */ this.logLevel = exports.LogLevel.INFO; this.config = ConfigManager.getInstance(); // 默认添加控制台处理器 this.addHandler(new ConsoleLogHandler()); // 监听配置变化 this.config.onConfigChange('logLevel', (level) => { this.debug('Logger', `Log level changed to ${level}`); }); } /** * 获取单例实例 */ static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } /** * 添加日志处理器 * @param handler 日志处理器 */ addHandler(handler) { this.handlers.push(handler); } /** * 移除日志处理器 * @param handler 要移除的处理器 */ removeHandler(handler) { const index = this.handlers.indexOf(handler); if (index !== -1) { this.handlers.splice(index, 1); } } /** * 移除所有处理器 */ clearHandlers() { this.handlers = []; } /** * 设置默认标签 * @param tag 默认标签 */ setDefaultTag(tag) { this.defaultTag = tag; } /** * 记录调试级别日志 * @param tag 标签 * @param message 消息 * @param error 错误 */ debug(tag, message, error) { this.log(exports.LogLevel.DEBUG, tag, message, error); } /** * 记录信息级别日志 * @param tag 标签 * @param message 消息 * @param error 错误 */ info(tag, message, error) { this.log(exports.LogLevel.INFO, tag, message, error); } /** * 记录警告级别日志 * @param tag 标签 * @param message 消息 * @param error 错误 */ warn(tag, message, error) { this.log(exports.LogLevel.WARN, tag, message, error); } /** * 记录错误级别日志 * @param tag 标签 * @param message 消息 * @param error 错误 */ error(tag, message, error) { this.log(exports.LogLevel.ERROR, tag, message, error); } /** * 创建标记了特定标签的日志记录器 * @param tag 标签 */ getTaggedLogger(tag) { return new TaggedLogger(this, tag); } /** * 记录日志 * @param level 日志级别 * @param tag 标签 * @param message 消息 * @param error 错误 */ log(level, tag, message, error) { // 检查日志级别 const levelValue = this.getLevelValue(level); const currentLevelValue = this.getLevelValue(this.logLevel); if (levelValue < currentLevelValue) { return; } // 创建日志条目 const entry = { timestamp: Date.now(), level: level, tag: tag || this.defaultTag, message, error }; // 分发到所有处理程序 for (const handler of this.handlers) { try { handler.handle(entry); } catch (handlerError) { console.error(`[Logger] 处理程序错误:`, handlerError); } } // 如果没有处理程序,使用控制台 if (this.handlers.length === 0) { this.consoleOutput(entry); } } /** * 控制台输出 * @param entry 日志条目 */ consoleOutput(entry) { const timestamp = new Date(entry.timestamp).toISOString(); const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`; switch (entry.level) { case exports.LogLevel.DEBUG: console.debug(`${prefix} ${entry.message}`, entry.error || ''); break; case exports.LogLevel.INFO: console.info(`${prefix} ${entry.message}`, entry.error || ''); break; case exports.LogLevel.WARN: console.warn(`${prefix} ${entry.message}`, entry.error || ''); break; case exports.LogLevel.ERROR: console.error(`${prefix} ${entry.message}`, entry.error || ''); break; } } /** * 获取日志级别值 * @param level 日志级别 */ getLevelValue(level) { switch (level) { case exports.LogLevel.DEBUG: return 0; case exports.LogLevel.INFO: return 1; case exports.LogLevel.WARN: return 2; case exports.LogLevel.ERROR: return 3; default: return 1; // 默认INFO级别 } } /** * 设置日志级别 * @param level 日志级别 */ setLevel(level) { if (typeof level === 'string') { switch (level) { case 'debug': this.logLevel = exports.LogLevel.DEBUG; break; case 'info': this.logLevel = exports.LogLevel.INFO; break; case 'warn': this.logLevel = exports.LogLevel.WARN; break; case 'error': this.logLevel = exports.LogLevel.ERROR; break; default: this.logLevel = exports.LogLevel.INFO; } } else { this.logLevel = level; } this.debug('Logger', `日志级别已设置为 ${this.logLevel}`); } /** * 获取当前日志级别 * @returns 当前日志级别 */ getLevel() { return this.logLevel; } } /** * 带标签的日志记录器 * 提供特定标签的简易日志接口 */ class TaggedLogger { /** * 构造函数 * @param logger 所属的主日志记录器 * @param tag 标签 */ constructor(logger, tag) { this.logger = logger; this.tag = tag; } /** * 记录调试级别日志 * @param message 消息 * @param error 错误 */ debug(message, error) { this.logger.debug(this.tag, message, error); } /** * 记录信息级别日志 * @param message 消息 * @param error 错误 */ info(message, error) { this.logger.info(this.tag, message, error); } /** * 记录警告级别日志 * @param message 消息 * @param error 错误 */ warn(message, error) { this.logger.warn(this.tag, message, error); } /** * 记录错误级别日志 * @param message 消息 * @param error 错误 */ error(message, error) { this.logger.error(this.tag, message, error); } } /** * 日志级别枚举 */ /** * @file 事件发射器 * @description 提供基础的事件发射和订阅功能 * @module core/event-emitter */ /** * 事件发射器基类 * 提供基础的事件发射和订阅功能 */ class EventEmitter { constructor() { /** 事件处理器映射 */ this.eventHandlers = new Map(); } /** * 订阅事件 * @param eventName 事件名称 * @param handler 事件处理器 */ on(eventName, handler) { if (!this.eventHandlers.has(eventName)) { this.eventHandlers.set(eventName, new Set()); } this.eventHandlers.get(eventName).add(handler); } /** * 取消订阅事件 * @param eventName 事件名称 * @param handler 事件处理器,如果不提供则移除该事件的所有处理器 */ off(eventName, handler) { if (!this.eventHandlers.has(eventName)) { return; } if (handler) { this.eventHandlers.get(eventName).delete(handler); // 如果没有处理器了,删除这个事件 if (this.eventHandlers.get(eventName).size === 0) { this.eventHandlers.delete(eventName); } } else { // 移除该事件的所有处理器 this.eventHandlers.delete(eventName); } } /** * 订阅事件,但只触发一次 * @param eventName 事件名称 * @param handler 事件处理器 */ once(eventName, handler) { const onceHandler = (data) => { handler(data); this.off(eventName, onceHandler); }; this.on(eventName, onceHandler); } /** * 发射事件 * @param eventName 事件名称 * @param data 事件数据 */ emit(eventName, data) { if (!this.eventHandlers.has(eventName)) { return; } for (const handler of this.eventHandlers.get(eventName)) { try { handler(data); } catch (error) { console.error(`Error in event handler for "${eventName}":`, error); } } } /** * 获取某个事件的处理器数量 * @param eventName 事件名称 */ listenerCount(eventName) { return this.eventHandlers.has(eventName) ? this.eventHandlers.get(eventName).size : 0; } /** * 移除所有事件处理器 */ removeAllListeners() { this.eventHandlers.clear(); } /** * 获取所有事件名称 */ eventNames() { return Array.from(this.eventHandlers.keys()); } } /** * @file 版本号文件 * @description 定义库的版本号 * @module Version */ // 当前版本号 const VERSION = '1.5.0'; // 构建日期 const BUILD_DATE = new Date().toISOString(); /** * @file 模块管理器 * @description 统一管理库的各功能模块,提供模块的注册、初始化和卸载功能 * @module core/module-manager */ /** * 模块管理器类 * 负责管理所有功能模块的生命周期 */ class ModuleManager extends EventEmitter { /** * 获取模块管理器单例 */ static getInstance() { if (!ModuleManager.instance) { ModuleManager.instance = new ModuleManager(); } return ModuleManager.instance; } /** * 私有构造函数,确保单例模式 */ constructor() { super(); this.modules = new Map(); this.initialized = false; this.logger = Logger.getInstance(); this.logger.debug('ModuleManager', `初始化模块管理器 v${VERSION}`); } /** * 注册模块 * @param module 要注册的模块 * @returns 模块管理器实例,支持链式调用 */ register(module) { if (this.modules.has(module.name)) { this.logger.warn('ModuleManager', `模块 "${module.name}" 已经注册,将被覆盖`); } this.modules.set(module.name, module); this.logger.debug('ModuleManager', `注册模块: ${module.name} v${module.version}`); this.emit('module:registered', { name: module.name }); return this; } /** * 获取模块 * @param name 模块名称 * @returns 模块实例 */ getModule(name) { return this.modules.get(name); } /** * 初始化所有注册的模块 */ async initialize() { if (this.initialized) { return; } this.logger.debug('ModuleManager', '开始初始化所有模块...'); for (const [name, module] of this.modules.entries()) { try { this.logger.debug('ModuleManager', `初始化模块: ${name}`); await module.initialize(); this.emit('module:initialized', { name }); this.logger.debug('ModuleManager', `模块 ${name} 初始化完成`); } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); this.logger.error('ModuleManager', `模块 ${name} 初始化失败`, errorObj); this.emit('module:error', { name, error }); throw new Error(`模块 ${name} 初始化失败: ${error instanceof Error ? error.message : String(error)}`); } } this.initialized = true; this.logger.debug('ModuleManager', '所有模块初始化完成'); this.emit('modules:initialized'); } /** * 卸载所有模块并释放资源 */ async dispose() { this.logger.debug('ModuleManager', '开始释放所有模块资源...'); for (const [name, module] of this.modules.entries()) { try { this.logger.debug('ModuleManager', `释放模块资源: ${name}`); await module.dispose(); this.emit('module:disposed', { name }); } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); this.logger.error('ModuleManager', `模块 ${name} 资源释放失败`, errorObj); this.emit('module:error', { name, error }); } } this.modules.clear(); this.initialized = false; this.logger.debug('ModuleManager', '所有模块资源已释放'); this.emit('modules:disposed'); } /** * 获取所有已注册的模块名称 */ getRegisteredModules() { return Array.from(this.modules.keys()); } /** * 检查模块是否已注册 * @param name 模块名称 */ hasModule(name) { return this.modules.has(name); } } /** * @file 基础模块 * @description 提供基础模块实现,作为所有功能模块的基类 * @module core/base-module */ /** * 基础模块类 * 提供模块的基本功能和生命周期管理 */ class BaseModule extends EventEmitter { /** * 构造函数 */ constructor() { super(); /** 模块版本 */ this.version = VERSION; /** 模块是否已初始化 */ this._isInitialized = false; this.logger = Logger.getInstance(); } /** * 获取模块是否已初始化 */ get isInitialized() { return this._isInitialized; } /** * 释放模块资源 * 子类可以覆盖此方法以添加额外的资源释放逻辑 */ async dispose() { if (!this._isInitialized) { return; } this.logger.debug(this.name, '释放模块资源'); // 重置初始化状态 this._isInitialized = false; // 删除所有事件监听器 this.removeAllListeners(); this.logger.debug(this.name, '模块资源已释放'); } /** * 检查模块是否已初始化,如果未初始化则抛出错误 */ ensureInitialized() { if (!this._isInitialized) { throw new Error(`模块 ${this.name} 尚未初始化`); } } } /** * @file 结果包装类 * @description 提供统一的操作结果封装 * @module core/result */ /** * 结果类型 * 用于封装操作的成功或失败结果 */ class Result { /** * 构造函数 * @param success 是否成功 * @param data 结果数据 * @param error 错误对象 * @param meta 元数据 */ constructor(success, data, error, meta) { this._success = success; this._data = data; this._error = error; this._meta = meta; } /** * 创建成功结果 * @param data 结果数据 * @param meta 元数据 */ static success(data, meta) { return new Result(true, data, undefined, meta); } /** * 创建失败结果 * @param error 错误对象 * @param meta 元数据 */ static failure(error, meta) { return new Result(false, undefined, error, meta); } /** * 检查结果是否成功 */ isSuccess() { return this._success; } /** * 检查结果是否失败 */ isFailure() { return !this._success; } /** * 获取结果数据 */ get data() { return this._data; } /** * 获取错误对象 */ get error() { return this._error; } /** * 获取元数据 */ get meta() { return this._meta; } /** * 映射结果(如果成功) * @param fn 映射函数 */ map(fn) { if (this.isSuccess() && this._data !== undefined) { try { const newData = fn(this._data); return Result.success(newData, this._meta); } catch (error) { return Result.failure(error instanceof Error ? error : new Error(String(error)), this._meta); } } return Result.failure(this._error, this._meta); } /** * 如果成功,则执行函数 * @param fn 要执行的函数 */ onSuccess(fn) { if (this.isSuccess()) { try { fn(this._data); } catch (error) { console.error('Error in onSuccess handler:', error); } } return this; } /** * 如果失败,则执行函数 * @param fn 要执行的函数 */ onFailure(fn) { if (this.isFailure() && this._error) { try { fn(this._error); } catch (error) { console.error('Error in onFailure handler:', error); } } return this; } /** * 无论成功失败,都执行函数 * @param fn 要执行的函数 */ onFinally(fn) { try { fn(); } catch (error) { console.error('Error in onFinally handler:', error); } return this; } /** * 转换为字符串 */ toString() { if (this.isSuccess()) { return `Success: ${JSON.stringify(this._data)}`; } else { return `Failure: ${this._error?.message || 'Unknown error'}`; } } } /** * @file 身份证模块类型定义 * @description 身份证模块相关的类型和接口定义 * @module modules/id-card/types */ /** * 身份证类型枚举 */ exports.IDCardType = void 0; (function (IDCardType) { /** 第二代居民身份证正面 */ IDCardType["FRONT"] = "front"; /** 第二代居民身份证背面 */ IDCardType["BACK"] = "back"; /** 第一代居民身份证 */ IDCardType["FIRST_GENERATION"] = "first_generation"; /** 临时身份证 */ IDCardType["TEMPORARY"] = "temporary"; /** 外国人永久居留证 */ IDCardType["FOREIGN_PERMANENT"] = "foreign_permanent"; /** 港澳台居民居住证 */ IDCardType["HMT_RESIDENT"] = "hmt_resident"; /** 未知类型 */ IDCardType["UNKNOWN"] = "unknown"; })(exports.IDCardType || (exports.IDCardType = {})); /** * @file 身份证检测器 * @description 提供身份证检测和解析功能 * @module modules/id-card/id-card-detector */ /** * 身份证检测器类 */ class IDCardDetector extends EventEmitter { /** * 构造函数 * @param options 配置选项 */ constructor(options = {}) { super(); this.initialized = false; this.models = {}; 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(); } /** * 初始化检测器 */ async initialize() { 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); throw error; } } /** * 加载检测模型 * @private */ async loadDetectionModel() { // 实际项目中,这里应该加载检测模型 this.logger.debug('IDCardDetector', '加载身份证检测模型'); // 模拟加载模型的延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 设置模型 this.models.detection = { loaded: true, name: 'id-card-detection' }; } /** * 加载OCR模型 * @private */ async loadOCRModel() { // 实际项目中,这里应该加载OCR模型 this.logger.debug('IDCardDetector', '加载身份证OCR模型'); // 模拟加载模型的延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 设置模型 this.models.ocr = { loaded: true, name: 'id-card-ocr' }; } /** * 加载防伪模型 * @private */ async loadAntiFakeModel() { // 实际项目中,这里应该加载防伪模型 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 处理结果 */ async processImage(image, processOptions = {}) { 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 = { 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); return Result.failure(error); } } /** * 预处理图像 * @param image 图像源 * @param options 处理选项 * @returns 处理后的图像 * @private */ async preprocessImage(image, options) { // 实际项目中,这里应该对图像进行预处理 this.logger.debug('IDCardDetector', '预处理图像'); // 创建ImageData对象 let 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 */ async detectIDCard(image) { // 实际项目中,这里应该调用模型进行身份证检测 this.logger.debug('IDCardDetector', '检测身份证'); // 模拟检测结果 // 在实际应用中,这里应该使用机器学习模型进行推理 return { type: exports.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 */ async cropAndAlign(image, edge) { // 实际项目中,这里应该进行透视变换以校正图像 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 */ async recognizeText(image, type) { // 实际项目中,这里应该调用OCR模型进行文字识别 this.logger.debug('IDCardDetector', '识别文字'); // 模拟OCR结果 // 在实际应用中,这里应该使用OCR模型进行文字识别 if (type === exports.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 === exports.IDCardType.BACK) { return { issueAuthority: '北京市公安局朝阳分局', validFrom: '2015-01-01', validTo: '2035-01-01' }; } return {}; } /** * 检测防伪特征 * @param image 图像数据 * @param detectionResult 检测结果 * @returns 防伪检测结果 * @private */ async detectAntiFake(image, detectionResult) { // 实际项目中,这里应该调用防伪模型进行特征检测 this.logger.debug('IDCardDetector', '检测防伪特征'); // 模拟防伪检测结果 // 在实际应用中,这里应该使用机器学习模型检测防伪特征 return { passed: true, score: 0.92, features: { fluorescent: true, microtext: true, opticalVariable: true, texture: true, watermark: true } }; } /** * 释放资源 */ dispose() { this.logger.debug('IDCardDetector', '释放资源'); // 清理模型 this.models = {}; this.initialized = false; // 清理事件监听 this.removeAllListeners(); } } /** * @file 图像处理工具类 * @description 提供图像预处理功能,用于提高OCR识别率 * @module ImageProcessor * @version 1.3.2 */ /** * 图像处理工具类 * * 提供各种图像处理功能,用于优化识别效果 */ class ImageProcessor { /** * 将ImageData转换为Canvas元素 * * @param {ImageData} imageData - 要转换的图像数据 * @returns {HTMLCanvasElement} 包含图像的Canvas元素 */ static imageDataToCanvas(imageData) { const canvas = document.createElement("canvas"); canvas.width = imageData.width; canvas.height = imageData.height; const ctx = canvas.getContext("2d"); if (ctx) { ctx.putImageData(imageData, 0, 0); } return canvas; } /** * 将Canvas转换为ImageData * * @param {HTMLCanvasElement} canvas - 要转换的Canvas元素 * @returns {ImageData|null} Canvas的图像数据,如果获取失败则返回null */ static canvasToImageData(canvas) { const ctx = canvas.getContext("2d"); return ctx ? ctx.getImageData(0, 0, canvas.width, canvas.height) : null; } /** * 调整图像亮度和对比度 * * @param imageData 原始图像数据 * @param brightness 亮度调整值 (-100到100) * @param contrast 对比度调整值 (-100到100) * @returns 处理后的图像数据 */ static adjustBrightnessContrast(imageData, brightness = 0, contrast = 0) { // 将亮度和对比度范围限制在 -100 到 100 之间 brightness = Math.max(-100, Math.min(100, brightness)); contrast = Math.max(-100, Math.min(100, contrast)); // 将范围转换为适合计算的值 const factor = (259 * (contrast + 255)) / (255 * (259 - contrast)); const briAdjust = (brightness / 100) * 255; const data = imageData.data; const length = data.length; for (let i = 0; i < length; i += 4) { // 分别处理 RGB 三个通道 for (let j = 0; j < 3; j++) { // 应用亮度和对比度调整公式 const newValue = factor * (data[i + j] + briAdjust - 128) + 128; data[i + j] = Math.max(0, Math.min(255, newValue)); } // Alpha 通道保持不变 } return imageData; } /** * 将图像转换为灰度图 * * @param imageData 原始图像数据 * @returns 灰度图像数据 */ static toGrayscale(imageData) { const data = imageData.data; const length = data.length; for (let i = 0; i < length; i += 4) { // 使用加权平均法将 RGB 转换为灰度值 const gray = data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11; data[i] = data[i + 1] = data[i + 2] = gray; } return imageData; } /** * 锐化图像 * * @param imageData 原始图像数据 * @param amount 锐化程度,默认为2 * @returns 锐化后的图像数据 */ static sharpen(imageData, amount = 2) { if (!imageData || !imageData.data) return imageData; const width = imageData.width; const height = imageData.height; const data = imageData.data; const outputData = new Uint8ClampedArray(data.length); // 锐化卷积核 const kernel = [ 0, -amount, 0, -amount, 1 + 4 * amount, -amount, 0, -amount, 0, ]; // 应用卷积 for (let y = 1; y < height - 1; y++) { for (let x = 1; x < width - 1; x++) { const pos = (y * width + x) * 4; // 对每个通道应用卷积 for (let c = 0; c < 3; c++) { let val = 0; for (let ky = -1; ky <= 1; ky++) { for (let kx = -1; kx <= 1; kx++) { const kernelPos = (ky + 1) * 3 + (kx + 1); const dataPos = ((y + ky) * width + (x + kx)) * 4 + c; val += data[dataPos] * kernel[kernelPos]; } } outputData[pos + c] = Math.max(0, Math.min(255, val)); } outputData[pos + 3] = data[pos + 3]; // 保持透明度不变 } } // 处理边缘像素 for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { if (y === 0 || y === height - 1 || x === 0 || x === width - 1) { const pos = (y * width + x) * 4; outputData[pos] = data[pos]; outputData[pos + 1] = data[pos + 1]; outputData[pos + 2] = data[pos + 2]; outputData[pos + 3] = data[pos + 3]; } } } // 创建新的ImageData对象 return new ImageData(outputData, width, height); } /** * 对图像应用阈值操作,增强对比度 *