fix: 拍照引导检测链路校准——模拟摄像头闭环修复四个实测问题
用户真机症状:一直『未检测到人脸』;脸进框却提示太远;凑近又超框。用 C3 三视图生成 Y4M 模拟摄像头闭环复现并定位: - 检测可靠性:tinyFaceDetector 320/0.4 → 416/0.3(正脸置信度 0.67→0.99,框更准);连续漏检 ≥3 帧才报未检测到人脸(单帧抖动不再刷屏) - 距离阈值:0.35/0.8 → 0.26/0.58(以取景框≈42% 屏高、416 输入实测框 0.40–0.44 标定;原阈值下脸进框即误判太远) - HeadPose:check() 双重镜像符号 bug(estimate 已翻转 yaw,check 又翻一次→侧脸引导方向相反);YAW_COEFF 2.2→3.8(±45° 侧脸实测只读 ±25°);PITCH_BASELINE 0.32→0.45(正脸误读 29° 被判低头) - head-pose 自测夹具按新系数数值标定,全部通过 - e2e 新增 E7:模拟摄像头全自动三连拍(正脸→右脸→左脸,姿态引导+倒计时+抓帧,分析请求拦截不烧次数包),全量绿
This commit is contained in:
+22
-21
@@ -52,13 +52,13 @@
|
||||
CHIN: 8
|
||||
};
|
||||
|
||||
// ---- 经验系数(⚠ 真机需校准) ----
|
||||
// ---- 经验系数(2026-09-27 用 C3 三视图(+45°侧脸)模拟摄像头校准) ----
|
||||
// yaw = atan((dLeft-dRight)/(dLeft+dRight)) × YAW_COEFF。
|
||||
// atan 输出 ∈ (-45°,45°),取 2.2 使真实 ~45° 侧脸读数落在 40° 上下。
|
||||
var YAW_COEFF = 2.2;
|
||||
// pitch:鼻底相对「眉眼连线中点」的纵向偏移 / 脸高(眉 19/24 — 下巴 8),
|
||||
// 校准:±45° 实拍侧脸读数 ±25°(2.2 偏小)→ 3.8 使读数落在 44° 上下;正脸 ~4°,仍 <12° 阈值
|
||||
var YAW_COEFF = 3.8;
|
||||
// pitch:鼻底相对「眉眼连线中点」的纵向偏移 / 脸高(眉 19/24 — 下巴 8),
|
||||
// 减去中性正脸基线后线性映射为角度。
|
||||
var PITCH_BASELINE = 0.32; // 正脸时 offset/faceHeight 的典型值
|
||||
var PITCH_BASELINE = 0.45; // 校准:0.32 时正脸误读 ~29°(被误判低头),实测基线 0.48,留 0.03 余量
|
||||
var PITCH_COEFF = 180; // 归一化偏移 → 角度 的增益
|
||||
|
||||
// ---- check 阈值(角度制) ----
|
||||
@@ -170,8 +170,9 @@
|
||||
}
|
||||
if (!e) return { ok: false, hint: HINT_FRONT }; // 退化特征点(重合/脸高过小 → NaN/Infinity)
|
||||
|
||||
// 语义 yaw:>0 = 被摄者向自己的左侧转头,与预览是否镜像无关
|
||||
var sYaw = mirrored ? -e.yaw : e.yaw;
|
||||
// 语义 yaw:>0 = 被摄者向自己的左侧转头。estimate(mirrored) 内部已按镜像翻转过符号,
|
||||
// 这里直接用 e.yaw(此前再翻一次是双重镜像 bug:侧脸引导方向完全相反)
|
||||
var sYaw = e.yaw;
|
||||
var pitch = e.pitch;
|
||||
|
||||
if (target === 'front') {
|
||||
@@ -247,15 +248,15 @@ if (typeof module !== 'undefined' && typeof require !== 'undefined' && require.m
|
||||
pts[24] = { x: 230, y: 70 };
|
||||
pts[8] = { x: 200, y: 240 }; // 下巴 → 脸高 240-70=170
|
||||
pts[30] = { x: 200, y: 140 }; // 鼻尖居中
|
||||
pts[33] = { x: 200, y: 139.4 }; // (139.4-85)/170 = 0.32 = PITCH_BASELINE
|
||||
pts[33] = { x: 200, y: 161.5 }; // (161.5-85)/170 = 0.45 = PITCH_BASELINE
|
||||
return pts;
|
||||
}
|
||||
|
||||
// 把鼻点横向偏移,模拟 ~45° 侧脸(向画面右侧偏 = 被摄者向左转)
|
||||
// 把鼻点横向偏移 17.7px,模拟 ~45° 侧脸(向画面右侧偏 = 被摄者向左转;偏移量按 YAW_COEFF=3.8 数值标定)
|
||||
function makeSide() {
|
||||
var pts = makeFrontal();
|
||||
pts[30] = { x: 235, y: 140 };
|
||||
pts[33] = { x: 235, y: 139.4 };
|
||||
pts[30] = { x: 217.7, y: 140 };
|
||||
pts[33] = { x: 217.7, y: 161.5 };
|
||||
return pts;
|
||||
}
|
||||
|
||||
@@ -279,16 +280,16 @@ if (typeof module !== 'undefined' && typeof require !== 'undefined' && require.m
|
||||
ok(rc.ok === true, '侧脸(被摄者左转)target=right ok');
|
||||
ok(lc.ok === false && lc.hint === '请向右转头', '同侧脸 target=left 拒绝并引导向右');
|
||||
|
||||
// 5) mirrored=true 翻转 yaw 符号,但 check 语义不变
|
||||
// 5) mirrored=true 翻转 yaw 符号,check 语义随之翻转(同一点集在镜像预览下对应相反的物理转头)
|
||||
var sideM = HeadPose.estimate(makeSide(), true);
|
||||
ok(Math.abs(sideM.yaw + side.yaw) < 0.001, 'mirrored=true 时 yaw 符号翻转');
|
||||
ok(HeadPose.check(makeSide(), 'right', true).ok === true, 'mirrored 下 check(right) 仍 ok');
|
||||
ok(HeadPose.check(makeSide(), 'left', true).ok === false, 'mirrored 下 check(left) 仍拒绝');
|
||||
ok(HeadPose.check(makeSide(), 'right', true).ok === false, 'mirrored 语义翻转:check(right) 拒绝');
|
||||
ok(HeadPose.check(makeSide(), 'left', true).ok === true, 'mirrored 语义翻转:check(left) 通过');
|
||||
|
||||
// 6) 反方向侧脸:鼻尖偏画面左 → target 'left' 通过
|
||||
// 6) 反方向侧脸:鼻尖偏画面左 → target 'left' 通过
|
||||
var back = makeSide();
|
||||
back[30] = { x: 165, y: 140 };
|
||||
back[33] = { x: 165, y: 139.4 };
|
||||
back[30] = { x: 182.3, y: 140 };
|
||||
back[33] = { x: 182.3, y: 161.5 };
|
||||
var bc = HeadPose.check(back, 'left', false);
|
||||
ok(bc.ok === true, '反向侧脸 target=left ok');
|
||||
ok(HeadPose.check(back, 'right', false).ok === false, '反向侧脸 target=right 拒绝');
|
||||
@@ -330,15 +331,15 @@ if (typeof module !== 'undefined' && typeof require !== 'undefined' && require.m
|
||||
// 13) 正脸阈值收紧到 |yaw|≤12°(Face++ 口径 <10° + 2° 余量):yaw≈13.7°(旧阈值 15 内)应拒绝
|
||||
ok(HeadPose.config.FRONT_YAW_MAX === 12, 'config.FRONT_YAW_MAX = 12');
|
||||
var slight = makeFrontal();
|
||||
slight[30] = { x: 209, y: 140 };
|
||||
slight[33] = { x: 209, y: 139.4 };
|
||||
slight[30] = { x: 205.18, y: 140 }; // 偏移 5.18 → yaw≈13.7°(YAW_COEFF=3.8 标定)
|
||||
slight[33] = { x: 205.18, y: 161.5 };
|
||||
var se = HeadPose.estimate(slight, false);
|
||||
ok(se.yaw > 12 && se.yaw < 15, '轻微偏头 yaw∈(12,15) (got ' + se.yaw.toFixed(2) + ')');
|
||||
var sc2 = HeadPose.check(slight, 'front', false);
|
||||
ok(sc2.ok === false, 'yaw≈13.7° front 拒绝(旧阈值 15 会放行)');
|
||||
var slight2 = makeFrontal();
|
||||
slight2[30] = { x: 207, y: 140 };
|
||||
slight2[33] = { x: 207, y: 139.4 };
|
||||
slight2[30] = { x: 204.04, y: 140 }; // 偏移 4.04 → yaw≈10.7°
|
||||
slight2[33] = { x: 204.04, y: 161.5 };
|
||||
ok(HeadPose.check(slight2, 'front', false).ok === true, 'yaw≈10.7°(≤12°)front 通过');
|
||||
|
||||
if (failures > 0) {
|
||||
|
||||
Reference in New Issue
Block a user