@sssxyd/face-liveness-detector 0.2.31 → 0.2.32

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/README.md CHANGED
@@ -31,53 +31,6 @@ pnpm add @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
31
31
 
32
32
  > **Note**: `@vladmandic/human` and `@techstark/opencv-js` are peer dependencies and must be installed separately to avoid bundling large libraries. This keeps your final bundle size smaller if you're already using these libraries elsewhere in your project.
33
33
 
34
- ## Quick Start - Using Local Model Files (Recommended)
35
-
36
- To improve performance and reduce external dependencies, you can download and use local copies of model files:
37
-
38
- ### Step 1: Download Model Files
39
-
40
- ```bash
41
- # Copy Human.js models locally
42
- node copy-human-models.js
43
-
44
- # Download TensorFlow.js WASM files
45
- node download-tensorflow-wasm.js
46
- ```
47
-
48
- This will create:
49
- - `public/models/` - Human.js face detection models
50
- - `public/wasm/` - TensorFlow.js WASM backend files
51
-
52
- ### Step 2: Initialize Engine with Local Files
53
-
54
- ```typescript
55
- import FaceDetectionEngine from '@sssxyd/face-liveness-detector'
56
-
57
- // Configure to use local model files
58
- const engine = new FaceDetectionEngine({
59
- human_model_path: '/models', // Path to downloaded models
60
- tensorflow_wasm_path: '/wasm', // Path to WASM files
61
- min_face_ratio: 0.5,
62
- max_face_ratio: 0.9,
63
- liveness_action_count: 1,
64
- liveness_action_list: ['blink']
65
- })
66
-
67
- // Initialize and start detection
68
- await engine.initialize()
69
- const videoElement = document.getElementById('video') as HTMLVideoElement
70
- await engine.startDetection(videoElement)
71
- ```
72
-
73
- ### Step 3: Serve Static Files
74
-
75
- Make sure your web server serves the `public/` directory:
76
-
77
- ```typescript
78
- // Express.js example
79
- app.use(express.static('public'))
80
- ```
81
34
 
82
35
  ## Quick Start - Using Default CDN Files
83
36
 
package/dist/index.esm.js CHANGED
@@ -310,21 +310,25 @@ function _isWebGLAvailable() {
310
310
  }
311
311
  function _detectBrowserEngine(userAgent) {
312
312
  const ua = userAgent.toLowerCase();
313
- // 检测 Gecko (Firefox)
313
+ // 1. 检测 Gecko (Firefox)
314
314
  if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
315
315
  return 'gecko';
316
316
  }
317
- // 检测 WebKit (Safari, iOS browsers)
318
- // Safari 的特征:有 Safari 但没有 Chrome
319
- if (/safari/i.test(ua) && !/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
317
+ // 2. 检测 WebKit
318
+ // 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
319
+ if (/webkit/i.test(ua)) {
320
+ // WebKit 特征明显,包括以下几种情况:
321
+ // - 真正的 Safari(有 Safari 标识)
322
+ // - iOS 浏览器(有 Mobile Safari 标识)
323
+ // - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
320
324
  return 'webkit';
321
325
  }
322
- // 检测 Chromium/Blink
326
+ // 3. 检测 Chromium/Blink
323
327
  // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
324
328
  if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
325
329
  return 'chromium';
326
330
  }
327
- // 默认为 other
331
+ // 4. 其他浏览器 - 保守方案,使用 WASM
328
332
  return 'other';
329
333
  }
330
334
  function _getOptimalBackendForEngine(engine) {
@@ -570,8 +574,9 @@ function getCvSync() {
570
574
  * @returns Promise that resolves with Human instance
571
575
  */
572
576
  async function loadHuman(modelPath, wasmPath, preferredBackend) {
577
+ const selectedBackend = _detectOptimalBackend(preferredBackend);
573
578
  const config = {
574
- backend: _detectOptimalBackend(preferredBackend),
579
+ backend: selectedBackend,
575
580
  face: {
576
581
  enabled: true,
577
582
  detector: { rotation: false, return: true },
@@ -607,7 +612,13 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
607
612
  }
608
613
  catch (error) {
609
614
  const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
610
- console.error('[FaceDetectionEngine] Failed to create Human instance:', errorMsg);
615
+ const stack = error instanceof Error ? error.stack : 'N/A';
616
+ console.error('[FaceDetectionEngine] Failed to create Human instance:', {
617
+ errorMsg,
618
+ stack,
619
+ backend: config.backend,
620
+ userAgent: navigator.userAgent
621
+ });
611
622
  throw new Error(`Human instantiation failed: ${errorMsg}`);
612
623
  }
613
624
  const instanceCreateTime = performance.now() - initStartTime;
@@ -616,6 +627,10 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
616
627
  if (!human) {
617
628
  throw new Error('Human instance is null after creation');
618
629
  }
630
+ // 验证 Human 实例结构(早期检测 WASM 问题)
631
+ if (!human.config) {
632
+ console.warn('[FaceDetectionEngine] Warning: human.config is missing');
633
+ }
619
634
  console.log('[FaceDetectionEngine] Loading Human.js models...');
620
635
  const modelLoadStartTime = performance.now();
621
636
  try {
@@ -626,10 +641,29 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
626
641
  }, 60000);
627
642
  });
628
643
  // 竞速:哪个先完成就用哪个
629
- await Promise.race([
630
- human.load(),
631
- loadTimeout
632
- ]);
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
+ }
633
667
  const loadTime = performance.now() - modelLoadStartTime;
634
668
  const totalTime = performance.now() - initStartTime;
635
669
  console.log('[FaceDetectionEngine] Human.js loaded successfully', {
@@ -645,26 +679,66 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
645
679
  if (!human.version) {
646
680
  console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
647
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
+ }
648
728
  // 打印加载的模型信息
649
729
  if (human.models) {
650
730
  const loadedModels = Object.entries(human.models).map(([name, model]) => ({
651
731
  name,
652
732
  loaded: model?.loaded || model?.state === 'loaded',
653
- type: typeof model
733
+ type: typeof model,
734
+ hasModel: !!model?.model
654
735
  }));
655
- console.log('[FaceDetectionEngine] Loaded models:', {
736
+ console.log('[FaceDetectionEngine] All loaded models:', {
656
737
  totalModels: Object.keys(human.models).length,
657
738
  models: loadedModels,
658
- allModels: Object.keys(human.models)
739
+ allModelNames: Object.keys(human.models)
659
740
  });
660
741
  }
661
- else {
662
- console.warn('[FaceDetectionEngine] human.models is not available');
663
- }
664
- // 额外验证:检查模型是否加载成功
665
- if (!human.models || Object.keys(human.models).length === 0) {
666
- console.warn('[FaceDetectionEngine] Warning: human.models appears to be empty after loading');
667
- }
668
742
  return human;
669
743
  }
670
744
  catch (error) {
@@ -676,8 +750,25 @@ async function loadHuman(modelPath, wasmPath, preferredBackend) {
676
750
  userAgent: navigator.userAgent,
677
751
  platform: navigator.platform,
678
752
  humanVersion: human?.version,
679
- humanConfig: human?.config
753
+ humanConfig: human?.config,
754
+ backend: config.backend
680
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
+ }
771
+ }
681
772
  throw new Error(`Human.js loading failed: ${errorMsg}`);
682
773
  }
683
774
  }
@@ -1719,28 +1810,59 @@ class FaceDetectionEngine extends SimpleEventEmitter {
1719
1810
  }
1720
1811
  catch (humanError) {
1721
1812
  const errorMsg = humanError instanceof Error ? humanError.message : 'Unknown error';
1722
- console.error('[FaceDetectionEngine] Human.js loading failed with error:', errorMsg);
1723
- this.emitDebug('initialization', 'Human.js loading failed with exception', {
1813
+ const stack = humanError instanceof Error ? humanError.stack : 'N/A';
1814
+ // 分析错误类型,提供针对性的建议
1815
+ let errorContext = {
1724
1816
  error: errorMsg,
1725
- stack: humanError instanceof Error ? humanError.stack : 'N/A',
1817
+ stack,
1726
1818
  userAgent: navigator.userAgent,
1727
1819
  platform: navigator.platform,
1728
- browser: this.detectBrowserInfo()
1729
- }, 'error');
1820
+ browser: this.detectBrowserInfo(),
1821
+ backend: this.config.tensorflow_backend,
1822
+ source: 'human.js'
1823
+ };
1824
+ // 特定错误类型的诊断
1825
+ if (errorMsg.includes('inputs')) {
1826
+ errorContext.diagnosis = 'Human.js internal error: Model structure incomplete';
1827
+ errorContext.rootCause = 'Human.js library issue - models not fully loaded or WASM backend initialization incomplete';
1828
+ errorContext.suggestion = 'This is a Human.js library issue. Models may not have proper executor or inputs structure. Check WASM initialization and model integrity.';
1829
+ }
1830
+ else if (errorMsg.includes('timeout')) {
1831
+ errorContext.diagnosis = 'Model loading timeout';
1832
+ errorContext.suggestion = 'Network issue or model file too large - check network conditions';
1833
+ }
1834
+ else if (errorMsg.includes('Critical models not loaded')) {
1835
+ errorContext.diagnosis = 'Human.js failed to load required models';
1836
+ errorContext.rootCause = 'Models (face, antispoof, liveness) are missing or incomplete';
1837
+ errorContext.suggestion = 'Check model files and ensure WASM backend is properly initialized';
1838
+ }
1839
+ else if (errorMsg.includes('empty')) {
1840
+ errorContext.diagnosis = 'Models object is empty after loading';
1841
+ errorContext.suggestion = 'Model path may be incorrect or HTTP response failed';
1842
+ }
1843
+ else if (errorMsg.includes('incomplete')) {
1844
+ errorContext.diagnosis = 'Models loaded but structure is incomplete';
1845
+ errorContext.rootCause = 'Human.js internal issue - missing executor, inputs, or modelUrl';
1846
+ errorContext.suggestion = 'Ensure all model resources are fully loaded and accessible';
1847
+ }
1848
+ console.error('[FaceDetectionEngine] Human.js loading failed with detailed error:', errorContext);
1849
+ this.emitDebug('initialization', 'Human.js loading failed with exception', errorContext, 'error');
1730
1850
  this.emit('detector-loaded', {
1731
1851
  success: false,
1732
- error: `Failed to load Human.js: ${errorMsg}`
1852
+ error: `Failed to load Human.js: ${errorMsg}`,
1853
+ details: errorContext
1733
1854
  });
1734
1855
  this.emit('detector-error', {
1735
1856
  code: ErrorCode.DETECTOR_NOT_INITIALIZED,
1736
- message: `Human.js loading error: ${errorMsg}`
1857
+ message: `Human.js loading error: ${errorMsg}`,
1858
+ details: errorContext
1737
1859
  });
1738
1860
  return;
1739
1861
  }
1740
1862
  const humanLoadTime = performance.now() - humanStartTime;
1741
1863
  if (!this.human) {
1742
1864
  const errorMsg = 'Failed to load Human.js: instance is null';
1743
- console.log('[FaceDetectionEngine] ' + errorMsg);
1865
+ console.error('[FaceDetectionEngine] ' + errorMsg);
1744
1866
  this.emitDebug('initialization', errorMsg, { loadTime: humanLoadTime }, 'error');
1745
1867
  this.emit('detector-loaded', {
1746
1868
  success: false,