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

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
@@ -96,7 +96,7 @@ const DEFAULT_OPTIONS = {
96
96
  },
97
97
  collect_image_quality_features: {
98
98
  require_full_face_in_bounds: false,
99
- min_laplacian_variance: 50,
99
+ min_laplacian_variance: 40, // 从 50 降低到 40,适应现实环境的光线和对焦变化
100
100
  min_gradient_sharpness: 0.15,
101
101
  min_blur_score: 0.6
102
102
  },
@@ -1891,20 +1891,37 @@ class MotionLivenessDetector {
1891
1891
  }
1892
1892
  /**
1893
1893
  * 验证运动一致性 - 防止照片微动攻击
1894
- * 真实面部运动:光流和关键点方差应该一致且相关
1895
- * 照片微动:只有光流或只有噪声,二者不匹配
1894
+ * 真实面部运动:光流和关键点方差应该都有意义(都不为零)
1895
+ * 照片微动:通常表现为只有光流或只有噪声,或两者都非常低
1896
+ *
1897
+ * 修复:允许不同类型运动的不同比例关系
1898
+ * - 大幅度头部运动(旋转/平移): 高keypoint variance, 中等optical flow
1899
+ * - 微妙表情运动: 中等optical flow, 低keypoint variance
1900
+ * - 照片微动: 两者都很低,或严重不匹配(一个近零一个不近零)
1896
1901
  */
1897
1902
  validateMotionConsistency(opticalFlow, keypointVariance) {
1898
- // 如果光流和关键点变化都很低,返回0(静止或照片)
1903
+ // 两个指标都非常低 = 照片或静止
1899
1904
  if (opticalFlow < 0.01 && keypointVariance < 0.01) {
1900
1905
  return 0;
1901
1906
  }
1902
- // 计算二者的相关性
1903
- // 真实运动:二者应该相关联(同时增加)
1904
- // 照片微动:可能只有一个很大(比如光流大但关键点稳定)
1905
- const ratio = Math.min(opticalFlow, keypointVariance) / Math.max(opticalFlow, keypointVariance, 0.01);
1906
- // 一致性分数:接近1表示匹配良好,接近0表示不匹配(可疑)
1907
- return ratio;
1907
+ // 照片微动的典型特征:其中一个接近零,另一个不为零
1908
+ // 但不是绝对拒绝,因为不同运动类型有不同的光流/关键点比例
1909
+ const minValue = Math.min(opticalFlow, keypointVariance);
1910
+ const maxValue = Math.max(opticalFlow, keypointVariance);
1911
+ // 如果两个都有意义的值(都 > 0.01),认为是真实运动
1912
+ // 即使比例不完全匹配(真实运动类型不同会导致不同的比例)
1913
+ if (minValue >= 0.01) {
1914
+ // 两者都有意义,即使比例不完全匹配也是可以接受的
1915
+ // 允许最大 5:1 的比例(头部大幅旋转可能导致这样的差异)
1916
+ const ratio = minValue / maxValue;
1917
+ // 高宽容度:比例超过 0.2 就认为一致
1918
+ // (之前的阈值 0.265 正好在这个范围内失败)
1919
+ return Math.max(ratio, 0.5); // 如果两者都有意义,至少返回 0.5
1920
+ }
1921
+ // 其中一个接近零
1922
+ // 这可能是照片微动,但也可能是特定运动(比如仅眼睛运动)
1923
+ // 给予较低但非零的分数
1924
+ return minValue / (maxValue + 0.001);
1908
1925
  }
1909
1926
  /**
1910
1927
  * 检测瞳孔反应 - 活体的关键特征
@@ -2338,7 +2355,7 @@ class MotionLivenessDetector {
2338
2355
  }
2339
2356
  /**
2340
2357
  * 根据运动分析确定面部是否活跃
2341
- * 增强照片防护:加入光流最小阈值和运动一致性检查
2358
+ * 修复:改进对真实面部运动的识别,减少误判
2342
2359
  */
2343
2360
  determineLiveness(motionScore, keypointVariance, motionType, opticalFlow, motionConsistency) {
2344
2361
  // 照片特征:
@@ -2346,7 +2363,6 @@ class MotionLivenessDetector {
2346
2363
  // - 关键点方差很低 (< 0.02)
2347
2364
  // - 光流几乎为零 (< 0.08) ← 最明显的照片特征
2348
2365
  // - 运动类型 = 'none'
2349
- // - 运动不一致(光流和关键点不匹配)← 照片微动
2350
2366
  // 检查1:必须有有意义的光流(照片的最弱点)
2351
2367
  // 照片无法产生光流,这是最可靠的指标
2352
2368
  if (opticalFlow < this.minOpticalFlowThreshold) {
@@ -2364,10 +2380,13 @@ class MotionLivenessDetector {
2364
2380
  if (motionType === 'none') {
2365
2381
  return false;
2366
2382
  }
2367
- // 检查5:运动一致性检查(防止照片微动)
2368
- // 真实面部运动:光流和关键点应该一致
2369
- // 照片微动:二者会严重不匹配
2370
- if (motionConsistency < this.motionConsistencyThreshold) {
2383
+ // 检查5(改进):运动一致性检查 - 但对真实面部运动更宽容
2384
+ // 如果两个指标都强劲,则宽松一致性检查
2385
+ const strongMotionIndicators = keypointVariance >= 0.5 && opticalFlow >= 0.1;
2386
+ const minConsistencyThreshold = strongMotionIndicators
2387
+ ? this.motionConsistencyThreshold * 0.5 // 放宽到 0.15(原来 0.3)
2388
+ : this.motionConsistencyThreshold;
2389
+ if (motionConsistency < minConsistencyThreshold) {
2371
2390
  return false;
2372
2391
  }
2373
2392
  // 检查6:验证物理约束(防止突跳式微动)
@@ -4683,12 +4702,12 @@ class FaceDetectionEngine extends SimpleEventEmitter {
4683
4702
  }
4684
4703
  getPerformActionCount() {
4685
4704
  if (this.options.action_liveness_action_count <= 0) {
4686
- this.emitDebug('config', 'liveness_action_count is 0 or negative', { count: this.options.action_liveness_action_count }, 'warn');
4705
+ this.emitDebug('config', 'liveness_action_count is 0 or negative', { count: this.options.action_liveness_action_count }, 'info');
4687
4706
  return 0;
4688
4707
  }
4689
4708
  const actionListLength = this.options.action_liveness_action_list?.length ?? 0;
4690
4709
  if (actionListLength === 0) {
4691
- this.emitDebug('config', 'liveness_action_list is empty', { actionListLength }, 'warn');
4710
+ this.emitDebug('config', 'liveness_action_list is empty', { actionListLength }, 'info');
4692
4711
  }
4693
4712
  return Math.min(this.options.action_liveness_action_count, actionListLength);
4694
4713
  }