@sssxyd/face-liveness-detector 0.2.32 → 0.2.33

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/dist/index.esm.js CHANGED
@@ -314,7 +314,13 @@ function _detectBrowserEngine(userAgent) {
314
314
  if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
315
315
  return 'gecko';
316
316
  }
317
- // 2. 检测 WebKit
317
+ // 2. 检测 Chromium/Blink(必须在 WebKit 之前,因为 Chrome 的 user-agent 也包含 WebKit)
318
+ // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
319
+ if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
320
+ return 'chromium';
321
+ }
322
+ // 3. 检测 WebKit(真正的 Safari 和 iOS 浏览器)
323
+ // 注意:真正的 WebKit 浏览器(Safari)user-agent 不包含 Chrome 标识
318
324
  // 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
319
325
  if (/webkit/i.test(ua)) {
320
326
  // WebKit 特征明显,包括以下几种情况:
@@ -323,11 +329,6 @@ function _detectBrowserEngine(userAgent) {
323
329
  // - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
324
330
  return 'webkit';
325
331
  }
326
- // 3. 检测 Chromium/Blink
327
- // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
328
- if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
329
- return 'chromium';
330
- }
331
332
  // 4. 其他浏览器 - 保守方案,使用 WASM
332
333
  return 'other';
333
334
  }
@@ -567,16 +568,11 @@ function getCvSync() {
567
568
  return null;
568
569
  }
569
570
  /**
570
- * Load Human.js
571
- * @param modelPath - Path to model files (optional)
572
- * @param wasmPath - Path to WASM files (optional)
573
- * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
574
- * @returns Promise that resolves with Human instance
571
+ * Create Human.js configuration object
575
572
  */
576
- async function loadHuman(modelPath, wasmPath, preferredBackend) {
577
- const selectedBackend = _detectOptimalBackend(preferredBackend);
573
+ function _createHumanConfig(backend, modelPath, wasmPath) {
578
574
  const config = {
579
- backend: selectedBackend,
575
+ backend,
580
576
  face: {
581
577
  enabled: true,
582
578
  detector: { rotation: false, return: true },
@@ -590,22 +586,120 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
590
586
  object: { enabled: false },
591
587
  gesture: { enabled: true }
592
588
  };
593
- // 只在提供了路径时才设置,否则让 Human.js 使用默认加载策略
594
589
  if (modelPath) {
595
590
  config.modelBasePath = modelPath;
596
591
  }
597
592
  if (wasmPath) {
598
593
  config.wasmPath = wasmPath;
599
594
  }
600
- console.log('[FaceDetectionEngine] Human.js config:', {
601
- backend: config.backend,
602
- modelBasePath: config.modelBasePath || '(using default)',
603
- wasmPath: config.wasmPath || '(using default)',
604
- userAgent: navigator.userAgent,
605
- platform: navigator.platform
595
+ return config;
596
+ }
597
+ /**
598
+ * Load and verify Human.js models
599
+ */
600
+ async function _loadAndVerifyHuman(human) {
601
+ const modelLoadStartTime = performance.now();
602
+ try {
603
+ await human.load();
604
+ }
605
+ catch (error) {
606
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
607
+ const errorStack = error instanceof Error ? error.stack : 'N/A';
608
+ console.error('[FaceDetectionEngine] Error during human.load():', {
609
+ errorMsg,
610
+ stack: errorStack,
611
+ backend: human.config?.backend,
612
+ hasModels: !!human.models,
613
+ modelsKeys: human.models ? Object.keys(human.models).length : 0
614
+ });
615
+ throw new Error(`Model loading error: ${errorMsg}`);
616
+ }
617
+ const loadTime = performance.now() - modelLoadStartTime;
618
+ console.log('[FaceDetectionEngine] Human.js loaded successfully', {
619
+ modelLoadTime: `${loadTime.toFixed(2)}ms`,
620
+ version: human.version,
621
+ config: human.config
606
622
  });
623
+ // 验证加载后的 Human 实例有必要的方法和属性
624
+ if (typeof human.detect !== 'function') {
625
+ throw new Error('Human.detect method not available after loading');
626
+ }
627
+ if (!human.version) {
628
+ console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
629
+ }
630
+ // 关键验证:检查模型是否真的加载了
631
+ if (!human.models || Object.keys(human.models).length === 0) {
632
+ console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
633
+ throw new Error('No models were loaded - human.models is empty');
634
+ }
635
+ // 详细检查每个关键模型及其结构
636
+ const criticalModels = ['face', 'antispoof', 'liveness'];
637
+ const missingModels = [];
638
+ for (const modelName of criticalModels) {
639
+ const model = human.models[modelName];
640
+ if (!model) {
641
+ missingModels.push(modelName);
642
+ console.error(`[FaceDetectionEngine] CRITICAL: Model '${modelName}' is missing!`);
643
+ }
644
+ else {
645
+ const isLoaded = model.loaded || model.state === 'loaded' || !!model.model;
646
+ // 检查模型是否有必要的内部结构(防止 "Cannot read properties of undefined (reading 'inputs')" 错误)
647
+ const hasExecutor = !!model['executor'];
648
+ const hasInputs = !!model.inputs && Array.isArray(model.inputs) && model.inputs.length > 0;
649
+ const hasModelUrl = !!model['modelUrl'];
650
+ console.log(`[FaceDetectionEngine] Model '${modelName}':`, {
651
+ loaded: isLoaded,
652
+ state: model.state,
653
+ hasModel: !!model.model,
654
+ hasExecutor,
655
+ hasInputs,
656
+ hasModelUrl,
657
+ inputsType: typeof model.inputs,
658
+ inputsLength: Array.isArray(model.inputs) ? model.inputs.length : 'N/A'
659
+ });
660
+ // 严格检查:模型必须有以下结构才能正常工作
661
+ if (!isLoaded || !hasExecutor || !hasModelUrl) {
662
+ missingModels.push(`${modelName} (incomplete)`);
663
+ console.error(`[FaceDetectionEngine] WARNING: Model '${modelName}' may not be fully loaded - missing structure`);
664
+ }
665
+ // 如果 inputs 未定义会导致 "Cannot read properties of undefined (reading 'inputs')" 错误
666
+ if (!hasInputs && modelName !== 'antispoof') {
667
+ console.warn(`[FaceDetectionEngine] WARNING: Model '${modelName}' has no inputs - may cause errors during detection`);
668
+ missingModels.push(`${modelName} (no inputs)`);
669
+ }
670
+ }
671
+ }
672
+ if (missingModels.length > 0) {
673
+ console.error('[FaceDetectionEngine] Some critical models failed to load:', missingModels);
674
+ throw new Error(`Critical models not loaded: ${missingModels.join(', ')}`);
675
+ }
676
+ // 打印加载的模型信息
677
+ if (human.models) {
678
+ const loadedModels = Object.entries(human.models).map(([name, model]) => ({
679
+ name,
680
+ loaded: model?.loaded || model?.state === 'loaded',
681
+ type: typeof model,
682
+ hasModel: !!model?.model
683
+ }));
684
+ console.log('[FaceDetectionEngine] All loaded models:', {
685
+ backend: human.config?.backend,
686
+ modelBasePath: human.config?.modelBasePath,
687
+ wasmPath: human.config?.wasmPath,
688
+ totalModels: Object.keys(human.models).length,
689
+ models: loadedModels,
690
+ allModelNames: Object.keys(human.models)
691
+ });
692
+ }
693
+ }
694
+ /**
695
+ * Try to load Human with a specific backend
696
+ * @param config The configuration object
697
+ * @param backend The backend to try
698
+ * @returns Human instance or null if fails
699
+ */
700
+ async function _tryLoadHumanWithBackend(backend, modelPath, wasmPath) {
701
+ const config = _createHumanConfig(backend, modelPath, wasmPath);
607
702
  const initStartTime = performance.now();
608
- console.log('[FaceDetectionEngine] Creating Human instance...');
609
703
  let human;
610
704
  try {
611
705
  human = new Human(config);
@@ -613,164 +707,74 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
613
707
  catch (error) {
614
708
  const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
615
709
  const stack = error instanceof Error ? error.stack : 'N/A';
616
- console.error('[FaceDetectionEngine] Failed to create Human instance:', {
710
+ console.error(`[FaceDetectionEngine] Failed to create Human instance (${backend}):`, {
617
711
  errorMsg,
618
712
  stack,
619
713
  backend: config.backend,
620
714
  userAgent: navigator.userAgent
621
715
  });
622
- throw new Error(`Human instantiation failed: ${errorMsg}`);
716
+ return null;
623
717
  }
624
- const instanceCreateTime = performance.now() - initStartTime;
625
- console.log(`[FaceDetectionEngine] Human instance created, took ${instanceCreateTime.toFixed(2)}ms`);
626
718
  // 验证 Human 实例
627
719
  if (!human) {
628
- throw new Error('Human instance is null after creation');
720
+ console.error(`[FaceDetectionEngine] Human instance is null (${backend})`);
721
+ return null;
629
722
  }
630
723
  // 验证 Human 实例结构(早期检测 WASM 问题)
631
724
  if (!human.config) {
632
725
  console.warn('[FaceDetectionEngine] Warning: human.config is missing');
633
726
  }
634
- console.log('[FaceDetectionEngine] Loading Human.js models...');
635
- const modelLoadStartTime = performance.now();
636
727
  try {
637
- // 添加超时机制防止无限等待
638
- const loadTimeout = new Promise((_, reject) => {
639
- const timeoutId = setTimeout(() => {
640
- reject(new Error('Human.js load() timeout after 60 seconds - possible issue with model loading on mobile'));
641
- }, 60000);
642
- });
643
- // 竞速:哪个先完成就用哪个
644
- try {
645
- await Promise.race([
646
- human.load(),
647
- loadTimeout
648
- ]);
649
- }
650
- catch (raceError) {
651
- // 如果是超时错误,直接抛出
652
- if (raceError instanceof Error && raceError.message.includes('timeout')) {
653
- throw raceError;
654
- }
655
- // 其他错误,提供更多上下文
656
- const raceErrorMsg = raceError instanceof Error ? raceError.message : 'Unknown error';
657
- const raceErrorStack = raceError instanceof Error ? raceError.stack : 'N/A';
658
- console.error('[FaceDetectionEngine] Error during human.load():', {
659
- errorMsg: raceErrorMsg,
660
- stack: raceErrorStack,
661
- backend: config.backend,
662
- hasModels: !!human.models,
663
- modelsKeys: human.models ? Object.keys(human.models).length : 0
664
- });
665
- throw new Error(`Model loading error: ${raceErrorMsg}`);
666
- }
667
- const loadTime = performance.now() - modelLoadStartTime;
728
+ await _loadAndVerifyHuman(human);
668
729
  const totalTime = performance.now() - initStartTime;
669
- console.log('[FaceDetectionEngine] Human.js loaded successfully', {
670
- modelLoadTime: `${loadTime.toFixed(2)}ms`,
671
- totalInitTime: `${totalTime.toFixed(2)}ms`,
672
- version: human.version,
673
- config: human.config
674
- });
675
- // 验证加载后的 Human 实例有必要的方法和属性
676
- if (typeof human.detect !== 'function') {
677
- throw new Error('Human.detect method not available after loading');
678
- }
679
- if (!human.version) {
680
- console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
681
- }
682
- // 关键验证:检查模型是否真的加载了
683
- if (!human.models || Object.keys(human.models).length === 0) {
684
- console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
685
- throw new Error('No models were loaded - human.models is empty');
686
- }
687
- // 详细检查每个关键模型及其结构
688
- const criticalModels = ['face', 'antispoof', 'liveness'];
689
- const missingModels = [];
690
- for (const modelName of criticalModels) {
691
- const model = human.models[modelName];
692
- if (!model) {
693
- missingModels.push(modelName);
694
- console.error(`[FaceDetectionEngine] CRITICAL: Model '${modelName}' is missing!`);
695
- }
696
- else {
697
- const isLoaded = model.loaded || model.state === 'loaded' || !!model.model;
698
- // 检查模型是否有必要的内部结构(防止 "Cannot read properties of undefined (reading 'inputs')" 错误)
699
- const hasExecutor = !!model['executor'];
700
- const hasInputs = !!model.inputs && Array.isArray(model.inputs) && model.inputs.length > 0;
701
- const hasModelUrl = !!model['modelUrl'];
702
- console.log(`[FaceDetectionEngine] Model '${modelName}':`, {
703
- loaded: isLoaded,
704
- state: model.state,
705
- hasModel: !!model.model,
706
- hasExecutor,
707
- hasInputs,
708
- hasModelUrl,
709
- inputsType: typeof model.inputs,
710
- inputsLength: Array.isArray(model.inputs) ? model.inputs.length : 'N/A'
711
- });
712
- // 严格检查:模型必须有以下结构才能正常工作
713
- if (!isLoaded || !hasExecutor || !hasModelUrl) {
714
- missingModels.push(`${modelName} (incomplete)`);
715
- console.error(`[FaceDetectionEngine] WARNING: Model '${modelName}' may not be fully loaded - missing structure`);
716
- }
717
- // 如果 inputs 未定义会导致 "Cannot read properties of undefined (reading 'inputs')" 错误
718
- if (!hasInputs && modelName !== 'antispoof') {
719
- console.warn(`[FaceDetectionEngine] WARNING: Model '${modelName}' has no inputs - may cause errors during detection`);
720
- missingModels.push(`${modelName} (no inputs)`);
721
- }
722
- }
723
- }
724
- if (missingModels.length > 0) {
725
- console.error('[FaceDetectionEngine] Some critical models failed to load:', missingModels);
726
- throw new Error(`Critical models not loaded: ${missingModels.join(', ')}`);
727
- }
728
- // 打印加载的模型信息
729
- if (human.models) {
730
- const loadedModels = Object.entries(human.models).map(([name, model]) => ({
731
- name,
732
- loaded: model?.loaded || model?.state === 'loaded',
733
- type: typeof model,
734
- hasModel: !!model?.model
735
- }));
736
- console.log('[FaceDetectionEngine] All loaded models:', {
737
- totalModels: Object.keys(human.models).length,
738
- models: loadedModels,
739
- allModelNames: Object.keys(human.models)
740
- });
741
- }
730
+ console.log(`[FaceDetectionEngine] Successfully loaded Human.js with ${backend} backend in ${totalTime.toFixed(2)}ms`);
742
731
  return human;
743
732
  }
744
733
  catch (error) {
745
734
  const errorMsg = error instanceof Error ? error.message : 'Unknown error';
746
- const stack = error instanceof Error ? error.stack : 'N/A';
747
- console.error('[FaceDetectionEngine] Human.js load failed:', {
748
- errorMsg,
749
- stack,
750
- userAgent: navigator.userAgent,
751
- platform: navigator.platform,
752
- humanVersion: human?.version,
753
- humanConfig: human?.config,
754
- backend: config.backend
755
- });
756
- // 如果是 WASM 后端失败,尝试降级到 WebGL
757
- if (selectedBackend === 'wasm' && !errorMsg.includes('timeout') && _isWebGLAvailable()) {
758
- console.warn('[FaceDetectionEngine] WASM backend failed, attempting fallback to WebGL...');
759
- config.backend = 'webgl';
760
- try {
761
- console.log('[FaceDetectionEngine] Retrying with WebGL backend');
762
- human = new Human(config);
763
- await human.load();
764
- console.log('[FaceDetectionEngine] Successfully loaded with WebGL fallback');
765
- return human;
766
- }
767
- catch (fallbackError) {
768
- const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : 'Unknown error';
769
- console.error('[FaceDetectionEngine] WebGL fallback also failed:', fallbackMsg);
770
- }
735
+ console.error(`[FaceDetectionEngine] Failed to load models with ${backend} backend:`, errorMsg);
736
+ return null;
737
+ }
738
+ }
739
+ /**
740
+ * Load Human.js
741
+ * @param modelPath - Path to model files (optional)
742
+ * @param wasmPath - Path to WASM files (optional)
743
+ * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
744
+ * @returns Promise that resolves with Human instance
745
+ */
746
+ async function loadHuman(modelPath, wasmPath, preferredBackend) {
747
+ const selectedBackend = _detectOptimalBackend(preferredBackend);
748
+ console.log('[FaceDetectionEngine] Starting Human.js initialization:', {
749
+ selectedBackend,
750
+ modelBasePath: modelPath || '(using default)',
751
+ wasmPath: wasmPath || '(using default)',
752
+ userAgent: navigator.userAgent,
753
+ platform: navigator.platform
754
+ });
755
+ // 尝试用主后端加载
756
+ const human = await _tryLoadHumanWithBackend(selectedBackend, modelPath, wasmPath);
757
+ if (human) {
758
+ return human;
759
+ }
760
+ console.log(`[FaceDetectionEngine] Human.js loading failed with ${selectedBackend} backend.`);
761
+ // 尝试用备选后端加载(最多一次降级)
762
+ let fallbackBackend;
763
+ if (selectedBackend === 'wasm' && _isWebGLAvailable()) {
764
+ fallbackBackend = 'webgl';
765
+ }
766
+ else if (selectedBackend === 'webgl') {
767
+ fallbackBackend = 'wasm';
768
+ }
769
+ if (fallbackBackend) {
770
+ console.warn(`[FaceDetectionEngine] Primary backend (${selectedBackend}) failed, attempting fallback to ${fallbackBackend}...`);
771
+ const humanFallback = await _tryLoadHumanWithBackend(fallbackBackend, modelPath, wasmPath);
772
+ if (humanFallback) {
773
+ return humanFallback;
771
774
  }
772
- throw new Error(`Human.js loading failed: ${errorMsg}`);
775
+ throw new Error(`Human.js loading failed: both ${selectedBackend} and ${fallbackBackend} backends failed`);
773
776
  }
777
+ throw new Error(`Human.js loading failed: ${selectedBackend} backend failed (no fallback available)`);
774
778
  }
775
779
  /**
776
780
  * Extract OpenCV version from getBuildInformation