@sssxyd/face-liveness-detector 0.2.32 → 0.2.34
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 +123 -159
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +123 -159
- package/dist/index.js.map +1 -1
- package/dist/types/library-loader.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -336,7 +336,13 @@
|
|
|
336
336
|
if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
|
|
337
337
|
return 'gecko';
|
|
338
338
|
}
|
|
339
|
-
// 2. 检测 WebKit
|
|
339
|
+
// 2. 检测 Chromium/Blink(必须在 WebKit 之前,因为 Chrome 的 user-agent 也包含 WebKit)
|
|
340
|
+
// Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
|
|
341
|
+
if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
|
|
342
|
+
return 'chromium';
|
|
343
|
+
}
|
|
344
|
+
// 3. 检测 WebKit(真正的 Safari 和 iOS 浏览器)
|
|
345
|
+
// 注意:真正的 WebKit 浏览器(Safari)user-agent 不包含 Chrome 标识
|
|
340
346
|
// 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
|
|
341
347
|
if (/webkit/i.test(ua)) {
|
|
342
348
|
// WebKit 特征明显,包括以下几种情况:
|
|
@@ -345,11 +351,6 @@
|
|
|
345
351
|
// - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
|
|
346
352
|
return 'webkit';
|
|
347
353
|
}
|
|
348
|
-
// 3. 检测 Chromium/Blink
|
|
349
|
-
// Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
|
|
350
|
-
if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
|
|
351
|
-
return 'chromium';
|
|
352
|
-
}
|
|
353
354
|
// 4. 其他浏览器 - 保守方案,使用 WASM
|
|
354
355
|
return 'other';
|
|
355
356
|
}
|
|
@@ -589,16 +590,11 @@
|
|
|
589
590
|
return null;
|
|
590
591
|
}
|
|
591
592
|
/**
|
|
592
|
-
*
|
|
593
|
-
* @param modelPath - Path to model files (optional)
|
|
594
|
-
* @param wasmPath - Path to WASM files (optional)
|
|
595
|
-
* @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
|
|
596
|
-
* @returns Promise that resolves with Human instance
|
|
593
|
+
* Create Human.js configuration object
|
|
597
594
|
*/
|
|
598
|
-
|
|
599
|
-
const selectedBackend = _detectOptimalBackend(preferredBackend);
|
|
595
|
+
function _createHumanConfig(backend, modelPath, wasmPath) {
|
|
600
596
|
const config = {
|
|
601
|
-
backend
|
|
597
|
+
backend,
|
|
602
598
|
face: {
|
|
603
599
|
enabled: true,
|
|
604
600
|
detector: { rotation: false, return: true },
|
|
@@ -612,22 +608,80 @@
|
|
|
612
608
|
object: { enabled: false },
|
|
613
609
|
gesture: { enabled: true }
|
|
614
610
|
};
|
|
615
|
-
// 只在提供了路径时才设置,否则让 Human.js 使用默认加载策略
|
|
616
611
|
if (modelPath) {
|
|
617
612
|
config.modelBasePath = modelPath;
|
|
618
613
|
}
|
|
619
614
|
if (wasmPath) {
|
|
620
615
|
config.wasmPath = wasmPath;
|
|
621
616
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
617
|
+
return config;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Load and verify Human.js models
|
|
621
|
+
*/
|
|
622
|
+
async function _loadAndVerifyHuman(human) {
|
|
623
|
+
const modelLoadStartTime = performance.now();
|
|
624
|
+
try {
|
|
625
|
+
await human.load();
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
629
|
+
const errorStack = error instanceof Error ? error.stack : 'N/A';
|
|
630
|
+
console.error('[FaceDetectionEngine] Error during human.load():', {
|
|
631
|
+
errorMsg,
|
|
632
|
+
stack: errorStack,
|
|
633
|
+
backend: human.config?.backend,
|
|
634
|
+
hasModels: !!human.models,
|
|
635
|
+
modelsKeys: human.models ? Object.keys(human.models).length : 0
|
|
636
|
+
});
|
|
637
|
+
throw new Error(`Model loading error: ${errorMsg}`);
|
|
638
|
+
}
|
|
639
|
+
const loadTime = performance.now() - modelLoadStartTime;
|
|
640
|
+
console.log('[FaceDetectionEngine] Human.js loaded successfully', {
|
|
641
|
+
modelLoadTime: `${loadTime.toFixed(2)}ms`,
|
|
642
|
+
version: human.version,
|
|
643
|
+
config: human.config
|
|
628
644
|
});
|
|
645
|
+
// 验证加载后的 Human 实例有必要的方法和属性
|
|
646
|
+
if (typeof human.detect !== 'function') {
|
|
647
|
+
throw new Error('Human.detect method not available after loading');
|
|
648
|
+
}
|
|
649
|
+
if (!human.version) {
|
|
650
|
+
console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
|
|
651
|
+
}
|
|
652
|
+
// 关键验证:检查模型是否真的加载了
|
|
653
|
+
if (!human.models || Object.keys(human.models).length === 0) {
|
|
654
|
+
console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
|
|
655
|
+
throw new Error('No models were loaded - human.models is empty');
|
|
656
|
+
}
|
|
657
|
+
// 打印加载的模型信息
|
|
658
|
+
if (human.models) {
|
|
659
|
+
const loadedModels = Object.entries(human.models).map(([name, model]) => ({
|
|
660
|
+
name,
|
|
661
|
+
loaded: model?.loaded || model?.state === 'loaded',
|
|
662
|
+
type: typeof model,
|
|
663
|
+
hasModel: !!model?.model,
|
|
664
|
+
keys: Object.keys(model).length
|
|
665
|
+
}));
|
|
666
|
+
console.log('[FaceDetectionEngine] All loaded models:', {
|
|
667
|
+
backend: human.config?.backend,
|
|
668
|
+
modelBasePath: human.config?.modelBasePath,
|
|
669
|
+
wasmPath: human.config?.wasmPath,
|
|
670
|
+
totalModels: Object.keys(human.models).length,
|
|
671
|
+
models: loadedModels,
|
|
672
|
+
allModelNames: Object.keys(human.models)
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Try to load Human with a specific backend
|
|
678
|
+
* @param config The configuration object
|
|
679
|
+
* @param backend The backend to try
|
|
680
|
+
* @returns Human instance or null if fails
|
|
681
|
+
*/
|
|
682
|
+
async function _tryLoadHumanWithBackend(backend, modelPath, wasmPath) {
|
|
683
|
+
const config = _createHumanConfig(backend, modelPath, wasmPath);
|
|
629
684
|
const initStartTime = performance.now();
|
|
630
|
-
console.log('[FaceDetectionEngine] Creating Human instance...');
|
|
631
685
|
let human;
|
|
632
686
|
try {
|
|
633
687
|
human = new Human(config);
|
|
@@ -635,164 +689,74 @@
|
|
|
635
689
|
catch (error) {
|
|
636
690
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
|
|
637
691
|
const stack = error instanceof Error ? error.stack : 'N/A';
|
|
638
|
-
console.error(
|
|
692
|
+
console.error(`[FaceDetectionEngine] Failed to create Human instance (${backend}):`, {
|
|
639
693
|
errorMsg,
|
|
640
694
|
stack,
|
|
641
695
|
backend: config.backend,
|
|
642
696
|
userAgent: navigator.userAgent
|
|
643
697
|
});
|
|
644
|
-
|
|
698
|
+
return null;
|
|
645
699
|
}
|
|
646
|
-
const instanceCreateTime = performance.now() - initStartTime;
|
|
647
|
-
console.log(`[FaceDetectionEngine] Human instance created, took ${instanceCreateTime.toFixed(2)}ms`);
|
|
648
700
|
// 验证 Human 实例
|
|
649
701
|
if (!human) {
|
|
650
|
-
|
|
702
|
+
console.error(`[FaceDetectionEngine] Human instance is null (${backend})`);
|
|
703
|
+
return null;
|
|
651
704
|
}
|
|
652
705
|
// 验证 Human 实例结构(早期检测 WASM 问题)
|
|
653
706
|
if (!human.config) {
|
|
654
707
|
console.warn('[FaceDetectionEngine] Warning: human.config is missing');
|
|
655
708
|
}
|
|
656
|
-
console.log('[FaceDetectionEngine] Loading Human.js models...');
|
|
657
|
-
const modelLoadStartTime = performance.now();
|
|
658
709
|
try {
|
|
659
|
-
|
|
660
|
-
const loadTimeout = new Promise((_, reject) => {
|
|
661
|
-
const timeoutId = setTimeout(() => {
|
|
662
|
-
reject(new Error('Human.js load() timeout after 60 seconds - possible issue with model loading on mobile'));
|
|
663
|
-
}, 60000);
|
|
664
|
-
});
|
|
665
|
-
// 竞速:哪个先完成就用哪个
|
|
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
|
-
}
|
|
689
|
-
const loadTime = performance.now() - modelLoadStartTime;
|
|
710
|
+
await _loadAndVerifyHuman(human);
|
|
690
711
|
const totalTime = performance.now() - initStartTime;
|
|
691
|
-
console.log(
|
|
692
|
-
modelLoadTime: `${loadTime.toFixed(2)}ms`,
|
|
693
|
-
totalInitTime: `${totalTime.toFixed(2)}ms`,
|
|
694
|
-
version: human.version,
|
|
695
|
-
config: human.config
|
|
696
|
-
});
|
|
697
|
-
// 验证加载后的 Human 实例有必要的方法和属性
|
|
698
|
-
if (typeof human.detect !== 'function') {
|
|
699
|
-
throw new Error('Human.detect method not available after loading');
|
|
700
|
-
}
|
|
701
|
-
if (!human.version) {
|
|
702
|
-
console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
|
|
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
|
-
}
|
|
750
|
-
// 打印加载的模型信息
|
|
751
|
-
if (human.models) {
|
|
752
|
-
const loadedModels = Object.entries(human.models).map(([name, model]) => ({
|
|
753
|
-
name,
|
|
754
|
-
loaded: model?.loaded || model?.state === 'loaded',
|
|
755
|
-
type: typeof model,
|
|
756
|
-
hasModel: !!model?.model
|
|
757
|
-
}));
|
|
758
|
-
console.log('[FaceDetectionEngine] All loaded models:', {
|
|
759
|
-
totalModels: Object.keys(human.models).length,
|
|
760
|
-
models: loadedModels,
|
|
761
|
-
allModelNames: Object.keys(human.models)
|
|
762
|
-
});
|
|
763
|
-
}
|
|
712
|
+
console.log(`[FaceDetectionEngine] Successfully loaded Human.js with ${backend} backend in ${totalTime.toFixed(2)}ms`);
|
|
764
713
|
return human;
|
|
765
714
|
}
|
|
766
715
|
catch (error) {
|
|
767
716
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
717
|
+
console.error(`[FaceDetectionEngine] Failed to load models with ${backend} backend:`, errorMsg);
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Load Human.js
|
|
723
|
+
* @param modelPath - Path to model files (optional)
|
|
724
|
+
* @param wasmPath - Path to WASM files (optional)
|
|
725
|
+
* @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
|
|
726
|
+
* @returns Promise that resolves with Human instance
|
|
727
|
+
*/
|
|
728
|
+
async function loadHuman(modelPath, wasmPath, preferredBackend) {
|
|
729
|
+
const selectedBackend = _detectOptimalBackend(preferredBackend);
|
|
730
|
+
console.log('[FaceDetectionEngine] Starting Human.js initialization:', {
|
|
731
|
+
selectedBackend,
|
|
732
|
+
modelBasePath: modelPath || '(using default)',
|
|
733
|
+
wasmPath: wasmPath || '(using default)',
|
|
734
|
+
userAgent: navigator.userAgent,
|
|
735
|
+
platform: navigator.platform
|
|
736
|
+
});
|
|
737
|
+
// 尝试用主后端加载
|
|
738
|
+
const human = await _tryLoadHumanWithBackend(selectedBackend, modelPath, wasmPath);
|
|
739
|
+
if (human) {
|
|
740
|
+
return human;
|
|
741
|
+
}
|
|
742
|
+
console.log(`[FaceDetectionEngine] Human.js loading failed with ${selectedBackend} backend.`);
|
|
743
|
+
// 尝试用备选后端加载(最多一次降级)
|
|
744
|
+
let fallbackBackend;
|
|
745
|
+
if (selectedBackend === 'wasm' && _isWebGLAvailable()) {
|
|
746
|
+
fallbackBackend = 'webgl';
|
|
747
|
+
}
|
|
748
|
+
else if (selectedBackend === 'webgl') {
|
|
749
|
+
fallbackBackend = 'wasm';
|
|
750
|
+
}
|
|
751
|
+
if (fallbackBackend) {
|
|
752
|
+
console.warn(`[FaceDetectionEngine] Primary backend (${selectedBackend}) failed, attempting fallback to ${fallbackBackend}...`);
|
|
753
|
+
const humanFallback = await _tryLoadHumanWithBackend(fallbackBackend, modelPath, wasmPath);
|
|
754
|
+
if (humanFallback) {
|
|
755
|
+
return humanFallback;
|
|
793
756
|
}
|
|
794
|
-
throw new Error(`Human.js loading failed: ${
|
|
757
|
+
throw new Error(`Human.js loading failed: both ${selectedBackend} and ${fallbackBackend} backends failed`);
|
|
795
758
|
}
|
|
759
|
+
throw new Error(`Human.js loading failed: ${selectedBackend} backend failed (no fallback available)`);
|
|
796
760
|
}
|
|
797
761
|
/**
|
|
798
762
|
* Extract OpenCV version from getBuildInformation
|