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:
+15
-5
@@ -310,10 +310,13 @@
|
||||
/* ─── 姿态检测(face-api + HeadPose,引导式自动三连拍) ─── */
|
||||
var MODEL_URL = 'vendor/faceapi/models';
|
||||
var DETECT_INTERVAL = 150; // 检测帧间隔(ms)
|
||||
var DETECT_INPUT_SIZE = 416; // tinyFaceDetector 输入尺寸:320 下正脸置信度仅 0.67 且框偏松,416 置信度 ~0.99 框更准(CPU 端略慢,移动端 GPU 无感)
|
||||
var DETECT_SCORE = 0.3; // 置信度阈值:0.4 在噪声帧易漏检,0.3 配合距离/角度闸门足够抗误检
|
||||
var MISS_FRAMES_MAX = 3; // 连续漏检 ≥3 帧(~450ms)才报「未检测到人脸」,容忍单帧抖动
|
||||
var OK_HOLD_MS = 600; // 姿态 ok 需连续维持时长,达标才进倒计时
|
||||
var COUNTDOWN_S = 3; // 倒计时秒数
|
||||
var NO_PROGRESS_MS = 12000; // 单步超过该时长未触发倒计时→浮出手动提示
|
||||
var FACE_NEAR = 0.35, FACE_FAR = 0.8; // 人脸框高度占画面比:<35% 太远,>80% 太近
|
||||
var FACE_NEAR = 0.26, FACE_FAR = 0.58; // 人脸框高/视频高:<26% 太远,>58% 太近(以取景框≈42% 屏高、416 输入实测框 0.40–0.44 标定)
|
||||
|
||||
var modelsPromise = null, modelsOk = false, detectDisabled = false; // detectDisabled:face-api 缺失→全程手动;模型加载失败仅当次禁用,下次进拍照页重试
|
||||
var detRunning = false, detTimer = null; // 检测循环
|
||||
@@ -356,6 +359,7 @@
|
||||
// 每步进入/回退时的检测状态机重置(蒙版/提示/倒计时/无进展计时全部归位)
|
||||
function resetDetectState() {
|
||||
okSince = 0;
|
||||
missFrames = 0;
|
||||
lastHint = '';
|
||||
cancelCountdown();
|
||||
clearTimeout(nudgeTimer);
|
||||
@@ -429,7 +433,7 @@
|
||||
if (shutterLock || capturing || !camLive) return Promise.resolve(); // 抓帧/流程推进中不判姿态
|
||||
var step = camStep, seq = camSeq; // 快照:检测是异步的,帧落地时步骤/流程可能已变
|
||||
return faceapi
|
||||
.detectSingleFace(camVideo, new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.4 }))
|
||||
.detectSingleFace(camVideo, new faceapi.TinyFaceDetectorOptions({ inputSize: DETECT_INPUT_SIZE, scoreThreshold: DETECT_SCORE }))
|
||||
.withFaceLandmarks()
|
||||
.then(function (d) {
|
||||
if (step !== camStep || seq !== camSeq) return; // 跨步骤陈帧/流程已重置:丢弃
|
||||
@@ -437,10 +441,15 @@
|
||||
})
|
||||
.catch(function () { /* 单帧异常忽略,下一帧继续 */ });
|
||||
}
|
||||
var missFrames = 0; // 连续漏检帧计数:单帧抖动不报「未检测到人脸」
|
||||
function onDetect(d) {
|
||||
var hint = '', ok = false, hasFace = !!d;
|
||||
if (!hasFace) {
|
||||
var hint = '', ok = false;
|
||||
missFrames = d ? 0 : missFrames + 1;
|
||||
var hasFace = !!d, noFace = !hasFace && missFrames >= MISS_FRAMES_MAX;
|
||||
if (noFace) {
|
||||
hint = '未检测到人脸';
|
||||
} else if (!hasFace) {
|
||||
return; // 偶发漏检帧:保持上一条提示与状态,不扰动倒计时/ok 累积
|
||||
} else {
|
||||
// 提示优先级:无人脸 > 距离 > 角度
|
||||
var ratio = d.detection.box.height / (camVideo.videoHeight || 1);
|
||||
@@ -452,9 +461,10 @@
|
||||
hint = pose.hint;
|
||||
ok = pose.ok;
|
||||
}
|
||||
window.__dbgRatio = ratio; // 自动化校准/测试用(检测框高/视频高)
|
||||
}
|
||||
if (counting && !ok) cancelCountdown(); // 倒计时中任一帧不 ok→立即取消,回到对应提示
|
||||
setMask(counting || ok ? 'ok' : (hasFace ? '' : 'no-face'));
|
||||
setMask(counting || ok ? 'ok' : (noFace ? 'no-face' : ''));
|
||||
setHint(counting ? '保持不动' : hint, counting || ok);
|
||||
if (counting) return;
|
||||
if (ok) {
|
||||
|
||||
Reference in New Issue
Block a user