@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/dist/index.js
CHANGED
|
@@ -332,21 +332,25 @@
|
|
|
332
332
|
}
|
|
333
333
|
function _detectBrowserEngine(userAgent) {
|
|
334
334
|
const ua = userAgent.toLowerCase();
|
|
335
|
-
// 检测 Gecko (Firefox)
|
|
335
|
+
// 1. 检测 Gecko (Firefox)
|
|
336
336
|
if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
|
|
337
337
|
return 'gecko';
|
|
338
338
|
}
|
|
339
|
-
// 检测 WebKit
|
|
340
|
-
// Safari
|
|
341
|
-
if (/
|
|
339
|
+
// 2. 检测 WebKit
|
|
340
|
+
// 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
|
|
341
|
+
if (/webkit/i.test(ua)) {
|
|
342
|
+
// WebKit 特征明显,包括以下几种情况:
|
|
343
|
+
// - 真正的 Safari(有 Safari 标识)
|
|
344
|
+
// - iOS 浏览器(有 Mobile Safari 标识)
|
|
345
|
+
// - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
|
|
342
346
|
return 'webkit';
|
|
343
347
|
}
|
|
344
|
-
// 检测 Chromium/Blink
|
|
348
|
+
// 3. 检测 Chromium/Blink
|
|
345
349
|
// Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
|
|
346
350
|
if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
|
|
347
351
|
return 'chromium';
|
|
348
352
|
}
|
|
349
|
-
//
|
|
353
|
+
// 4. 其他浏览器 - 保守方案,使用 WASM
|
|
350
354
|
return 'other';
|
|
351
355
|
}
|
|
352
356
|
function _getOptimalBackendForEngine(engine) {
|
|
@@ -592,8 +596,9 @@
|
|
|
592
596
|
* @returns Promise that resolves with Human instance
|
|
593
597
|
*/
|
|
594
598
|
async function loadHuman(modelPath, wasmPath, preferredBackend) {
|
|
599
|
+
const selectedBackend = _detectOptimalBackend(preferredBackend);
|
|
595
600
|
const config = {
|
|
596
|
-
backend:
|
|
601
|
+
backend: selectedBackend,
|
|
597
602
|
face: {
|
|
598
603
|
enabled: true,
|
|
599
604
|
detector: { rotation: false, return: true },
|
|
@@ -629,7 +634,13 @@
|
|
|
629
634
|
}
|
|
630
635
|
catch (error) {
|
|
631
636
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
|
|
632
|
-
|
|
637
|
+
const stack = error instanceof Error ? error.stack : 'N/A';
|
|
638
|
+
console.error('[FaceDetectionEngine] Failed to create Human instance:', {
|
|
639
|
+
errorMsg,
|
|
640
|
+
stack,
|
|
641
|
+
backend: config.backend,
|
|
642
|
+
userAgent: navigator.userAgent
|
|
643
|
+
});
|
|
633
644
|
throw new Error(`Human instantiation failed: ${errorMsg}`);
|
|
634
645
|
}
|
|
635
646
|
const instanceCreateTime = performance.now() - initStartTime;
|
|
@@ -638,6 +649,10 @@
|
|
|
638
649
|
if (!human) {
|
|
639
650
|
throw new Error('Human instance is null after creation');
|
|
640
651
|
}
|
|
652
|
+
// 验证 Human 实例结构(早期检测 WASM 问题)
|
|
653
|
+
if (!human.config) {
|
|
654
|
+
console.warn('[FaceDetectionEngine] Warning: human.config is missing');
|
|
655
|
+
}
|
|
641
656
|
console.log('[FaceDetectionEngine] Loading Human.js models...');
|
|
642
657
|
const modelLoadStartTime = performance.now();
|
|
643
658
|
try {
|
|
@@ -648,10 +663,29 @@
|
|
|
648
663
|
}, 60000);
|
|
649
664
|
});
|
|
650
665
|
// 竞速:哪个先完成就用哪个
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
666
|
+
try {
|
|
667
|
+
await Promise.race([
|
|
668
|
+
human.load(),
|
|
669
|
+
loadTimeout
|
|
670
|
+
]);
|
|
671
|
+
}
|
|
672
|
+
catch (raceError) {
|
|
673
|
+
// 如果是超时错误,直接抛出
|
|
674
|
+
if (raceError instanceof Error && raceError.message.includes('timeout')) {
|
|
675
|
+
throw raceError;
|
|
676
|
+
}
|
|
677
|
+
// 其他错误,提供更多上下文
|
|
678
|
+
const raceErrorMsg = raceError instanceof Error ? raceError.message : 'Unknown error';
|
|
679
|
+
const raceErrorStack = raceError instanceof Error ? raceError.stack : 'N/A';
|
|
680
|
+
console.error('[FaceDetectionEngine] Error during human.load():', {
|
|
681
|
+
errorMsg: raceErrorMsg,
|
|
682
|
+
stack: raceErrorStack,
|
|
683
|
+
backend: config.backend,
|
|
684
|
+
hasModels: !!human.models,
|
|
685
|
+
modelsKeys: human.models ? Object.keys(human.models).length : 0
|
|
686
|
+
});
|
|
687
|
+
throw new Error(`Model loading error: ${raceErrorMsg}`);
|
|
688
|
+
}
|
|
655
689
|
const loadTime = performance.now() - modelLoadStartTime;
|
|
656
690
|
const totalTime = performance.now() - initStartTime;
|
|
657
691
|
console.log('[FaceDetectionEngine] Human.js loaded successfully', {
|
|
@@ -667,26 +701,66 @@
|
|
|
667
701
|
if (!human.version) {
|
|
668
702
|
console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
|
|
669
703
|
}
|
|
704
|
+
// 关键验证:检查模型是否真的加载了
|
|
705
|
+
if (!human.models || Object.keys(human.models).length === 0) {
|
|
706
|
+
console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
|
|
707
|
+
throw new Error('No models were loaded - human.models is empty');
|
|
708
|
+
}
|
|
709
|
+
// 详细检查每个关键模型及其结构
|
|
710
|
+
const criticalModels = ['face', 'antispoof', 'liveness'];
|
|
711
|
+
const missingModels = [];
|
|
712
|
+
for (const modelName of criticalModels) {
|
|
713
|
+
const model = human.models[modelName];
|
|
714
|
+
if (!model) {
|
|
715
|
+
missingModels.push(modelName);
|
|
716
|
+
console.error(`[FaceDetectionEngine] CRITICAL: Model '${modelName}' is missing!`);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
const isLoaded = model.loaded || model.state === 'loaded' || !!model.model;
|
|
720
|
+
// 检查模型是否有必要的内部结构(防止 "Cannot read properties of undefined (reading 'inputs')" 错误)
|
|
721
|
+
const hasExecutor = !!model['executor'];
|
|
722
|
+
const hasInputs = !!model.inputs && Array.isArray(model.inputs) && model.inputs.length > 0;
|
|
723
|
+
const hasModelUrl = !!model['modelUrl'];
|
|
724
|
+
console.log(`[FaceDetectionEngine] Model '${modelName}':`, {
|
|
725
|
+
loaded: isLoaded,
|
|
726
|
+
state: model.state,
|
|
727
|
+
hasModel: !!model.model,
|
|
728
|
+
hasExecutor,
|
|
729
|
+
hasInputs,
|
|
730
|
+
hasModelUrl,
|
|
731
|
+
inputsType: typeof model.inputs,
|
|
732
|
+
inputsLength: Array.isArray(model.inputs) ? model.inputs.length : 'N/A'
|
|
733
|
+
});
|
|
734
|
+
// 严格检查:模型必须有以下结构才能正常工作
|
|
735
|
+
if (!isLoaded || !hasExecutor || !hasModelUrl) {
|
|
736
|
+
missingModels.push(`${modelName} (incomplete)`);
|
|
737
|
+
console.error(`[FaceDetectionEngine] WARNING: Model '${modelName}' may not be fully loaded - missing structure`);
|
|
738
|
+
}
|
|
739
|
+
// 如果 inputs 未定义会导致 "Cannot read properties of undefined (reading 'inputs')" 错误
|
|
740
|
+
if (!hasInputs && modelName !== 'antispoof') {
|
|
741
|
+
console.warn(`[FaceDetectionEngine] WARNING: Model '${modelName}' has no inputs - may cause errors during detection`);
|
|
742
|
+
missingModels.push(`${modelName} (no inputs)`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (missingModels.length > 0) {
|
|
747
|
+
console.error('[FaceDetectionEngine] Some critical models failed to load:', missingModels);
|
|
748
|
+
throw new Error(`Critical models not loaded: ${missingModels.join(', ')}`);
|
|
749
|
+
}
|
|
670
750
|
// 打印加载的模型信息
|
|
671
751
|
if (human.models) {
|
|
672
752
|
const loadedModels = Object.entries(human.models).map(([name, model]) => ({
|
|
673
753
|
name,
|
|
674
754
|
loaded: model?.loaded || model?.state === 'loaded',
|
|
675
|
-
type: typeof model
|
|
755
|
+
type: typeof model,
|
|
756
|
+
hasModel: !!model?.model
|
|
676
757
|
}));
|
|
677
|
-
console.log('[FaceDetectionEngine]
|
|
758
|
+
console.log('[FaceDetectionEngine] All loaded models:', {
|
|
678
759
|
totalModels: Object.keys(human.models).length,
|
|
679
760
|
models: loadedModels,
|
|
680
|
-
|
|
761
|
+
allModelNames: Object.keys(human.models)
|
|
681
762
|
});
|
|
682
763
|
}
|
|
683
|
-
else {
|
|
684
|
-
console.warn('[FaceDetectionEngine] human.models is not available');
|
|
685
|
-
}
|
|
686
|
-
// 额外验证:检查模型是否加载成功
|
|
687
|
-
if (!human.models || Object.keys(human.models).length === 0) {
|
|
688
|
-
console.warn('[FaceDetectionEngine] Warning: human.models appears to be empty after loading');
|
|
689
|
-
}
|
|
690
764
|
return human;
|
|
691
765
|
}
|
|
692
766
|
catch (error) {
|
|
@@ -698,8 +772,25 @@
|
|
|
698
772
|
userAgent: navigator.userAgent,
|
|
699
773
|
platform: navigator.platform,
|
|
700
774
|
humanVersion: human?.version,
|
|
701
|
-
humanConfig: human?.config
|
|
775
|
+
humanConfig: human?.config,
|
|
776
|
+
backend: config.backend
|
|
702
777
|
});
|
|
778
|
+
// 如果是 WASM 后端失败,尝试降级到 WebGL
|
|
779
|
+
if (selectedBackend === 'wasm' && !errorMsg.includes('timeout') && _isWebGLAvailable()) {
|
|
780
|
+
console.warn('[FaceDetectionEngine] WASM backend failed, attempting fallback to WebGL...');
|
|
781
|
+
config.backend = 'webgl';
|
|
782
|
+
try {
|
|
783
|
+
console.log('[FaceDetectionEngine] Retrying with WebGL backend');
|
|
784
|
+
human = new Human(config);
|
|
785
|
+
await human.load();
|
|
786
|
+
console.log('[FaceDetectionEngine] Successfully loaded with WebGL fallback');
|
|
787
|
+
return human;
|
|
788
|
+
}
|
|
789
|
+
catch (fallbackError) {
|
|
790
|
+
const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : 'Unknown error';
|
|
791
|
+
console.error('[FaceDetectionEngine] WebGL fallback also failed:', fallbackMsg);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
703
794
|
throw new Error(`Human.js loading failed: ${errorMsg}`);
|
|
704
795
|
}
|
|
705
796
|
}
|
|
@@ -1741,28 +1832,59 @@
|
|
|
1741
1832
|
}
|
|
1742
1833
|
catch (humanError) {
|
|
1743
1834
|
const errorMsg = humanError instanceof Error ? humanError.message : 'Unknown error';
|
|
1744
|
-
|
|
1745
|
-
|
|
1835
|
+
const stack = humanError instanceof Error ? humanError.stack : 'N/A';
|
|
1836
|
+
// 分析错误类型,提供针对性的建议
|
|
1837
|
+
let errorContext = {
|
|
1746
1838
|
error: errorMsg,
|
|
1747
|
-
stack
|
|
1839
|
+
stack,
|
|
1748
1840
|
userAgent: navigator.userAgent,
|
|
1749
1841
|
platform: navigator.platform,
|
|
1750
|
-
browser: this.detectBrowserInfo()
|
|
1751
|
-
|
|
1842
|
+
browser: this.detectBrowserInfo(),
|
|
1843
|
+
backend: this.config.tensorflow_backend,
|
|
1844
|
+
source: 'human.js'
|
|
1845
|
+
};
|
|
1846
|
+
// 特定错误类型的诊断
|
|
1847
|
+
if (errorMsg.includes('inputs')) {
|
|
1848
|
+
errorContext.diagnosis = 'Human.js internal error: Model structure incomplete';
|
|
1849
|
+
errorContext.rootCause = 'Human.js library issue - models not fully loaded or WASM backend initialization incomplete';
|
|
1850
|
+
errorContext.suggestion = 'This is a Human.js library issue. Models may not have proper executor or inputs structure. Check WASM initialization and model integrity.';
|
|
1851
|
+
}
|
|
1852
|
+
else if (errorMsg.includes('timeout')) {
|
|
1853
|
+
errorContext.diagnosis = 'Model loading timeout';
|
|
1854
|
+
errorContext.suggestion = 'Network issue or model file too large - check network conditions';
|
|
1855
|
+
}
|
|
1856
|
+
else if (errorMsg.includes('Critical models not loaded')) {
|
|
1857
|
+
errorContext.diagnosis = 'Human.js failed to load required models';
|
|
1858
|
+
errorContext.rootCause = 'Models (face, antispoof, liveness) are missing or incomplete';
|
|
1859
|
+
errorContext.suggestion = 'Check model files and ensure WASM backend is properly initialized';
|
|
1860
|
+
}
|
|
1861
|
+
else if (errorMsg.includes('empty')) {
|
|
1862
|
+
errorContext.diagnosis = 'Models object is empty after loading';
|
|
1863
|
+
errorContext.suggestion = 'Model path may be incorrect or HTTP response failed';
|
|
1864
|
+
}
|
|
1865
|
+
else if (errorMsg.includes('incomplete')) {
|
|
1866
|
+
errorContext.diagnosis = 'Models loaded but structure is incomplete';
|
|
1867
|
+
errorContext.rootCause = 'Human.js internal issue - missing executor, inputs, or modelUrl';
|
|
1868
|
+
errorContext.suggestion = 'Ensure all model resources are fully loaded and accessible';
|
|
1869
|
+
}
|
|
1870
|
+
console.error('[FaceDetectionEngine] Human.js loading failed with detailed error:', errorContext);
|
|
1871
|
+
this.emitDebug('initialization', 'Human.js loading failed with exception', errorContext, 'error');
|
|
1752
1872
|
this.emit('detector-loaded', {
|
|
1753
1873
|
success: false,
|
|
1754
|
-
error: `Failed to load Human.js: ${errorMsg}
|
|
1874
|
+
error: `Failed to load Human.js: ${errorMsg}`,
|
|
1875
|
+
details: errorContext
|
|
1755
1876
|
});
|
|
1756
1877
|
this.emit('detector-error', {
|
|
1757
1878
|
code: exports.ErrorCode.DETECTOR_NOT_INITIALIZED,
|
|
1758
|
-
message: `Human.js loading error: ${errorMsg}
|
|
1879
|
+
message: `Human.js loading error: ${errorMsg}`,
|
|
1880
|
+
details: errorContext
|
|
1759
1881
|
});
|
|
1760
1882
|
return;
|
|
1761
1883
|
}
|
|
1762
1884
|
const humanLoadTime = performance.now() - humanStartTime;
|
|
1763
1885
|
if (!this.human) {
|
|
1764
1886
|
const errorMsg = 'Failed to load Human.js: instance is null';
|
|
1765
|
-
console.
|
|
1887
|
+
console.error('[FaceDetectionEngine] ' + errorMsg);
|
|
1766
1888
|
this.emitDebug('initialization', errorMsg, { loadTime: humanLoadTime }, 'error');
|
|
1767
1889
|
this.emit('detector-loaded', {
|
|
1768
1890
|
success: false,
|