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

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
@@ -173,9 +173,7 @@ function mergeOptions(userConfig) {
173
173
  * Provides on, off, once, and emit methods for event-driven architecture
174
174
  */
175
175
  class SimpleEventEmitter {
176
- constructor() {
177
- this.listeners = new Map();
178
- }
176
+ listeners = new Map();
179
177
  /**
180
178
  * Register an event listener
181
179
  * @param event - Event name
@@ -1680,6 +1678,22 @@ function matToGray(cv, mat) {
1680
1678
  * 运动检测结果
1681
1679
  */
1682
1680
  class MotionDetectionResult {
1681
+ // 总体运动评分 (0-1)
1682
+ motionScore;
1683
+ // 人脸区域的光流幅度
1684
+ opticalFlowMagnitude;
1685
+ // 关键点稳定性评分 (0 = 像照片一样稳定, 1 = 自然运动)
1686
+ keypointVariance;
1687
+ // 眼睛区域运动强度
1688
+ eyeMotionScore;
1689
+ // 嘴巴区域运动强度
1690
+ mouthMotionScore;
1691
+ // 检测到的运动类型 ('none' | 'rotation' | 'translation' | 'breathing' | 'micro_expression')
1692
+ motionType;
1693
+ // 基于运动的总体活体性判断
1694
+ isLively;
1695
+ // 详细调试信息
1696
+ details;
1683
1697
  constructor(motionScore, opticalFlowMagnitude, keypointVariance, eyeMotionScore, mouthMotionScore, motionType, isLively, details) {
1684
1698
  this.motionScore = motionScore;
1685
1699
  this.opticalFlowMagnitude = opticalFlowMagnitude;
@@ -1723,17 +1737,25 @@ class MotionDetectionResult {
1723
1737
  * 使用光流、关键点跟踪和面部特征分析
1724
1738
  */
1725
1739
  class MotionLivenessDetector {
1740
+ // 配置及默认值
1741
+ minMotionThreshold;
1742
+ minKeypointVariance;
1743
+ frameBufferSize;
1744
+ eyeAspectRatioThreshold;
1745
+ motionConsistencyThreshold;
1746
+ minOpticalFlowThreshold;
1747
+ strictPhotoDetection;
1748
+ // 状态
1749
+ frameBuffer = []; // 存储 cv.Mat (gray)
1750
+ keypointHistory = [];
1751
+ faceAreaHistory = [];
1752
+ eyeAspectRatioHistory = [];
1753
+ mouthAspectRatioHistory = [];
1754
+ opticalFlowHistory = [];
1755
+ pupilSizeHistory = [];
1756
+ // OpenCV 实例
1757
+ cv = null;
1726
1758
  constructor(options = {}) {
1727
- // 状态
1728
- this.frameBuffer = []; // 存储 cv.Mat (gray)
1729
- this.keypointHistory = [];
1730
- this.faceAreaHistory = [];
1731
- this.eyeAspectRatioHistory = [];
1732
- this.mouthAspectRatioHistory = [];
1733
- this.opticalFlowHistory = [];
1734
- this.pupilSizeHistory = [];
1735
- // OpenCV 实例
1736
- this.cv = null;
1737
1759
  // 用提供的选项或默认值设置配置
1738
1760
  this.minMotionThreshold = options.minMotionThreshold ?? 0.15;
1739
1761
  this.minKeypointVariance = options.minKeypointVariance ?? 0.02;
@@ -3668,6 +3690,16 @@ function stringToDetectionStrategy(value, defaultValue) {
3668
3690
  * 优化版屏幕采集检测结果
3669
3691
  */
3670
3692
  class ScreenCaptureDetectionResult {
3693
+ isScreenCapture;
3694
+ confidenceScore;
3695
+ // 实际执行的检测方法结果
3696
+ executedMethods;
3697
+ // 未执行的方法(因为已经有结论)
3698
+ skippedMethods;
3699
+ riskLevel;
3700
+ processingTimeMs;
3701
+ strategy;
3702
+ debug;
3671
3703
  constructor(isScreenCapture, confidenceScore, executedMethods, riskLevel, processingTimeMs, strategy, skippedMethods, debug) {
3672
3704
  this.isScreenCapture = isScreenCapture;
3673
3705
  this.confidenceScore = confidenceScore;
@@ -3696,10 +3728,13 @@ class ScreenCaptureDetectionResult {
3696
3728
  * 使用级联检测策略,支持多种模式以平衡速度和精准度
3697
3729
  */
3698
3730
  class ScreenCaptureDetector {
3731
+ cv = null;
3732
+ confidenceThreshold = 0.6;
3733
+ detectionStrategy = DetectionStrategy.ADAPTIVE;
3734
+ moirePatternConfig;
3735
+ screenColorConfig;
3736
+ rgbEmissionConfig;
3699
3737
  constructor(options = {}) {
3700
- this.cv = null;
3701
- this.confidenceThreshold = 0.6;
3702
- this.detectionStrategy = DetectionStrategy.ADAPTIVE;
3703
3738
  this.confidenceThreshold = options.confidenceThreshold ?? 0.6;
3704
3739
  this.detectionStrategy = options.detectionStrategy
3705
3740
  ? stringToDetectionStrategy(options.detectionStrategy, DetectionStrategy.ADAPTIVE)
@@ -3995,21 +4030,21 @@ class ScreenCaptureDetector {
3995
4030
  * Internal detection state interface
3996
4031
  */
3997
4032
  class DetectionState {
4033
+ period = DetectionPeriod.DETECT;
4034
+ startTime = performance.now();
4035
+ collectCount = 0;
4036
+ suspectedFraudsCount = 0;
4037
+ bestQualityScore = 0;
4038
+ bestFrameImage = null;
4039
+ bestFaceImage = null;
4040
+ completedActions = new Set();
4041
+ currentAction = null;
4042
+ actionVerifyTimeout = null;
4043
+ lastFrontalScore = 1;
4044
+ motionDetector = null;
4045
+ liveness = false;
4046
+ screenDetector = null;
3998
4047
  constructor(options) {
3999
- this.period = DetectionPeriod.DETECT;
4000
- this.startTime = performance.now();
4001
- this.collectCount = 0;
4002
- this.suspectedFraudsCount = 0;
4003
- this.bestQualityScore = 0;
4004
- this.bestFrameImage = null;
4005
- this.bestFaceImage = null;
4006
- this.completedActions = new Set();
4007
- this.currentAction = null;
4008
- this.actionVerifyTimeout = null;
4009
- this.lastFrontalScore = 1;
4010
- this.motionDetector = null;
4011
- this.liveness = false;
4012
- this.screenDetector = null;
4013
4048
  Object.assign(this, options);
4014
4049
  }
4015
4050
  reset() {
@@ -4050,6 +4085,10 @@ class DetectionState {
4050
4085
  this.completedActions.add(this.currentAction);
4051
4086
  this.currentAction = null;
4052
4087
  }
4088
+ setCVInstance(cvInstance) {
4089
+ this.motionDetector?.setCVInstance(cvInstance);
4090
+ this.screenDetector?.setCVInstance(cvInstance);
4091
+ }
4053
4092
  /**
4054
4093
  * Clear action verify timeout
4055
4094
  */
@@ -4100,35 +4139,95 @@ function createDetectionState(options) {
4100
4139
  * Provides core detection logic without UI dependencies
4101
4140
  */
4102
4141
  class FaceDetectionEngine extends SimpleEventEmitter {
4142
+ options;
4143
+ // OpenCV instance
4144
+ cv = null;
4145
+ human = null;
4146
+ engineState = EngineState.IDLE;
4147
+ // 视频及保存当前帧图片的Canvas元素
4148
+ videoElement = null;
4149
+ stream = null;
4150
+ frameCanvasElement = null;
4151
+ frameCanvasContext = null;
4152
+ faceCanvasElement = null;
4153
+ faceCanvasContext = null;
4154
+ detectionFrameId = null;
4155
+ actualVideoWidth = 0;
4156
+ actualVideoHeight = 0;
4157
+ detectionState;
4103
4158
  /**
4104
4159
  * Constructor
4105
4160
  * @param config - Configuration object
4106
4161
  */
4107
4162
  constructor(options) {
4108
4163
  super();
4109
- // OpenCV instance
4110
- this.cv = null;
4111
- this.human = null;
4112
- this.engineState = EngineState.IDLE;
4113
- // 视频及保存当前帧图片的Canvas元素
4114
- this.videoElement = null;
4115
- this.stream = null;
4116
- this.frameCanvasElement = null;
4117
- this.frameCanvasContext = null;
4118
- this.faceCanvasElement = null;
4119
- this.faceCanvasContext = null;
4120
- this.detectionFrameId = null;
4121
- this.actualVideoWidth = 0;
4122
- this.actualVideoHeight = 0;
4123
4164
  this.options = mergeOptions(options);
4124
4165
  this.detectionState = createDetectionState(this.options);
4125
4166
  }
4167
+ /**
4168
+ * 提取错误信息的辅助方法 - 处理各种错误类型
4169
+ * @param error - 任意类型的错误对象
4170
+ * @returns 包含错误消息和堆栈的对象
4171
+ */
4172
+ extractErrorInfo(error) {
4173
+ // 处理 Error 实例
4174
+ if (error instanceof Error) {
4175
+ let causeStr;
4176
+ if (error.cause) {
4177
+ causeStr = error.cause instanceof Error ? error.cause.message : String(error.cause);
4178
+ }
4179
+ return {
4180
+ message: error.message || 'Unknown error',
4181
+ stack: error.stack || this.getStackTrace(),
4182
+ name: error.name,
4183
+ cause: causeStr
4184
+ };
4185
+ }
4186
+ // 处理其他对象类型
4187
+ if (typeof error === 'object' && error !== null) {
4188
+ let causeStr;
4189
+ if ('cause' in error) {
4190
+ const cause = error.cause;
4191
+ causeStr = cause instanceof Error ? cause.message : String(cause);
4192
+ }
4193
+ return {
4194
+ message: error.message || JSON.stringify(error),
4195
+ stack: error.stack || this.getStackTrace(),
4196
+ name: error.name,
4197
+ cause: causeStr
4198
+ };
4199
+ }
4200
+ // 处理基本类型(string, number 等)
4201
+ return {
4202
+ message: String(error),
4203
+ stack: this.getStackTrace()
4204
+ };
4205
+ }
4206
+ /**
4207
+ * 获取当前调用栈信息
4208
+ */
4209
+ getStackTrace() {
4210
+ try {
4211
+ // 创建一个Error对象来获取堆栈
4212
+ const err = new Error();
4213
+ if (err.stack) {
4214
+ // 移除前两行(Error 和 getStackTrace 本身)
4215
+ const lines = err.stack.split('\n');
4216
+ return lines.slice(2).join('\n') || 'Stack trace unavailable';
4217
+ }
4218
+ return 'Stack trace unavailable';
4219
+ }
4220
+ catch {
4221
+ return 'Stack trace unavailable';
4222
+ }
4223
+ }
4126
4224
  updateOptions(options) {
4127
4225
  if (this.engineState == EngineState.DETECTING) {
4128
4226
  this.stopDetection(false);
4129
4227
  }
4130
4228
  this.options = mergeOptions(options);
4131
4229
  this.detectionState = createDetectionState(this.options);
4230
+ this.detectionState.setCVInstance(this.cv);
4132
4231
  }
4133
4232
  getEngineState() {
4134
4233
  return this.engineState;
@@ -4167,10 +4266,8 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4167
4266
  console.log('[FaceDetectionEngine] OpenCV loaded successfully', {
4168
4267
  version: cv_version
4169
4268
  });
4170
- // Inject OpenCV instance into motion detector
4171
- if (this.detectionState.motionDetector) {
4172
- this.detectionState.motionDetector.cv = cv;
4173
- }
4269
+ // Inject OpenCV instance into motion detector and screen detector
4270
+ this.detectionState.setCVInstance(this.cv);
4174
4271
  // Load Human.js
4175
4272
  console.log('[FaceDetectionEngine] Loading Human.js models...');
4176
4273
  this.emitDebug('initialization', 'Loading Human.js...');
@@ -4179,12 +4276,14 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4179
4276
  this.human = await loadHuman(this.options.human_model_path, this.options.tensorflow_wasm_path, this.options.tensorflow_backend);
4180
4277
  }
4181
4278
  catch (humanError) {
4182
- const errorMsg = humanError instanceof Error ? humanError.message : 'Unknown error';
4183
- const stack = humanError instanceof Error ? humanError.stack : 'N/A';
4279
+ const errorInfo = this.extractErrorInfo(humanError);
4280
+ const errorMsg = errorInfo.message;
4184
4281
  // 分析错误类型,提供针对性的建议
4185
4282
  let errorContext = {
4186
4283
  error: errorMsg,
4187
- stack,
4284
+ stack: errorInfo.stack,
4285
+ name: errorInfo.name,
4286
+ cause: errorInfo.cause,
4188
4287
  userAgent: navigator.userAgent,
4189
4288
  platform: navigator.userAgentData?.platform || 'unknown',
4190
4289
  browser: detectBrowserEngine(navigator.userAgent),
@@ -4288,7 +4387,8 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4288
4387
  this.emitDebug('initialization', 'Engine initialized and ready', loadedData);
4289
4388
  }
4290
4389
  catch (error) {
4291
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4390
+ const errorInfo = this.extractErrorInfo(error);
4391
+ const errorMsg = errorInfo.message;
4292
4392
  this.emit('detector-loaded', {
4293
4393
  success: false,
4294
4394
  error: errorMsg
@@ -4299,7 +4399,9 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4299
4399
  });
4300
4400
  this.emitDebug('initialization', 'Failed to load libraries', {
4301
4401
  error: errorMsg,
4302
- stack: error instanceof Error ? error.stack : 'N/A'
4402
+ stack: errorInfo.stack,
4403
+ name: errorInfo.name,
4404
+ cause: errorInfo.cause
4303
4405
  }, 'error');
4304
4406
  }
4305
4407
  finally {
@@ -4426,10 +4528,13 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4426
4528
  this.emitDebug('video-setup', 'Detection started');
4427
4529
  }
4428
4530
  catch (error) {
4429
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4531
+ const errorInfo = this.extractErrorInfo(error);
4532
+ const errorMsg = errorInfo.message;
4430
4533
  this.emitDebug('video-setup', 'Failed to start detection', {
4431
4534
  error: errorMsg,
4432
- stack: error instanceof Error ? error.stack : 'N/A'
4535
+ stack: errorInfo.stack,
4536
+ name: errorInfo.name,
4537
+ cause: errorInfo.cause
4433
4538
  }, 'error');
4434
4539
  this.emit('detector-error', {
4435
4540
  code: ErrorCode.STREAM_ACQUISITION_FAILED,
@@ -4527,10 +4632,12 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4527
4632
  result = await this.human.detect(this.videoElement);
4528
4633
  }
4529
4634
  catch (detectError) {
4530
- const errorMsg = detectError instanceof Error ? detectError.message : 'Unknown error';
4635
+ const errorInfo = this.extractErrorInfo(detectError);
4636
+ const errorMsg = errorInfo.message;
4531
4637
  this.emitDebug('detection', 'Human.detect() call failed', {
4532
4638
  error: errorMsg,
4533
- stack: detectError instanceof Error ? detectError.stack : 'N/A',
4639
+ stack: errorInfo.stack,
4640
+ name: errorInfo.name,
4534
4641
  hasHuman: !!this.human,
4535
4642
  humanVersion: this.human?.version,
4536
4643
  videoReadyState: this.videoElement?.readyState,
@@ -4555,10 +4662,13 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4555
4662
  }
4556
4663
  }
4557
4664
  catch (error) {
4558
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4665
+ const errorInfo = this.extractErrorInfo(error);
4666
+ const errorMsg = errorInfo.message;
4559
4667
  this.emitDebug('detection', 'Unexpected error in detection loop', {
4560
4668
  error: errorMsg,
4561
- stack: error instanceof Error ? error.stack : 'N/A'
4669
+ stack: errorInfo.stack,
4670
+ name: errorInfo.name,
4671
+ cause: errorInfo.cause
4562
4672
  }, 'error');
4563
4673
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4564
4674
  }
@@ -4747,10 +4857,13 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4747
4857
  }
4748
4858
  }
4749
4859
  catch (error) {
4750
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4860
+ const errorInfo = this.extractErrorInfo(error);
4861
+ const errorMsg = errorInfo.message;
4751
4862
  this.emitDebug('detection', 'Unexpected error in single face handling', {
4752
4863
  error: errorMsg,
4753
- stack: error instanceof Error ? error.stack : 'N/A'
4864
+ stack: errorInfo.stack,
4865
+ name: errorInfo.name,
4866
+ cause: errorInfo.cause
4754
4867
  }, 'error');
4755
4868
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4756
4869
  }
@@ -5380,6 +5493,8 @@ async function preloadResources() {
5380
5493
  * Wrapper around FaceDetectionEngine optimized for UniApp
5381
5494
  */
5382
5495
  class UniAppFaceDetectionEngine extends FaceDetectionEngine {
5496
+ resourcesInitialized = false;
5497
+ resourcesPreloaded = false;
5383
5498
  /**
5384
5499
  * Constructor
5385
5500
  * @param config - Configuration object
@@ -5393,8 +5508,6 @@ class UniAppFaceDetectionEngine extends FaceDetectionEngine {
5393
5508
  tensorflow_wasm_path: config?.tensorflow_wasm_path || getWasmPath()
5394
5509
  };
5395
5510
  super(finalConfig);
5396
- this.resourcesInitialized = false;
5397
- this.resourcesPreloaded = false;
5398
5511
  // Initialize UniApp resources
5399
5512
  if (uniAppConfig.isUniApp) {
5400
5513
  initializeUniAppResources();