fix(camera): relax initial frontal face recognition

This commit is contained in:
xsl
2026-10-16 15:27:36 +08:00
parent 8365ef459f
commit 30c90f8df5
5 changed files with 35 additions and 12 deletions
+9 -4
View File
@@ -607,7 +607,7 @@
var img = new Image();
img.onload = function () {
faceapi
.detectAllFaces(img, new faceapi.TinyFaceDetectorOptions({ inputSize: DETECT_INPUT_SIZE, scoreThreshold: DETECT_SCORE }))
.detectAllFaces(img, new faceapi.TinyFaceDetectorOptions({ inputSize: DETECT_INPUT_SIZE, scoreThreshold: CaptureQuality.scoreThreshold(target) }))
.withFaceLandmarks()
.then(function (faces) {
var d = faces[0], box = d && d.detection.box;
@@ -768,8 +768,8 @@
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 = CaptureQuality.config.score;
var MISS_FRAMES_MAX = 3; // 连续漏检 ≥3 帧(~450ms)才报「未检测到人脸」,容忍单帧抖动
var FRONT_MISS_FRAMES_MAX = 6; // 正面首次发现人脸前,多给相机对焦/曝光几次检测机会。
var NO_PROGRESS_MS = 12000; // 单步超过该时长仍未抓拍→浮出调整提示
var PREVIEW_SCALE = 1; // 与 CSS #cam-video 铺满预览一致,镜像只改变方向
@@ -860,6 +860,7 @@
holdState = null;
resetAngleHaptic();
missFrames = 0;
frontFaceSeen = false;
oppFrames = 0;
lastHint = '';
clearTimeout(nudgeTimer);
@@ -913,7 +914,7 @@
if (shutterLock || capturing || !camLive) return Promise.resolve(); // 抓帧/流程推进中不判姿态
var step = camStep, seq = camSeq, generation = detGeneration; // 旧镜头推理结果不能污染新镜头
return faceapi
.detectAllFaces(camVideo, new faceapi.TinyFaceDetectorOptions({ inputSize: DETECT_INPUT_SIZE, scoreThreshold: DETECT_SCORE }))
.detectAllFaces(camVideo, new faceapi.TinyFaceDetectorOptions({ inputSize: DETECT_INPUT_SIZE, scoreThreshold: CaptureQuality.scoreThreshold(CAM_STEPS[step].target) }))
.withFaceLandmarks()
.then(function (d) {
if (step !== camStep || seq !== camSeq || generation !== detGeneration) return;
@@ -941,6 +942,7 @@
} catch (e) { return null; }
}
var missFrames = 0; // 连续漏检帧计数:单帧抖动不报「未检测到人脸」
var frontFaceSeen = false;
/* camera-haptic-pure-begin */
function captureHaptic(nav) {
// Unsupported/blocked vibration must never interrupt automatic capture.
@@ -976,7 +978,10 @@
if (Array.isArray(d)) d = d[0];
var hint = '', ok = false;
missFrames = d ? 0 : missFrames + 1;
var hasFace = !!d, noFace = !hasFace && missFrames >= MISS_FRAMES_MAX;
var hasFace = !!d;
if (hasFace && CAM_STEPS[camStep].target === 'front') frontFaceSeen = true;
var missLimit = CAM_STEPS[camStep].target === 'front' && !frontFaceSeen ? FRONT_MISS_FRAMES_MAX : MISS_FRAMES_MAX;
var noFace = !hasFace && missFrames >= missLimit;
if (noFace) {
hint = '未检测到人脸';
} else if (!hasFace) {
+4 -3
View File
@@ -6,11 +6,12 @@
'use strict';
// Real-phone feedback: ease alignment and normal hand/head movement while
// retaining two checked frames, one face, framing and final JPEG validation.
var config = Object.freeze({ version: '2026-10-15-pitch-roll-relaxed-v7', score: 0.4,
var config = Object.freeze({ version: '2026-10-16-front-detection-v8', score: 0.4, frontScore: 0.3,
near: 0.30, far: 0.68, center: 0.16, edge: 0.01,
dark: 110, bright: 220, holdMs: 450, minFrames: 2, maxGapMs: 2000,
yawDrift: 8, pitchDrift: 8, centerDrift: 0.055, sizeDrift: 0.065 });
function finite(v) { return typeof v === 'number' && isFinite(v); }
function scoreThreshold(target) { return target === 'front' ? config.frontScore : config.score; }
function evaluate(d, options) {
var failed = function(hint, reason) { return { ok: false, hint: hint, reason: reason || 'quality' }; };
if (!d) return failed('未检测到人脸');
@@ -18,7 +19,7 @@
var box = d.detection.box, w = options.width, h = options.height;
if (!box || ![box.x, box.y, box.width, box.height, w, h].every(finite) || w <= 0 || h <= 0 || box.width <= 0 || box.height <= 0) return failed('请将脸部完整移入取景框');
var ratio = box.height / h;
if (!finite(d.detection.score) || d.detection.score < config.score) return failed('请保持面部清晰');
if (!finite(d.detection.score) || d.detection.score < scoreThreshold(options.target)) return failed('请保持面部清晰');
if (ratio < config.near) return failed('请靠近一点');
if (ratio > config.far) return failed('请远一点');
if (box.x < w * config.edge || box.y < h * config.edge || box.x + box.width > w * (1 - config.edge) || box.y + box.height > h * (1 - config.edge)) return failed('请将脸部完整移入取景框');
@@ -51,5 +52,5 @@
{ since: state.since, lastAt: now, frames: state.frames + 1, anchor: state.anchor };
return { state: next, ready: now - next.since >= config.holdMs && next.frames >= config.minFrames };
}
return { config: config, evaluate: evaluate, hold: hold };
return { config: config, scoreThreshold: scoreThreshold, evaluate: evaluate, hold: hold };
});