@sssxyd/face-liveness-detector 0.4.0-alpha.1 → 0.4.0-alpha.3

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 CHANGED
@@ -1632,6 +1632,46 @@ function getOpenCVVersion() {
1632
1632
  }
1633
1633
  }
1634
1634
 
1635
+ /**
1636
+ * 将绘制图片的canvas转换为mat
1637
+ * @param {any} cv OpenCV实例
1638
+ * @param {HTMLCanvasElement} canvas canvas元素
1639
+ * @param {boolean} gray 是否转换为灰度图像
1640
+ * @returns {any | null} - 转换后的Mat对象,如果转换失败则返回null
1641
+ */
1642
+ function drawCanvasToMat(cv, canvas, gray) {
1643
+ try {
1644
+ if (!cv || !canvas) {
1645
+ return null;
1646
+ }
1647
+ // Convert canvas directly to cv.Mat
1648
+ const mat = cv.imread(canvas);
1649
+ if (!mat || mat.empty()) {
1650
+ return null;
1651
+ }
1652
+ if (gray) {
1653
+ cv.cvtColor(mat, mat, cv.COLOR_BGR2GRAY);
1654
+ }
1655
+ return mat;
1656
+ }
1657
+ catch (e) {
1658
+ return null;
1659
+ }
1660
+ }
1661
+ function matToGray(cv, mat) {
1662
+ try {
1663
+ if (!cv || !mat || mat.empty()) {
1664
+ return null;
1665
+ }
1666
+ const grayMat = new cv.Mat();
1667
+ cv.cvtColor(mat, grayMat, cv.COLOR_BGR2GRAY);
1668
+ return grayMat;
1669
+ }
1670
+ catch (e) {
1671
+ return null;
1672
+ }
1673
+ }
1674
+
1635
1675
  /**
1636
1676
  * 运动和活体检测 - 防止照片攻击
1637
1677
  * 检测微妙的面部运动和运动模式,以区分真实面孔和高质量照片
@@ -2380,46 +2420,6 @@ class MotionLivenessDetector {
2380
2420
  }
2381
2421
  }
2382
2422
 
2383
- /**
2384
- * 将绘制图片的canvas转换为mat
2385
- * @param {any} cv OpenCV实例
2386
- * @param {HTMLCanvasElement} canvas canvas元素
2387
- * @param {boolean} gray 是否转换为灰度图像
2388
- * @returns {any | null} - 转换后的Mat对象,如果转换失败则返回null
2389
- */
2390
- function drawCanvasToMat(cv, canvas, gray) {
2391
- try {
2392
- if (!cv || !canvas) {
2393
- return null;
2394
- }
2395
- // Convert canvas directly to cv.Mat
2396
- const mat = cv.imread(canvas);
2397
- if (!mat || mat.empty()) {
2398
- return null;
2399
- }
2400
- if (gray) {
2401
- cv.cvtColor(mat, mat, cv.COLOR_BGR2GRAY);
2402
- }
2403
- return mat;
2404
- }
2405
- catch (e) {
2406
- return null;
2407
- }
2408
- }
2409
- function matToGray(cv, mat) {
2410
- try {
2411
- if (!cv || !mat || mat.empty()) {
2412
- return null;
2413
- }
2414
- const grayMat = new cv.Mat();
2415
- cv.cvtColor(mat, grayMat, cv.COLOR_BGR2GRAY);
2416
- return grayMat;
2417
- }
2418
- catch (e) {
2419
- return null;
2420
- }
2421
- }
2422
-
2423
2423
  /**
2424
2424
  * 屏幕色彩特征检测模块
2425
2425
  *
@@ -3991,10 +3991,6 @@ class ScreenCaptureDetector {
3991
3991
  }
3992
3992
  }
3993
3993
 
3994
- /**
3995
- * Face Detection Engine - Core Detection Engine
3996
- * Framework-agnostic face liveness detection engine
3997
- */
3998
3994
  /**
3999
3995
  * Internal detection state interface
4000
3996
  */
@@ -4017,6 +4013,7 @@ class DetectionState {
4017
4013
  Object.assign(this, options);
4018
4014
  }
4019
4015
  reset() {
4016
+ this.clearActionVerifyTimeout();
4020
4017
  const savedMotionDetector = this.motionDetector;
4021
4018
  const savedScreenDetector = this.screenDetector;
4022
4019
  savedMotionDetector?.reset();
@@ -4037,7 +4034,71 @@ class DetectionState {
4037
4034
  }
4038
4035
  return false;
4039
4036
  }
4037
+ onActionStarted(nextAction, timeoutMills, timeoutCallback) {
4038
+ if (nextAction === null) {
4039
+ return;
4040
+ }
4041
+ this.currentAction = nextAction;
4042
+ this.clearActionVerifyTimeout();
4043
+ this.actionVerifyTimeout = setTimeout(timeoutCallback, timeoutMills);
4044
+ }
4045
+ onActionCompleted() {
4046
+ if (this.currentAction === null) {
4047
+ return;
4048
+ }
4049
+ this.clearActionVerifyTimeout();
4050
+ this.completedActions.add(this.currentAction);
4051
+ this.currentAction = null;
4052
+ }
4053
+ setCVInstance(cvInstance) {
4054
+ this.motionDetector?.setCVInstance(cvInstance);
4055
+ this.screenDetector?.setCVInstance(cvInstance);
4056
+ }
4057
+ /**
4058
+ * Clear action verify timeout
4059
+ */
4060
+ clearActionVerifyTimeout() {
4061
+ if (this.actionVerifyTimeout !== null) {
4062
+ clearTimeout(this.actionVerifyTimeout);
4063
+ this.actionVerifyTimeout = null;
4064
+ }
4065
+ }
4040
4066
  }
4067
+ // <-- Add this import at the top if ResolvedEngineOptions is defined in types.ts
4068
+ function createDetectionState(options) {
4069
+ const detectionState = new DetectionState({});
4070
+ detectionState.motionDetector = new MotionLivenessDetector({
4071
+ minMotionThreshold: options.motion_liveness_min_motion_score,
4072
+ minKeypointVariance: options.motion_liveness_min_keypoint_variance,
4073
+ frameBufferSize: options.motion_liveness_frame_buffer_size,
4074
+ eyeAspectRatioThreshold: options.motion_liveness_eye_aspect_ratio_threshold
4075
+ });
4076
+ detectionState.screenDetector = new ScreenCaptureDetector({
4077
+ confidenceThreshold: options.screen_capture_confidence_threshold,
4078
+ detectionStrategy: stringToDetectionStrategy(options.screen_capture_detection_strategy),
4079
+ moireThreshold: options.screen_moire_pattern_threshold,
4080
+ moireEnableDCT: options.screen_moire_pattern_enable_dct,
4081
+ moireEnableEdgeDetection: options.screen_moire_pattern_enable_edge_detection,
4082
+ colorSaturationThreshold: options.screen_color_saturation_threshold,
4083
+ colorRgbCorrelationThreshold: options.screen_color_rgb_correlation_threshold,
4084
+ colorPixelEntropyThreshold: options.screen_color_pixel_entropy_threshold,
4085
+ colorConfidenceThreshold: options.screen_color_confidence_threshold,
4086
+ rgbLowFreqStartPercent: options.screen_rgb_low_freq_start_percent,
4087
+ rgbLowFreqEndPercent: options.screen_rgb_low_freq_end_percent,
4088
+ rgbEnergyRatioNormalizationFactor: options.screen_rgb_energy_ratio_normalization_factor,
4089
+ rgbChannelDifferenceNormalizationFactor: options.screen_rgb_channel_difference_normalization_factor,
4090
+ rgbEnergyScoreWeight: options.screen_rgb_energy_score_weight,
4091
+ rgbAsymmetryScoreWeight: options.screen_rgb_asymmetry_score_weight,
4092
+ rgbDifferenceFactorWeight: options.screen_rgb_difference_factor_weight,
4093
+ rgbConfidenceThreshold: options.screen_rgb_confidence_threshold
4094
+ });
4095
+ return detectionState;
4096
+ }
4097
+
4098
+ /**
4099
+ * Face Detection Engine - Core Detection Engine
4100
+ * Framework-agnostic face liveness detection engine
4101
+ */
4041
4102
  /**
4042
4103
  * Framework-agnostic face liveness detection engine
4043
4104
  * Provides core detection logic without UI dependencies
@@ -4064,32 +4125,15 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4064
4125
  this.actualVideoWidth = 0;
4065
4126
  this.actualVideoHeight = 0;
4066
4127
  this.options = mergeOptions(options);
4067
- this.detectionState = new DetectionState({});
4068
- this.detectionState.motionDetector = new MotionLivenessDetector({
4069
- minMotionThreshold: this.options.motion_liveness_min_motion_score,
4070
- minKeypointVariance: this.options.motion_liveness_min_keypoint_variance,
4071
- frameBufferSize: this.options.motion_liveness_frame_buffer_size,
4072
- eyeAspectRatioThreshold: this.options.motion_liveness_eye_aspect_ratio_threshold
4073
- });
4074
- this.detectionState.screenDetector = new ScreenCaptureDetector({
4075
- confidenceThreshold: this.options.screen_capture_confidence_threshold,
4076
- detectionStrategy: stringToDetectionStrategy(this.options.screen_capture_detection_strategy),
4077
- moireThreshold: this.options.screen_moire_pattern_threshold,
4078
- moireEnableDCT: this.options.screen_moire_pattern_enable_dct,
4079
- moireEnableEdgeDetection: this.options.screen_moire_pattern_enable_edge_detection,
4080
- colorSaturationThreshold: this.options.screen_color_saturation_threshold,
4081
- colorRgbCorrelationThreshold: this.options.screen_color_rgb_correlation_threshold,
4082
- colorPixelEntropyThreshold: this.options.screen_color_pixel_entropy_threshold,
4083
- colorConfidenceThreshold: this.options.screen_color_confidence_threshold,
4084
- rgbLowFreqStartPercent: this.options.screen_rgb_low_freq_start_percent,
4085
- rgbLowFreqEndPercent: this.options.screen_rgb_low_freq_end_percent,
4086
- rgbEnergyRatioNormalizationFactor: this.options.screen_rgb_energy_ratio_normalization_factor,
4087
- rgbChannelDifferenceNormalizationFactor: this.options.screen_rgb_channel_difference_normalization_factor,
4088
- rgbEnergyScoreWeight: this.options.screen_rgb_energy_score_weight,
4089
- rgbAsymmetryScoreWeight: this.options.screen_rgb_asymmetry_score_weight,
4090
- rgbDifferenceFactorWeight: this.options.screen_rgb_difference_factor_weight,
4091
- rgbConfidenceThreshold: this.options.screen_rgb_confidence_threshold
4092
- });
4128
+ this.detectionState = createDetectionState(this.options);
4129
+ }
4130
+ updateOptions(options) {
4131
+ if (this.engineState == EngineState.DETECTING) {
4132
+ this.stopDetection(false);
4133
+ }
4134
+ this.options = mergeOptions(options);
4135
+ this.detectionState = createDetectionState(this.options);
4136
+ this.detectionState.setCVInstance(this.cv);
4093
4137
  }
4094
4138
  getEngineState() {
4095
4139
  return this.engineState;
@@ -4128,10 +4172,8 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4128
4172
  console.log('[FaceDetectionEngine] OpenCV loaded successfully', {
4129
4173
  version: cv_version
4130
4174
  });
4131
- // Inject OpenCV instance into motion detector
4132
- if (this.detectionState.motionDetector) {
4133
- this.detectionState.motionDetector.cv = cv;
4134
- }
4175
+ // Inject OpenCV instance into motion detector and screen detector
4176
+ this.detectionState.setCVInstance(this.cv);
4135
4177
  // Load Human.js
4136
4178
  console.log('[FaceDetectionEngine] Loading Human.js models...');
4137
4179
  this.emitDebug('initialization', 'Loading Human.js...');
@@ -4749,7 +4791,14 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4749
4791
  handleVerifyPhase(gestures) {
4750
4792
  // No action set yet, will continue after setting
4751
4793
  if (!this.detectionState.currentAction) {
4752
- this.selectNextAction();
4794
+ if (!this.selectNextAction()) {
4795
+ this.emit('detector-error', {
4796
+ code: ErrorCode.INTERNAL_ERROR,
4797
+ message: 'No available actions to perform for liveness verification'
4798
+ });
4799
+ this.stopDetection(false);
4800
+ return;
4801
+ }
4753
4802
  this.scheduleNextDetection(this.options.detect_frame_delay * 3);
4754
4803
  return;
4755
4804
  }
@@ -4766,16 +4815,21 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4766
4815
  // Action completed
4767
4816
  this.emit('detector-action', actionComplete);
4768
4817
  this.emitDebug('liveness', 'Action detected', { action: this.detectionState.currentAction });
4769
- this.clearActionVerifyTimeout();
4770
- this.detectionState.completedActions.add(this.detectionState.currentAction);
4771
- this.detectionState.currentAction = null;
4818
+ this.detectionState.onActionCompleted();
4772
4819
  // Check if all required actions completed
4773
4820
  if (this.detectionState.completedActions.size >= this.getPerformActionCount()) {
4774
4821
  this.stopDetection(true);
4775
4822
  return;
4776
4823
  }
4777
4824
  // Select next action
4778
- this.selectNextAction();
4825
+ if (!this.selectNextAction()) {
4826
+ this.emit('detector-error', {
4827
+ code: ErrorCode.INTERNAL_ERROR,
4828
+ message: 'No available actions to perform for liveness verification'
4829
+ });
4830
+ this.stopDetection(false);
4831
+ return;
4832
+ }
4779
4833
  this.scheduleNextDetection();
4780
4834
  }
4781
4835
  /**
@@ -4844,7 +4898,7 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4844
4898
  const availableActions = (this.options.action_liveness_action_list ?? []).filter(action => !this.detectionState.completedActions.has(action));
4845
4899
  if (availableActions.length === 0) {
4846
4900
  this.emitDebug('liveness', 'No available actions to perform', { completedActions: Array.from(this.detectionState.completedActions), totalActions: this.options.action_liveness_action_list?.length ?? 0 }, 'warn');
4847
- return;
4901
+ return false;
4848
4902
  }
4849
4903
  let nextAction = availableActions[0];
4850
4904
  if (this.options.action_liveness_action_randomize) {
@@ -4852,38 +4906,24 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4852
4906
  const randomIndex = Math.floor(Math.random() * availableActions.length);
4853
4907
  nextAction = availableActions[randomIndex];
4854
4908
  }
4855
- this.detectionState.currentAction = nextAction;
4856
4909
  const actionStart = {
4857
4910
  action: nextAction,
4858
4911
  status: LivenessActionStatus.STARTED
4859
4912
  };
4860
4913
  this.emit('detector-action', actionStart);
4861
4914
  this.emitDebug('liveness', 'Action selected', { action: this.detectionState.currentAction });
4862
- // Start action verification timeout timer
4863
- this.clearActionVerifyTimeout();
4864
- this.detectionState.actionVerifyTimeout = setTimeout(() => {
4865
- if (nextAction) {
4866
- this.emitDebug('liveness', 'Action verify timeout', {
4867
- action: nextAction,
4868
- timeout: this.options.action_liveness_verify_timeout
4869
- }, 'warn');
4870
- this.emit('detector-action', {
4871
- action: nextAction,
4872
- status: LivenessActionStatus.TIMEOUT
4873
- });
4874
- this.resetDetectionState();
4875
- }
4876
- }, this.options.action_liveness_verify_timeout);
4877
- return;
4878
- }
4879
- /**
4880
- * Clear action verify timeout
4881
- */
4882
- clearActionVerifyTimeout() {
4883
- if (this.detectionState.actionVerifyTimeout !== null) {
4884
- clearTimeout(this.detectionState.actionVerifyTimeout);
4885
- this.detectionState.actionVerifyTimeout = null;
4886
- }
4915
+ this.detectionState.onActionStarted(nextAction, this.options.action_liveness_verify_timeout, () => {
4916
+ this.emitDebug('liveness', 'Action verify timeout', {
4917
+ action: nextAction,
4918
+ timeout: this.options.action_liveness_verify_timeout
4919
+ }, 'warn');
4920
+ this.emit('detector-action', {
4921
+ action: nextAction,
4922
+ status: LivenessActionStatus.TIMEOUT
4923
+ });
4924
+ this.resetDetectionState();
4925
+ });
4926
+ return true;
4887
4927
  }
4888
4928
  /**
4889
4929
  * Detect specific action