@sssxyd/face-liveness-detector 0.4.0-alpha.4 → 0.4.0-alpha.6

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.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.6,
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.85,
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.6,
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.60,
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
  }
@@ -4604,6 +4611,7 @@
4604
4611
  * Reset detection state
4605
4612
  */
4606
4613
  resetDetectionState() {
4614
+ this.emitDebug('detection', 'Resetting detection state...');
4607
4615
  this.detectionState.reset();
4608
4616
  this.actualVideoWidth = 0;
4609
4617
  this.actualVideoHeight = 0;
@@ -4724,34 +4732,34 @@
4724
4732
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4725
4733
  return;
4726
4734
  }
4727
- // 当前帧图片
4728
- const bgrFrame = drawCanvasToMat(this.cv, frameCanvas, false);
4729
- if (!bgrFrame) {
4730
- this.emitDebug('detection', 'Failed to convert canvas to OpenCV Mat', {}, 'warn');
4731
- this.scheduleNextDetection(this.options.detect_error_retry_delay);
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();
4735
+ // 所有需要删除的 Mat 对象
4736
+ let bgrFrame = null;
4737
+ let grayFrame = null;
4738
+ let bgrFace = null;
4739
+ let grayFace = null;
4754
4740
  try {
4741
+ // 当前帧图片
4742
+ bgrFrame = drawCanvasToMat(this.cv, frameCanvas, false);
4743
+ if (!bgrFrame) {
4744
+ this.emitDebug('detection', 'Failed to convert canvas to OpenCV Mat', {}, 'warn');
4745
+ this.scheduleNextDetection(this.options.detect_error_retry_delay);
4746
+ return;
4747
+ }
4748
+ // 当前帧灰度图片
4749
+ grayFrame = matToGray(this.cv, bgrFrame);
4750
+ if (!grayFrame) {
4751
+ this.emitDebug('detection', 'Failed to convert frame Mat to grayscale', {}, 'warn');
4752
+ this.scheduleNextDetection(this.options.detect_error_retry_delay);
4753
+ return;
4754
+ }
4755
+ // 提取人脸区域图片及灰度图片
4756
+ bgrFace = bgrFrame.roi(new this.cv.Rect(faceBox[0], faceBox[1], faceBox[2], faceBox[3]));
4757
+ grayFace = matToGray(this.cv, bgrFace);
4758
+ if (!grayFace) {
4759
+ this.emitDebug('detection', 'Failed to convert face Mat to grayscale', {}, 'warn');
4760
+ this.scheduleNextDetection(this.options.detect_error_retry_delay);
4761
+ return;
4762
+ }
4755
4763
  if (!this.detectionState.screenDetector) {
4756
4764
  this.emit('detector-error', {
4757
4765
  code: exports.ErrorCode.INTERNAL_ERROR,
@@ -4770,15 +4778,37 @@
4770
4778
  }
4771
4779
  // 屏幕捕获检测, 只关心脸部区域
4772
4780
  const screenResult = this.detectionState.screenDetector.detectAuto(bgrFace, grayFace);
4773
- bgrFace.delete();
4774
- grayFace.delete();
4775
4781
  // 屏幕捕获检测器已经准备就绪,其验证结果可信
4776
4782
  if (screenResult.isScreenCapture) {
4777
- grayFrame.delete();
4783
+ // 从 executedMethods 提取各检测器的置信度
4784
+ const methodConfidences = screenResult.executedMethods.reduce((acc, method) => {
4785
+ if (method.method.includes('Moiré')) {
4786
+ acc.moireConfidence = method.confidence;
4787
+ }
4788
+ else if (method.method.includes('Color')) {
4789
+ acc.colorConfidence = method.confidence;
4790
+ }
4791
+ else if (method.method.includes('RGB')) {
4792
+ acc.rgbConfidence = method.confidence;
4793
+ }
4794
+ return acc;
4795
+ }, {});
4778
4796
  this.emitDetectorInfo({ code: exports.DetectionCode.FACE_NOT_REAL, message: screenResult.getMessage(), screenConfidence: screenResult.confidenceScore });
4779
4797
  this.emitDebug('screen-capture-detection', 'Screen capture detected - possible video replay attack', {
4780
- confidence: screenResult.confidenceScore,
4781
- minConfidence: this.options.screen_capture_confidence_threshold
4798
+ overallConfidence: screenResult.confidenceScore,
4799
+ minConfidenceThreshold: this.options.screen_capture_confidence_threshold,
4800
+ moireConfidence: methodConfidences.moireConfidence ?? 'N/A',
4801
+ colorConfidence: methodConfidences.colorConfidence ?? 'N/A',
4802
+ rgbConfidence: methodConfidences.rgbConfidence ?? 'N/A',
4803
+ detectionStrategy: screenResult.strategy,
4804
+ riskLevel: screenResult.riskLevel,
4805
+ processingTimeMs: screenResult.processingTimeMs,
4806
+ executedMethods: screenResult.executedMethods.map((m) => ({
4807
+ method: m.method,
4808
+ isScreenCapture: m.isScreenCapture,
4809
+ confidence: m.confidence
4810
+ })),
4811
+ skippedMethods: screenResult.skippedMethods
4782
4812
  }, 'warn');
4783
4813
  this.resetDetectionState();
4784
4814
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
@@ -4789,13 +4819,18 @@
4789
4819
  if (this.detectionState.motionDetector.isReady()) {
4790
4820
  // 运动检测器已经准备就绪,其验证结果可信
4791
4821
  if (!motionResult.isLively) {
4792
- grayFrame.delete();
4793
4822
  this.emitDebug('motion-detection', 'Motion liveness check failed - possible photo attack', {
4794
4823
  motionScore: motionResult.motionScore,
4795
4824
  keypointVariance: motionResult.keypointVariance,
4825
+ opticalFlowMagnitude: motionResult.opticalFlowMagnitude,
4826
+ eyeMotionScore: motionResult.eyeMotionScore,
4827
+ mouthMotionScore: motionResult.mouthMotionScore,
4796
4828
  motionType: motionResult.motionType,
4797
4829
  minMotionScore: this.options.motion_liveness_min_motion_score,
4798
- minKeypointVariance: this.options.motion_liveness_min_keypoint_variance
4830
+ minKeypointVariance: this.options.motion_liveness_min_keypoint_variance,
4831
+ minOpticalFlowThreshold: this.options.motion_liveness_min_optical_flow_threshold,
4832
+ minMotionConsistencyThreshold: this.options.motion_liveness_motion_consistency_threshold,
4833
+ details: motionResult.details
4799
4834
  }, 'warn');
4800
4835
  this.emitDetectorInfo({
4801
4836
  code: exports.DetectionCode.FACE_NOT_LIVE,
@@ -4844,7 +4879,6 @@
4844
4879
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4845
4880
  return;
4846
4881
  }
4847
- grayFrame.delete();
4848
4882
  // 当前帧通过常规检查
4849
4883
  this.emitDetectorInfo({ passed: true, code: exports.DetectionCode.FACE_CHECK_PASS, faceRatio: faceRatio, faceFrontal: frontal, imageQuality: qualityResult.score });
4850
4884
  // 处理不同检测阶段的逻辑
@@ -4890,18 +4924,15 @@
4890
4924
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4891
4925
  }
4892
4926
  finally {
4893
- if (grayFrame) {
4927
+ // 统一在 finally 块中删除所有 Mat 对象
4928
+ if (grayFrame)
4894
4929
  grayFrame.delete();
4895
- }
4896
- if (bgrFrame) {
4930
+ if (bgrFrame)
4897
4931
  bgrFrame.delete();
4898
- }
4899
- if (bgrFace) {
4932
+ if (bgrFace)
4900
4933
  bgrFace.delete();
4901
- }
4902
- if (grayFace) {
4934
+ if (grayFace)
4903
4935
  grayFace.delete();
4904
- }
4905
4936
  }
4906
4937
  }
4907
4938
  /**
@@ -5161,7 +5192,7 @@
5161
5192
  return null;
5162
5193
  }
5163
5194
  this.frameCanvasContext.drawImage(this.videoElement, 0, 0, videoWidth_actual, videoHeight_actual);
5164
- this.emitDebug('capture', 'Frame drawn to canvas');
5195
+ this.emitDebug('capture', 'Frame drawn to canvas as ' + videoHeight_actual + 'x' + videoWidth_actual);
5165
5196
  return this.frameCanvasElement;
5166
5197
  }
5167
5198
  catch (e) {