Files
juzi/h5/README.md
T
Ubuntu 4e3d915b7d feat: 拍摄流程对齐 Figma(正→左→右顺序/描摹轮廓蒙版/正脸只提示回正) + 报告页按 Figma 08 重排
- 拍照顺序 正脸→左脸→右脸:步骤文案/语音文件(step2-leftface/step3-rightface)/payload 归属对齐
- 蒙版轮廓由 Figma 拍照示意位图 alpha 镂空区逐像素描摹生成(0 正脸含耳/1 左脸鼻朝左/2 右脸鼻朝右),框外纯黑
- 侧脸步预览与抓帧不镜像(左右不再迷惑),HeadPose 侧脸自校准阈值 30 帧防误翻转
- 语音:「保持不动」播完再进倒计时(afterCurrent 接续,不再拦腰切断)
- 正脸步角度不合格只提示「请正对屏幕」,阈值放宽 |yaw|≤15°/|pitch|≤18°;转头提示仅侧脸步
- 抓帧改完整帧(修 Face++ INVALID_IMAGE_FACE)
- 报告页按 Figma 08:内联白底顶栏(返回+标题)、总分双独立白卡(30px 大数字+15px 单位后缀)、
  维度短竖分隔线+最低分标红、Tab 黄条、免责左对齐、护理建议去 tag 加粗+收尾句、
  产品大标题移出白卡、COREⅡ 换 Figma 导出 SVG 字标、产品图 151×174、卡圆角 10px
- 精华卡判据措辞用户化(均分档→综合均分/最差项约束→最低分维度/年龄组档→年龄段,引擎值不动)
- 修报告页滚动 P0(去 position:relative 覆写),文档同步(语音文档/README)
2026-09-28 00:57:28 +08:00

13 KiB
Raw Blame History

1'me 在线肌肤测试 H5

引导式自动三连拍(正脸/右脸/左脸),调用 Face++ 皮肤分析(专业版 skinanalyze_pro) 三视图真实检测,即时生成三维度×6分类肌肤状态分析与个性化护理建议的移动端 H5。

  • 前端:原生 HTML/CSS/JS,零依赖、零构建(face-api.js 已本地化到 vendor/,运行时零外部请求)
  • 后端:server.js(Node ≥ 18,零依赖)静态托管 + Face++ API 中转,api_secret 只存在服务端

目录结构

h5/
├── index.html          页面结构(启动 / 档案 / 拍照 / 报告 + 弹层 + 内联 SVG 取景蒙版)
├── server.js           后端:静态托管 + POST /api/skin-analyze(Face++ skinanalyze_pro 三视图中转)
├── css/style.css       全部样式(375px 基准)
├── js/app.js           全部交互(屏幕切换、肤质轮播、引导式三连拍、报告渲染)
├── js/skin-score.js    三维度×6分类评分引擎(纯逻辑 UMD,window.SkinScore,可 node 自测)
├── js/head-pose.js     头部姿态估算(纯逻辑 UMD,window.HeadPose,可 node 自测)
├── test/regression.js  真值回归(node h5/test/regression.js,对照 docs xlsx 两组三视图真值)
├── vendor/faceapi/     face-api.js 0.22.2 + 模型(本地化,见该目录 README)
├── assets/
│   ├── favicon.svg     站点图标
│   ├── img/            背景图、肤质插画、拍照底图、报告检测/产品图
│   ├── mock/           mock 报告页数据源(report-fixture.js,真实案例1数据)
│   └── svg/            品牌 Logo 与控件图标
└── preview/            开发期整页截图(仅存档,部署时可整目录剔除)

../apikey.bash          Face++ 密钥(export SKIN_API_KEY=... / SKIN_API_SECRET=...,已 .gitignore)

密钥配置

二选一:

  1. 环境变量:export SKIN_API_KEY=xxx SKIN_API_SECRET=xxx 后启动 server.js
  2. 密钥文件:项目根目录放 apikey.bash(内容为 export SKIN_API_KEY=... 等,server.js 会自动解析)

密钥文件已被 .gitignore 排除,严禁提交到版本库。

本地运行

cd h5
node server.js              # 默认 8080,可用 PORT=xxx 修改
# 打开 http://127.0.0.1:8080

调试:

  • http://127.0.0.1:8080/?mock=1 使用内置样本(SkinScore.MOCK_RESULT/MOCK_SIDE)跳过真实检测;不加载 face-api、不跑姿态检测,快门手动逐步推进(离线/自动化验证用)。
  • http://127.0.0.1:8080/?mock=1&screen=report 直渲 mock 报告页(仅调试用,供无头浏览器截图验证布局;档案缺省按 28 岁/干性肌肤);mock 数据源为 assets/mock/report-fixture.js(真实案例1三视图检测返回,含 face_maps 图层 data URL 与 marks 坐标),改该文件即可换样本。

拍照交互(引导式自动三连拍)

三步:1/3 正脸(target front)→ 2/3 左脸出镜(用户头向右转,target left,产物作 left_image)→ 3/3 右脸出镜(头向左转,target right,产物作 right_image)。

  • 步骤圆点:顶部三点,当前步高亮、完成步绿色打勾。
  • 取景蒙版:内联 SVG(#cam-mask)按步骤切换轮廓——正脸步为正脸剪影(含耳部),侧脸步为对应方向的侧脸剪影(鼻朝向与转头方向一致);轮廓路径由 Figma 拍照示意图(design/assets/images/*-拍照示意.png 的 alpha 镂空区)逐像素描摹生成,等比缩放到 375×812 viewBox;框外纯黑不透明(SVG mask 镂空,对齐 Figma:只有轮廓内显示实时画面),轮廓线 2.5px 半透明白。未检测到人脸时轮廓呼吸动画(1.8s);姿态 ok 时轮廓变绿并停止动效。
  • 正脸步提示口径:正脸步角度不合格一律只提示「请正对屏幕」(不再提示左右转头/抬头低头),阈值 |yaw|≤15°、|pitch|≤18°(2026-09-28 产品要求放宽);侧脸步仍按方向提示「请向左/右转头」。
  • 实时检测:face-api 从本地 vendor/faceapi/ 加载(tinyFaceDetector + faceLandmark68Net)。进入拍照页即开始加载模型(状态提示"检测引擎加载中…"),加载完成前快门为手动模式。检测循环 ~150ms 一帧,detectSingleFace(video, TinyFaceDetectorOptions({inputSize:416, scoreThreshold:0.3})).withFaceLandmarks() 后拿 68 特征点调 HeadPose.check(pos, target, mirrored)——mirrored 正脸步跟随镜头(前置 true/后置 false),侧脸步恒 false(预览不镜像,特征点即原始视角;个别前置帧已镜像的机型由侧脸自校准翻转 sideYawFlip,只动判定不动画面);连续漏检 ≥3 帧才报"未检测到人脸"(容忍单帧抖动);人脸框高度占比 <26% 提示"请靠近一点"、>58% 提示"请远一点"(以取景框≈42% 屏高标定)。提示一次一条,优先级:无人脸 > 距离 > 角度。
  • 倒计时自动拍:姿态 ok 连续维持 ≥600ms 后,先等「保持不动」等在播提示音自然收尾(画面保持绿框+保持不动),再进 3s 倒计时(中央大数字,轮廓保持绿色,数字与「三、二、一」语音同步起播,不再拦腰切断);倒计时中任一帧不 ok 立即取消并回到对应提示。倒计时结束进入白屏补光。
  • 镜像策略:正脸步前置镜像(自拍习惯),侧脸步预览与抓帧均不镜像(所见即摄像头原始视角,轮廓鼻朝向与转头方向一致,左右不再迷惑);后置镜头全程不镜像。
  • 白屏补光:全屏纯白覆盖层瞬间点亮,~300ms 后抓全分辨率帧(与预览同镜像策略 + 最长边 1280 JPEG),保持 ~300ms 再淡出(总 600–900ms),同步 navigator.vibrate(30)。
  • 手动兜底:快门任何时刻可拍当前步(同样走白屏补光),不依赖检测;face-api/模型加载失败 → toast"自动识别不可用,已切换手动拍摄",全程手动。系统相机降级模式(getUserMedia 不可用/被拒)维持原样:无检测、无补光。
  • 相册选图:相机页「相册」按钮可选当前步的图片(任何模式下可用),与实拍同管线(压 1280 → 推进流程)。
  • 语音播报:步骤引导/姿态提示/倒计时/拍摄完成/分析中/失败共 14 条,文案与文件名契约见 ../docs/语音播报内容设计文档_V1.0.md。WebAudio 单通道播放、同条 4s 去重;assets/voice/manifest.json 清单制,语音文件待生成——清单未提供时全部静默跳过。
  • 无进展提示:单步 12s 未触发倒计时,底部浮出"识别不到?可点下方快门手动拍摄"(不阻塞流程)。
  • 资源管理:离开拍照页(cam-back/完成)停止检测循环、清全部定时器、释放摄像头;camSeq(流程)/camSeqCam(相机)双令牌防竞态;face-api 模型只加载一次(缓存 promise,失败后下次进入重试)。

检测流程与评分渲染

  1. 三视图送检:POST /api/skin-analyze,请求 { front_image, left_image, right_image }(base64 可带 dataURL 前缀;正脸=photos[0],左=photos[2],右=photos[1])。
  2. 服务端转发 Face++ 专业版 skinanalyze_pro(三视图必须走 image_file 文件形式),请求 return_side_results=jawline_info 取左右下颌线角度,return_maps/return_marks 取叠加图与标注;响应 { ok:true, result, face_rectangle, side:{ left, right } },失败 { ok:false, code, message }(message 已是用户可读文案)。result.face_maps 为对象,含报告页所需图层的完整 data URL(red_area/brown_area/texture_enhanced_lines/texture_enhanced_pores/texture_enhanced_blackheads,由 server.js 的 REPORT_MAPS 常量控制);marks 坐标(acne/brown_spot/mole/closed_comedones/acne_pustule/acne_nodule/wrinkle_mark/melanin_mark/dark_circle_mark)随 result 原样透传,与上传图片同一像素空间。
  3. 评分由 js/skin-score.js 引擎完成,公式/锁值/话术/产品推荐以 docs/三维度评分与话术生成需求文档_V2.2.docx 为最高契约(V2.2 口径):
    • 6 分类:轮廓 = min(眼袋映射分, 下颌线角度分);皱纹(12 分区);光滑(毛孔/黑头/纹理);肤色水润(色素×水润);敏感(泛红面积取 red_spot.red_spot_area/红区分);痘痘(分级映射×丘疹 acne.count 计数扣分);分组按实际年龄;
    • 三维度木桶 min:紧塑力(轮廓+皱纹)、活力感(光滑+肤色水润)、稳定力(敏感+痘痘);
    • S = 7 + 3×P/100;档位:良好(P≥90)/改善(80–90)/预警(<80);肤龄钳制到实际年龄 ±3 并留痕;三视图版 18 条话术;
    • 精华推荐三重判据(均分档/最差项约束/年龄组档)取最强档,triggers 记录命中判据。
  4. 前端 SkinScore.buildViewModel(data.result, data.side, { realAge, skinType }) → viewModel 直渲报告页:
    • 头部:综合评分(vm.total,缺失回退三维度 S 均分×10)、肤龄 vm.skinAgeDisplay + 钳制标记 vm.ageClampNote;三维度 S(1 位小数)+ 档位配色(良好绿/改善琥珀/预警红);
    • 三个 Tab 面板各展示所辖两分类:分类名、S + 档位徽章、话术正文(vm.cats.*.text);P 值不直接展示,P=null 的分类显示"本次未检出有效数据";
    • 报告底部:降级标注(vm.notes 非空才展示,如下颌线未参与评分)、综合分析(vm.summary)、生活方式建议(vm.lifestyle)、精华推荐卡(vm.essence + 触发判据);
    • 近期优先关注 = 最差维度(vm.focus);6 分类各配检测可视化图:轮廓=正脸实拍、皱纹=正脸压暗+texture_enhanced_lines 皱纹增强线(线图层几乎全透明,直挂白底不可读,故与压暗沉脸合成)、光滑=正脸+色斑/痣/痤疮标记叠加、肤色=brown_area 色沉图、敏感=red_area 红区图、痘痘=正脸+闭口粉刺/痤疮标记叠加(marks 按原像素坐标叠加到正脸实拍)。
  5. 真值回归:node h5/test/regression.js 以 docs/皮肤检测测试结果_三视图_20260926.xlsx(sheet「三视图检测汇总」,数据第 3/4 行=案例1/案例2)两组三视图真值对照评分引擎输出。
  6. 报告历史:检测成功后按「检测」为单位存入 localStorage(juzi.reports.v1,上限 5 条,超配额逐条淘汰)——含三张照片、档案、全部评分数据与 marks 坐标,不含 face_maps 整幅图层(体积原因;回看时皱纹/肤色/敏感图降级为占位,光滑/痘痘标记叠加仍可用)。当前无 UI 入口(第9轮移除),数据仅作本地追溯。
  7. 原始返回落盘:服务端每次检测成功后把 Face++ 完整原始返回写入 h5/data/results/<时间戳>-<request_id>.json(复核/真值用,已 .gitignore;写盘失败不影响响应)。
  8. 检测失败按服务端 message 提示并退回档案页。

部署

需要 Node ≥ 18 运行 server.js(负责静态与 API 中转),不能用纯静态托管(密钥不能进前端)。

PORT=8080 node server.js    # 建议 pm2 / systemd 常驻

Windows 本机部署(提测用)

h5\deploy-local.bat      :: 默认 PORT=8088,日志写 h5\server.log

登记开机自启(用户级计划任务,需系统允许;若提示"拒绝访问",直接双击 deploy-local.bat 或手动运行即可):

schtasks /create /tn "JuziSkinH5" /tr "\"C:\Windows\System32\cmd.exe\" /c \"D:\juzi\h5\deploy-local.bat\"" /sc onlogon /f
:: 移除: schtasks /delete /tn "JuziSkinH5" /f

局域网提测访问 http://<本机局域网IP>:8088/。注意:① 首次监听若弹 Windows 防火墙提示需允许 node.exe;② 手机走局域网 HTTP 时 getUserMedia 不可用,会自动降级为系统相机手动三连拍(分析链路完整),要测引导式自动拍照需在本机浏览器访问 http://127.0.0.1:8088,或上 HTTPS。

nginx 反代示例(静态与 API 都交给 server.js,nginx 只做 HTTPS 终结):

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        client_max_body_size 20m;   # 与 server.js 请求上限一致
    }
}

HTTPS 必需:getUserMedia 相机权限仅在安全上下文可用(localhost 除外)。正式域名必须上 HTTPS,否则前端自动降级为系统相机拍照。

已知占位清单

  • 产品图(prod-1.jpg)为设计稿素材;检测图已替换为用户实拍。
  • 语音文件待生成:播报文案/文件名/格式已定(../docs/语音播报内容设计文档_V1.0.md),生成后放入 assets/voice/ 并更新 manifest.json 即生效。
  • 切换镜头、「了解更多」为开发中占位(Toast 提示)。
  • 姿态与距离参数已用模拟摄像头校准(2026-09-27):以 C3 三视图(+45° 侧脸)生成 Y4M 模拟摄像头视频,闭环修正了 yaw 增益(2.2→3.8)、pitch 基线(0.32→0.45)、check() 双重镜像符号 bug、距离阈值(0.35/0.8→0.26/0.58)、检测参数(320/0.4→416/0.3)与漏检 3 帧容忍;自动三连拍在无头浏览器全程通过(e2e E7)。真机仍建议复核一次:不同机型前置镜像策略/取景比例可能微调体验(校准接口:调用方 mirrored 标志与 HeadPose.config)。

技术说明

  • 尺寸换算:设计稿 1080px 基准 ÷ 2.88 = 375px 落地基准。
  • 图片性能:首屏背景 preload;首屏外 loading="lazy"。
  • 快门防连点/双流程令牌(camSeq/camSeqCam)防竞态;拍照页取景框外纯黑(对齐 Figma),白字直接落在黑色上。
  • face-api.js 0.22.2 与模型全部本地化(vendor/faceapi/,约 1.4MB),运行时零外部请求,微信 webview / iOS Safari 弱网与内网部署可用;许可证(MIT / Apache-2.0)见 vendor/faceapi/README.md。