UNPKG

id-scanner-lib

Version:

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

223 lines (197 loc) 6.44 kB
/** * @file 人脸模型加载器 * @description 统一管理 face-api 模型的懒加载和缓存 * @module modules/face/face-model-loader */ import * as faceapi from '@vladmandic/face-api'; import { Logger } from '../../core/logger'; import { ResourceLoadError } from '../../core/errors'; import { FaceModelType } from './face-detector'; /** * 已加载模型记录 */ interface LoadedModelRecord { /** 模型名称 */ name: string; /** 加载时间戳 */ loadedAt: number; } /** * 模型加载器配置 */ export interface ModelLoaderConfig { /** 模型路径 */ modelPath: string; /** 检测模型类型 */ detectionModel: FaceModelType; /** 关键点模型类型 */ landmarksModel: 'tiny' | '68_points'; /** 是否检测关键点 */ detectLandmarks: boolean; /** 是否检测表情 */ detectExpressions: boolean; /** 是否检测年龄和性别 */ detectAgeGender: boolean; /** 是否提取人脸特征向量 */ extractEmbeddings: boolean; } /** * 人脸模型加载器 * * 负责按需加载 face-api 模型,避免一次性加载所有模型造成内存浪费 */ export class FaceModelLoader { /** 日志记录器 */ private logger: Logger; /** 已加载模型集合 */ private loadedModels: Set<string> = new Set(); /** 模型是否已全部加载 */ private modelsLoaded: boolean = false; /** 配置 */ private config: ModelLoaderConfig; /** * 构造函数 * @param config 模型加载配置 */ constructor(config: ModelLoaderConfig) { this.logger = Logger.getInstance(); this.config = config; } /** * 获取检测模型选项 */ private getDetectorOptions(minConfidence: number): faceapi.SsdMobilenetv1Options | faceapi.TinyFaceDetectorOptions | faceapi.MtcnnOptions { switch (this.config.detectionModel) { case FaceModelType.SSD_MOBILENET: return new faceapi.SsdMobilenetv1Options({ minConfidence }); case FaceModelType.TINY_FACE: return new faceapi.TinyFaceDetectorOptions({ scoreThreshold: minConfidence }); case FaceModelType.MTCNN: return new faceapi.MtcnnOptions({ minConfidence }); default: return new faceapi.SsdMobilenetv1Options({ minConfidence }); } } /** * 懒加载单个模型 * @param modelType 模型类型 * @param modelPath 模型路径 */ async lazyLoadModel(modelType: string, modelPath: string): Promise<void> { if (this.loadedModels.has(modelType)) { return; } this.logger.info('FaceModelLoader', `懒加载模型: ${modelType}`); try { switch (modelType) { case 'ssdMobilenetv1': await faceapi.nets.ssdMobilenetv1.loadFromUri(modelPath); break; case 'tinyFaceDetector': await faceapi.nets.tinyFaceDetector.loadFromUri(modelPath); break; case 'faceLandmark68Net': await faceapi.nets.faceLandmark68Net.loadFromUri(modelPath); break; case 'faceLandmark68TinyNet': await faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelPath); break; case 'faceExpressionNet': await faceapi.nets.faceExpressionNet.loadFromUri(modelPath); break; case 'ageGenderNet': await faceapi.nets.ageGenderNet.loadFromUri(modelPath); break; case 'faceRecognitionNet': await faceapi.nets.faceRecognitionNet.loadFromUri(modelPath); break; default: this.logger.warn('FaceModelLoader', `未知模型类型: ${modelType}`); return; } this.loadedModels.add(modelType); this.logger.info('FaceModelLoader', `模型加载完成: ${modelType}`); } catch (error) { this.logger.error('FaceModelLoader', `模型加载失败: ${modelType}`, error as Error); throw new ResourceLoadError(modelType, `模型加载失败: ${error}`); } } /** * 根据需求加载模型 * @param options 检测选项 */ async loadModelsOnDemand(options: { withLandmarks?: boolean; withAttributes?: boolean; withEmbedding?: boolean; }): Promise<void> { const { modelPath, landmarksModel, detectExpressions, detectAgeGender, extractEmbeddings } = this.config; // 基础检测模型 const detectionModelKey = this.config.detectionModel === FaceModelType.TINY_FACE ? 'tinyFaceDetector' : 'ssdMobilenetv1'; await this.lazyLoadModel(detectionModelKey, modelPath); // 关键点模型 if (options.withLandmarks || this.config.detectLandmarks) { await this.lazyLoadModel( landmarksModel === '68_points' ? 'faceLandmark68Net' : 'faceLandmark68TinyNet', modelPath ); } // 表情模型 if (options.withAttributes || detectExpressions) { await this.lazyLoadModel('faceExpressionNet', modelPath); } // 年龄性别模型 if (options.withAttributes || detectAgeGender) { await this.lazyLoadModel('ageGenderNet', modelPath); } // 人脸识别模型 if (options.withEmbedding || extractEmbeddings) { await this.lazyLoadModel('faceRecognitionNet', modelPath); } this.modelsLoaded = true; } /** * 确保模型已加载(用于需要所有模型的场景) */ async ensureModelsLoaded(): Promise<void> { if (this.modelsLoaded) return; await this.loadModelsOnDemand({}); } /** * 获取模型加载状态 */ isModelsLoaded(): boolean { return this.modelsLoaded; } /** * 获取已加载模型列表 */ getLoadedModels(): string[] { return Array.from(this.loadedModels); } /** * 释放所有模型(释放 TensorFlow.js 内存) */ async dispose(): Promise<void> { if (this.loadedModels.size > 0) { try { // face-api 使用 TensorFlow.js,需要显式释放 const tf = await import('@tensorflow/tfjs'); tf.dispose(); this.loadedModels.clear(); this.modelsLoaded = false; this.logger.debug('FaceModelLoader', '模型资源已释放'); } catch (error) { this.logger.error('FaceModelLoader', `释放模型失败: ${error}`); } } } /** * 获取检测器选项(用于 detectAllFaces) */ getFaceDetectionOptions(minConfidence: number): faceapi.SsdMobilenetv1Options | faceapi.TinyFaceDetectorOptions | faceapi.MtcnnOptions { return this.getDetectorOptions(minConfidence); } }