@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.js CHANGED
@@ -195,9 +195,7 @@
195
195
  * Provides on, off, once, and emit methods for event-driven architecture
196
196
  */
197
197
  class SimpleEventEmitter {
198
- constructor() {
199
- this.listeners = new Map();
200
- }
198
+ listeners = new Map();
201
199
  /**
202
200
  * Register an event listener
203
201
  * @param event - Event name
@@ -1702,6 +1700,22 @@
1702
1700
  * 运动检测结果
1703
1701
  */
1704
1702
  class MotionDetectionResult {
1703
+ // 总体运动评分 (0-1)
1704
+ motionScore;
1705
+ // 人脸区域的光流幅度
1706
+ opticalFlowMagnitude;
1707
+ // 关键点稳定性评分 (0 = 像照片一样稳定, 1 = 自然运动)
1708
+ keypointVariance;
1709
+ // 眼睛区域运动强度
1710
+ eyeMotionScore;
1711
+ // 嘴巴区域运动强度
1712
+ mouthMotionScore;
1713
+ // 检测到的运动类型 ('none' | 'rotation' | 'translation' | 'breathing' | 'micro_expression')
1714
+ motionType;
1715
+ // 基于运动的总体活体性判断
1716
+ isLively;
1717
+ // 详细调试信息
1718
+ details;
1705
1719
  constructor(motionScore, opticalFlowMagnitude, keypointVariance, eyeMotionScore, mouthMotionScore, motionType, isLively, details) {
1706
1720
  this.motionScore = motionScore;
1707
1721
  this.opticalFlowMagnitude = opticalFlowMagnitude;
@@ -1745,17 +1759,25 @@
1745
1759
  * 使用光流、关键点跟踪和面部特征分析
1746
1760
  */
1747
1761
  class MotionLivenessDetector {
1762
+ // 配置及默认值
1763
+ minMotionThreshold;
1764
+ minKeypointVariance;
1765
+ frameBufferSize;
1766
+ eyeAspectRatioThreshold;
1767
+ motionConsistencyThreshold;
1768
+ minOpticalFlowThreshold;
1769
+ strictPhotoDetection;
1770
+ // 状态
1771
+ frameBuffer = []; // 存储 cv.Mat (gray)
1772
+ keypointHistory = [];
1773
+ faceAreaHistory = [];
1774
+ eyeAspectRatioHistory = [];
1775
+ mouthAspectRatioHistory = [];
1776
+ opticalFlowHistory = [];
1777
+ pupilSizeHistory = [];
1778
+ // OpenCV 实例
1779
+ cv = null;
1748
1780
  constructor(options = {}) {
1749
- // 状态
1750
- this.frameBuffer = []; // 存储 cv.Mat (gray)
1751
- this.keypointHistory = [];
1752
- this.faceAreaHistory = [];
1753
- this.eyeAspectRatioHistory = [];
1754
- this.mouthAspectRatioHistory = [];
1755
- this.opticalFlowHistory = [];
1756
- this.pupilSizeHistory = [];
1757
- // OpenCV 实例
1758
- this.cv = null;
1759
1781
  // 用提供的选项或默认值设置配置
1760
1782
  this.minMotionThreshold = options.minMotionThreshold ?? 0.15;
1761
1783
  this.minKeypointVariance = options.minKeypointVariance ?? 0.02;
@@ -3690,6 +3712,16 @@
3690
3712
  * 优化版屏幕采集检测结果
3691
3713
  */
3692
3714
  class ScreenCaptureDetectionResult {
3715
+ isScreenCapture;
3716
+ confidenceScore;
3717
+ // 实际执行的检测方法结果
3718
+ executedMethods;
3719
+ // 未执行的方法(因为已经有结论)
3720
+ skippedMethods;
3721
+ riskLevel;
3722
+ processingTimeMs;
3723
+ strategy;
3724
+ debug;
3693
3725
  constructor(isScreenCapture, confidenceScore, executedMethods, riskLevel, processingTimeMs, strategy, skippedMethods, debug) {
3694
3726
  this.isScreenCapture = isScreenCapture;
3695
3727
  this.confidenceScore = confidenceScore;
@@ -3718,10 +3750,13 @@
3718
3750
  * 使用级联检测策略,支持多种模式以平衡速度和精准度
3719
3751
  */
3720
3752
  class ScreenCaptureDetector {
3753
+ cv = null;
3754
+ confidenceThreshold = 0.6;
3755
+ detectionStrategy = DetectionStrategy.ADAPTIVE;
3756
+ moirePatternConfig;
3757
+ screenColorConfig;
3758
+ rgbEmissionConfig;
3721
3759
  constructor(options = {}) {
3722
- this.cv = null;
3723
- this.confidenceThreshold = 0.6;
3724
- this.detectionStrategy = DetectionStrategy.ADAPTIVE;
3725
3760
  this.confidenceThreshold = options.confidenceThreshold ?? 0.6;
3726
3761
  this.detectionStrategy = options.detectionStrategy
3727
3762
  ? stringToDetectionStrategy(options.detectionStrategy, DetectionStrategy.ADAPTIVE)
@@ -4017,21 +4052,21 @@
4017
4052
  * Internal detection state interface
4018
4053
  */
4019
4054
  class DetectionState {
4055
+ period = exports.DetectionPeriod.DETECT;
4056
+ startTime = performance.now();
4057
+ collectCount = 0;
4058
+ suspectedFraudsCount = 0;
4059
+ bestQualityScore = 0;
4060
+ bestFrameImage = null;
4061
+ bestFaceImage = null;
4062
+ completedActions = new Set();
4063
+ currentAction = null;
4064
+ actionVerifyTimeout = null;
4065
+ lastFrontalScore = 1;
4066
+ motionDetector = null;
4067
+ liveness = false;
4068
+ screenDetector = null;
4020
4069
  constructor(options) {
4021
- this.period = exports.DetectionPeriod.DETECT;
4022
- this.startTime = performance.now();
4023
- this.collectCount = 0;
4024
- this.suspectedFraudsCount = 0;
4025
- this.bestQualityScore = 0;
4026
- this.bestFrameImage = null;
4027
- this.bestFaceImage = null;
4028
- this.completedActions = new Set();
4029
- this.currentAction = null;
4030
- this.actionVerifyTimeout = null;
4031
- this.lastFrontalScore = 1;
4032
- this.motionDetector = null;
4033
- this.liveness = false;
4034
- this.screenDetector = null;
4035
4070
  Object.assign(this, options);
4036
4071
  }
4037
4072
  reset() {
@@ -4072,6 +4107,10 @@
4072
4107
  this.completedActions.add(this.currentAction);
4073
4108
  this.currentAction = null;
4074
4109
  }
4110
+ setCVInstance(cvInstance) {
4111
+ this.motionDetector?.setCVInstance(cvInstance);
4112
+ this.screenDetector?.setCVInstance(cvInstance);
4113
+ }
4075
4114
  /**
4076
4115
  * Clear action verify timeout
4077
4116
  */
@@ -4122,35 +4161,95 @@
4122
4161
  * Provides core detection logic without UI dependencies
4123
4162
  */
4124
4163
  class FaceDetectionEngine extends SimpleEventEmitter {
4164
+ options;
4165
+ // OpenCV instance
4166
+ cv = null;
4167
+ human = null;
4168
+ engineState = exports.EngineState.IDLE;
4169
+ // 视频及保存当前帧图片的Canvas元素
4170
+ videoElement = null;
4171
+ stream = null;
4172
+ frameCanvasElement = null;
4173
+ frameCanvasContext = null;
4174
+ faceCanvasElement = null;
4175
+ faceCanvasContext = null;
4176
+ detectionFrameId = null;
4177
+ actualVideoWidth = 0;
4178
+ actualVideoHeight = 0;
4179
+ detectionState;
4125
4180
  /**
4126
4181
  * Constructor
4127
4182
  * @param config - Configuration object
4128
4183
  */
4129
4184
  constructor(options) {
4130
4185
  super();
4131
- // OpenCV instance
4132
- this.cv = null;
4133
- this.human = null;
4134
- this.engineState = exports.EngineState.IDLE;
4135
- // 视频及保存当前帧图片的Canvas元素
4136
- this.videoElement = null;
4137
- this.stream = null;
4138
- this.frameCanvasElement = null;
4139
- this.frameCanvasContext = null;
4140
- this.faceCanvasElement = null;
4141
- this.faceCanvasContext = null;
4142
- this.detectionFrameId = null;
4143
- this.actualVideoWidth = 0;
4144
- this.actualVideoHeight = 0;
4145
4186
  this.options = mergeOptions(options);
4146
4187
  this.detectionState = createDetectionState(this.options);
4147
4188
  }
4189
+ /**
4190
+ * 提取错误信息的辅助方法 - 处理各种错误类型
4191
+ * @param error - 任意类型的错误对象
4192
+ * @returns 包含错误消息和堆栈的对象
4193
+ */
4194
+ extractErrorInfo(error) {
4195
+ // 处理 Error 实例
4196
+ if (error instanceof Error) {
4197
+ let causeStr;
4198
+ if (error.cause) {
4199
+ causeStr = error.cause instanceof Error ? error.cause.message : String(error.cause);
4200
+ }
4201
+ return {
4202
+ message: error.message || 'Unknown error',
4203
+ stack: error.stack || this.getStackTrace(),
4204
+ name: error.name,
4205
+ cause: causeStr
4206
+ };
4207
+ }
4208
+ // 处理其他对象类型
4209
+ if (typeof error === 'object' && error !== null) {
4210
+ let causeStr;
4211
+ if ('cause' in error) {
4212
+ const cause = error.cause;
4213
+ causeStr = cause instanceof Error ? cause.message : String(cause);
4214
+ }
4215
+ return {
4216
+ message: error.message || JSON.stringify(error),
4217
+ stack: error.stack || this.getStackTrace(),
4218
+ name: error.name,
4219
+ cause: causeStr
4220
+ };
4221
+ }
4222
+ // 处理基本类型(string, number 等)
4223
+ return {
4224
+ message: String(error),
4225
+ stack: this.getStackTrace()
4226
+ };
4227
+ }
4228
+ /**
4229
+ * 获取当前调用栈信息
4230
+ */
4231
+ getStackTrace() {
4232
+ try {
4233
+ // 创建一个Error对象来获取堆栈
4234
+ const err = new Error();
4235
+ if (err.stack) {
4236
+ // 移除前两行(Error 和 getStackTrace 本身)
4237
+ const lines = err.stack.split('\n');
4238
+ return lines.slice(2).join('\n') || 'Stack trace unavailable';
4239
+ }
4240
+ return 'Stack trace unavailable';
4241
+ }
4242
+ catch {
4243
+ return 'Stack trace unavailable';
4244
+ }
4245
+ }
4148
4246
  updateOptions(options) {
4149
4247
  if (this.engineState == exports.EngineState.DETECTING) {
4150
4248
  this.stopDetection(false);
4151
4249
  }
4152
4250
  this.options = mergeOptions(options);
4153
4251
  this.detectionState = createDetectionState(this.options);
4252
+ this.detectionState.setCVInstance(this.cv);
4154
4253
  }
4155
4254
  getEngineState() {
4156
4255
  return this.engineState;
@@ -4189,10 +4288,8 @@
4189
4288
  console.log('[FaceDetectionEngine] OpenCV loaded successfully', {
4190
4289
  version: cv_version
4191
4290
  });
4192
- // Inject OpenCV instance into motion detector
4193
- if (this.detectionState.motionDetector) {
4194
- this.detectionState.motionDetector.cv = cv;
4195
- }
4291
+ // Inject OpenCV instance into motion detector and screen detector
4292
+ this.detectionState.setCVInstance(this.cv);
4196
4293
  // Load Human.js
4197
4294
  console.log('[FaceDetectionEngine] Loading Human.js models...');
4198
4295
  this.emitDebug('initialization', 'Loading Human.js...');
@@ -4201,12 +4298,14 @@
4201
4298
  this.human = await loadHuman(this.options.human_model_path, this.options.tensorflow_wasm_path, this.options.tensorflow_backend);
4202
4299
  }
4203
4300
  catch (humanError) {
4204
- const errorMsg = humanError instanceof Error ? humanError.message : 'Unknown error';
4205
- const stack = humanError instanceof Error ? humanError.stack : 'N/A';
4301
+ const errorInfo = this.extractErrorInfo(humanError);
4302
+ const errorMsg = errorInfo.message;
4206
4303
  // 分析错误类型,提供针对性的建议
4207
4304
  let errorContext = {
4208
4305
  error: errorMsg,
4209
- stack,
4306
+ stack: errorInfo.stack,
4307
+ name: errorInfo.name,
4308
+ cause: errorInfo.cause,
4210
4309
  userAgent: navigator.userAgent,
4211
4310
  platform: navigator.userAgentData?.platform || 'unknown',
4212
4311
  browser: detectBrowserEngine(navigator.userAgent),
@@ -4310,7 +4409,8 @@
4310
4409
  this.emitDebug('initialization', 'Engine initialized and ready', loadedData);
4311
4410
  }
4312
4411
  catch (error) {
4313
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4412
+ const errorInfo = this.extractErrorInfo(error);
4413
+ const errorMsg = errorInfo.message;
4314
4414
  this.emit('detector-loaded', {
4315
4415
  success: false,
4316
4416
  error: errorMsg
@@ -4321,7 +4421,9 @@
4321
4421
  });
4322
4422
  this.emitDebug('initialization', 'Failed to load libraries', {
4323
4423
  error: errorMsg,
4324
- stack: error instanceof Error ? error.stack : 'N/A'
4424
+ stack: errorInfo.stack,
4425
+ name: errorInfo.name,
4426
+ cause: errorInfo.cause
4325
4427
  }, 'error');
4326
4428
  }
4327
4429
  finally {
@@ -4448,10 +4550,13 @@
4448
4550
  this.emitDebug('video-setup', 'Detection started');
4449
4551
  }
4450
4552
  catch (error) {
4451
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4553
+ const errorInfo = this.extractErrorInfo(error);
4554
+ const errorMsg = errorInfo.message;
4452
4555
  this.emitDebug('video-setup', 'Failed to start detection', {
4453
4556
  error: errorMsg,
4454
- stack: error instanceof Error ? error.stack : 'N/A'
4557
+ stack: errorInfo.stack,
4558
+ name: errorInfo.name,
4559
+ cause: errorInfo.cause
4455
4560
  }, 'error');
4456
4561
  this.emit('detector-error', {
4457
4562
  code: exports.ErrorCode.STREAM_ACQUISITION_FAILED,
@@ -4549,10 +4654,12 @@
4549
4654
  result = await this.human.detect(this.videoElement);
4550
4655
  }
4551
4656
  catch (detectError) {
4552
- const errorMsg = detectError instanceof Error ? detectError.message : 'Unknown error';
4657
+ const errorInfo = this.extractErrorInfo(detectError);
4658
+ const errorMsg = errorInfo.message;
4553
4659
  this.emitDebug('detection', 'Human.detect() call failed', {
4554
4660
  error: errorMsg,
4555
- stack: detectError instanceof Error ? detectError.stack : 'N/A',
4661
+ stack: errorInfo.stack,
4662
+ name: errorInfo.name,
4556
4663
  hasHuman: !!this.human,
4557
4664
  humanVersion: this.human?.version,
4558
4665
  videoReadyState: this.videoElement?.readyState,
@@ -4577,10 +4684,13 @@
4577
4684
  }
4578
4685
  }
4579
4686
  catch (error) {
4580
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4687
+ const errorInfo = this.extractErrorInfo(error);
4688
+ const errorMsg = errorInfo.message;
4581
4689
  this.emitDebug('detection', 'Unexpected error in detection loop', {
4582
4690
  error: errorMsg,
4583
- stack: error instanceof Error ? error.stack : 'N/A'
4691
+ stack: errorInfo.stack,
4692
+ name: errorInfo.name,
4693
+ cause: errorInfo.cause
4584
4694
  }, 'error');
4585
4695
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4586
4696
  }
@@ -4769,10 +4879,13 @@
4769
4879
  }
4770
4880
  }
4771
4881
  catch (error) {
4772
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
4882
+ const errorInfo = this.extractErrorInfo(error);
4883
+ const errorMsg = errorInfo.message;
4773
4884
  this.emitDebug('detection', 'Unexpected error in single face handling', {
4774
4885
  error: errorMsg,
4775
- stack: error instanceof Error ? error.stack : 'N/A'
4886
+ stack: errorInfo.stack,
4887
+ name: errorInfo.name,
4888
+ cause: errorInfo.cause
4776
4889
  }, 'error');
4777
4890
  this.scheduleNextDetection(this.options.detect_error_retry_delay);
4778
4891
  }
@@ -5402,6 +5515,8 @@
5402
5515
  * Wrapper around FaceDetectionEngine optimized for UniApp
5403
5516
  */
5404
5517
  class UniAppFaceDetectionEngine extends FaceDetectionEngine {
5518
+ resourcesInitialized = false;
5519
+ resourcesPreloaded = false;
5405
5520
  /**
5406
5521
  * Constructor
5407
5522
  * @param config - Configuration object
@@ -5415,8 +5530,6 @@
5415
5530
  tensorflow_wasm_path: config?.tensorflow_wasm_path || getWasmPath()
5416
5531
  };
5417
5532
  super(finalConfig);
5418
- this.resourcesInitialized = false;
5419
- this.resourcesPreloaded = false;
5420
5533
  // Initialize UniApp resources
5421
5534
  if (uniAppConfig.isUniApp) {
5422
5535
  initializeUniAppResources();