@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 +0 -47
- package/dist/index.esm.js +153 -31
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +153 -31
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/library-loader.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
318
|
-
// Safari
|
|
319
|
-
if (/
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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]
|
|
736
|
+
console.log('[FaceDetectionEngine] All loaded models:', {
|
|
656
737
|
totalModels: Object.keys(human.models).length,
|
|
657
738
|
models: loadedModels,
|
|
658
|
-
|
|
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
|
-
|
|
1723
|
-
|
|
1813
|
+
const stack = humanError instanceof Error ? humanError.stack : 'N/A';
|
|
1814
|
+
// 分析错误类型,提供针对性的建议
|
|
1815
|
+
let errorContext = {
|
|
1724
1816
|
error: errorMsg,
|
|
1725
|
-
stack
|
|
1817
|
+
stack,
|
|
1726
1818
|
userAgent: navigator.userAgent,
|
|
1727
1819
|
platform: navigator.platform,
|
|
1728
|
-
browser: this.detectBrowserInfo()
|
|
1729
|
-
|
|
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.
|
|
1865
|
+
console.error('[FaceDetectionEngine] ' + errorMsg);
|
|
1744
1866
|
this.emitDebug('initialization', errorMsg, { loadTime: humanLoadTime }, 'error');
|
|
1745
1867
|
this.emit('detector-loaded', {
|
|
1746
1868
|
success: false,
|