@sssxyd/face-liveness-detector 0.4.0-alpha.4 → 0.4.0-alpha.5
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 +52 -47
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +52 -47
- package/dist/index.js.map +1 -1
- package/dist/types/face-detection-engine.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,16 +137,16 @@
|
|
|
137
137
|
motion_liveness_motion_consistency_threshold: 0.3,
|
|
138
138
|
motion_liveness_strict_photo_detection: false,
|
|
139
139
|
// Screen Capture Detection Settings
|
|
140
|
-
screen_capture_confidence_threshold: 0.
|
|
140
|
+
screen_capture_confidence_threshold: 0.7,
|
|
141
141
|
screen_capture_detection_strategy: 'adaptive',
|
|
142
142
|
screen_moire_pattern_threshold: 0.65,
|
|
143
143
|
screen_moire_pattern_enable_dct: true,
|
|
144
144
|
screen_moire_pattern_enable_edge_detection: true,
|
|
145
145
|
screen_color_saturation_threshold: 40,
|
|
146
|
-
screen_color_rgb_correlation_threshold: 0.
|
|
146
|
+
screen_color_rgb_correlation_threshold: 0.75,
|
|
147
147
|
screen_color_pixel_entropy_threshold: 6.5,
|
|
148
148
|
screen_color_gradient_smoothness_threshold: 0.7,
|
|
149
|
-
screen_color_confidence_threshold: 0.
|
|
149
|
+
screen_color_confidence_threshold: 0.65,
|
|
150
150
|
screen_rgb_low_freq_start_percent: 0.15,
|
|
151
151
|
screen_rgb_low_freq_end_percent: 0.35,
|
|
152
152
|
screen_rgb_energy_ratio_normalization_factor: 10,
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
screen_rgb_energy_score_weight: 0.40,
|
|
155
155
|
screen_rgb_asymmetry_score_weight: 0.40,
|
|
156
156
|
screen_rgb_difference_factor_weight: 0.20,
|
|
157
|
-
screen_rgb_confidence_threshold: 0.
|
|
157
|
+
screen_rgb_confidence_threshold: 0.65,
|
|
158
158
|
};
|
|
159
159
|
/**
|
|
160
160
|
* Merge user configuration with defaults
|
|
@@ -4538,9 +4538,16 @@
|
|
|
4538
4538
|
};
|
|
4539
4539
|
if (this.videoElement) {
|
|
4540
4540
|
this.videoElement.addEventListener('canplay', onCanPlay, { once: true });
|
|
4541
|
-
this.videoElement.play().catch(err => {
|
|
4541
|
+
this.videoElement.play().catch((err) => {
|
|
4542
4542
|
clearTimeout(timeout);
|
|
4543
4543
|
cleanup();
|
|
4544
|
+
const errorInfo = this.extractErrorInfo(err);
|
|
4545
|
+
this.emitDebug('video-setup', 'Failed to play video', {
|
|
4546
|
+
error: errorInfo.message,
|
|
4547
|
+
stack: errorInfo.stack,
|
|
4548
|
+
name: errorInfo.name,
|
|
4549
|
+
cause: errorInfo.cause
|
|
4550
|
+
}, 'error');
|
|
4544
4551
|
reject(err);
|
|
4545
4552
|
});
|
|
4546
4553
|
}
|
|
@@ -4724,34 +4731,34 @@
|
|
|
4724
4731
|
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4725
4732
|
return;
|
|
4726
4733
|
}
|
|
4727
|
-
//
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
return;
|
|
4733
|
-
}
|
|
4734
|
-
// 当前帧灰度图片
|
|
4735
|
-
const grayFrame = matToGray(this.cv, bgrFrame);
|
|
4736
|
-
if (!grayFrame) {
|
|
4737
|
-
bgrFrame.delete();
|
|
4738
|
-
this.emitDebug('detection', 'Failed to convert frame Mat to grayscale', {}, 'warn');
|
|
4739
|
-
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4740
|
-
return;
|
|
4741
|
-
}
|
|
4742
|
-
// 提取人脸区域图片及灰度图片
|
|
4743
|
-
const bgrFace = bgrFrame.roi(new this.cv.Rect(faceBox[0], faceBox[1], faceBox[2], faceBox[3]));
|
|
4744
|
-
const grayFace = matToGray(this.cv, bgrFace);
|
|
4745
|
-
if (!grayFace) {
|
|
4746
|
-
bgrFrame.delete();
|
|
4747
|
-
bgrFace.delete();
|
|
4748
|
-
this.emitDebug('detection', 'Failed to convert face Mat to grayscale', {}, 'warn');
|
|
4749
|
-
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4750
|
-
return;
|
|
4751
|
-
}
|
|
4752
|
-
// 释放不再需要的Mat
|
|
4753
|
-
bgrFrame.delete();
|
|
4734
|
+
// 所有需要删除的 Mat 对象
|
|
4735
|
+
let bgrFrame = null;
|
|
4736
|
+
let grayFrame = null;
|
|
4737
|
+
let bgrFace = null;
|
|
4738
|
+
let grayFace = null;
|
|
4754
4739
|
try {
|
|
4740
|
+
// 当前帧图片
|
|
4741
|
+
bgrFrame = drawCanvasToMat(this.cv, frameCanvas, false);
|
|
4742
|
+
if (!bgrFrame) {
|
|
4743
|
+
this.emitDebug('detection', 'Failed to convert canvas to OpenCV Mat', {}, 'warn');
|
|
4744
|
+
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
// 当前帧灰度图片
|
|
4748
|
+
grayFrame = matToGray(this.cv, bgrFrame);
|
|
4749
|
+
if (!grayFrame) {
|
|
4750
|
+
this.emitDebug('detection', 'Failed to convert frame Mat to grayscale', {}, 'warn');
|
|
4751
|
+
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
// 提取人脸区域图片及灰度图片
|
|
4755
|
+
bgrFace = bgrFrame.roi(new this.cv.Rect(faceBox[0], faceBox[1], faceBox[2], faceBox[3]));
|
|
4756
|
+
grayFace = matToGray(this.cv, bgrFace);
|
|
4757
|
+
if (!grayFace) {
|
|
4758
|
+
this.emitDebug('detection', 'Failed to convert face Mat to grayscale', {}, 'warn');
|
|
4759
|
+
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4755
4762
|
if (!this.detectionState.screenDetector) {
|
|
4756
4763
|
this.emit('detector-error', {
|
|
4757
4764
|
code: exports.ErrorCode.INTERNAL_ERROR,
|
|
@@ -4770,11 +4777,8 @@
|
|
|
4770
4777
|
}
|
|
4771
4778
|
// 屏幕捕获检测, 只关心脸部区域
|
|
4772
4779
|
const screenResult = this.detectionState.screenDetector.detectAuto(bgrFace, grayFace);
|
|
4773
|
-
bgrFace.delete();
|
|
4774
|
-
grayFace.delete();
|
|
4775
4780
|
// 屏幕捕获检测器已经准备就绪,其验证结果可信
|
|
4776
4781
|
if (screenResult.isScreenCapture) {
|
|
4777
|
-
grayFrame.delete();
|
|
4778
4782
|
this.emitDetectorInfo({ code: exports.DetectionCode.FACE_NOT_REAL, message: screenResult.getMessage(), screenConfidence: screenResult.confidenceScore });
|
|
4779
4783
|
this.emitDebug('screen-capture-detection', 'Screen capture detected - possible video replay attack', {
|
|
4780
4784
|
confidence: screenResult.confidenceScore,
|
|
@@ -4789,13 +4793,18 @@
|
|
|
4789
4793
|
if (this.detectionState.motionDetector.isReady()) {
|
|
4790
4794
|
// 运动检测器已经准备就绪,其验证结果可信
|
|
4791
4795
|
if (!motionResult.isLively) {
|
|
4792
|
-
grayFrame.delete();
|
|
4793
4796
|
this.emitDebug('motion-detection', 'Motion liveness check failed - possible photo attack', {
|
|
4794
4797
|
motionScore: motionResult.motionScore,
|
|
4795
4798
|
keypointVariance: motionResult.keypointVariance,
|
|
4799
|
+
opticalFlowMagnitude: motionResult.opticalFlowMagnitude,
|
|
4800
|
+
eyeMotionScore: motionResult.eyeMotionScore,
|
|
4801
|
+
mouthMotionScore: motionResult.mouthMotionScore,
|
|
4796
4802
|
motionType: motionResult.motionType,
|
|
4797
4803
|
minMotionScore: this.options.motion_liveness_min_motion_score,
|
|
4798
|
-
minKeypointVariance: this.options.motion_liveness_min_keypoint_variance
|
|
4804
|
+
minKeypointVariance: this.options.motion_liveness_min_keypoint_variance,
|
|
4805
|
+
minOpticalFlowThreshold: this.options.motion_liveness_min_optical_flow_threshold,
|
|
4806
|
+
minMotionConsistencyThreshold: this.options.motion_liveness_motion_consistency_threshold,
|
|
4807
|
+
details: motionResult.details
|
|
4799
4808
|
}, 'warn');
|
|
4800
4809
|
this.emitDetectorInfo({
|
|
4801
4810
|
code: exports.DetectionCode.FACE_NOT_LIVE,
|
|
@@ -4844,7 +4853,6 @@
|
|
|
4844
4853
|
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4845
4854
|
return;
|
|
4846
4855
|
}
|
|
4847
|
-
grayFrame.delete();
|
|
4848
4856
|
// 当前帧通过常规检查
|
|
4849
4857
|
this.emitDetectorInfo({ passed: true, code: exports.DetectionCode.FACE_CHECK_PASS, faceRatio: faceRatio, faceFrontal: frontal, imageQuality: qualityResult.score });
|
|
4850
4858
|
// 处理不同检测阶段的逻辑
|
|
@@ -4890,18 +4898,15 @@
|
|
|
4890
4898
|
this.scheduleNextDetection(this.options.detect_error_retry_delay);
|
|
4891
4899
|
}
|
|
4892
4900
|
finally {
|
|
4893
|
-
|
|
4901
|
+
// 统一在 finally 块中删除所有 Mat 对象
|
|
4902
|
+
if (grayFrame)
|
|
4894
4903
|
grayFrame.delete();
|
|
4895
|
-
|
|
4896
|
-
if (bgrFrame) {
|
|
4904
|
+
if (bgrFrame)
|
|
4897
4905
|
bgrFrame.delete();
|
|
4898
|
-
|
|
4899
|
-
if (bgrFace) {
|
|
4906
|
+
if (bgrFace)
|
|
4900
4907
|
bgrFace.delete();
|
|
4901
|
-
|
|
4902
|
-
if (grayFace) {
|
|
4908
|
+
if (grayFace)
|
|
4903
4909
|
grayFace.delete();
|
|
4904
|
-
}
|
|
4905
4910
|
}
|
|
4906
4911
|
}
|
|
4907
4912
|
/**
|
|
@@ -5161,7 +5166,7 @@
|
|
|
5161
5166
|
return null;
|
|
5162
5167
|
}
|
|
5163
5168
|
this.frameCanvasContext.drawImage(this.videoElement, 0, 0, videoWidth_actual, videoHeight_actual);
|
|
5164
|
-
this.emitDebug('capture', 'Frame drawn to canvas');
|
|
5169
|
+
this.emitDebug('capture', 'Frame drawn to canvas as ' + videoHeight_actual + 'x' + videoWidth_actual);
|
|
5165
5170
|
return this.frameCanvasElement;
|
|
5166
5171
|
}
|
|
5167
5172
|
catch (e) {
|