fix(h5): ease camera capture requirements and rename page title

This commit is contained in:
xsl
2026-10-12 20:47:58 +08:00
parent 3f165bf9e5
commit c9352fe4ca
15 changed files with 960 additions and 804 deletions
@@ -0,0 +1,41 @@
# 拍摄约束放宽与标题更新
依据用户真实手机反馈:上一轮拍摄过于费劲,适度放宽前端门限,页面名称改为 `1'me肌肤状态检测`。此次不修改设计素材、评分、肌龄、推荐规则、微信登录流程或历史报告。
## 参数对照
参数版本由 `2026-10-12-fourth-v1` 升为 `2026-10-12-fourth-v2`;新报告后台保存新版本与配置快照,已有记录不回写。
| 项目 | 上一轮 v1 | 本次 v2 |
| --- | --- | --- |
| 人脸置信度 | ≥0.5 | ≥0.4 |
| 人脸框高占原帧 | 34%~64% | 30%~68% |
| 水平偏离屏幕中心 | ≤12% 屏宽 | ≤16% 屏宽 |
| 检测框边缘余量 | ≥2% 原帧 | ≥1% 原帧 |
| 正脸 yaw/pitch | ≤10°/12° | ≤15°/18° |
| 侧脸幅度/pitch | 35°~65°/≤15° | 30°~70°/≤20° |
| 歪头 roll | ≤10° | ≤15° |
| 脸部平均亮度 | 75~225 | 65~235 |
| 稳定时长/检测帧数 | ≥650ms/≥3 帧 | ≥450ms/≥2 帧 |
| yaw/pitch 漂移 | ≤5° | ≤8° |
| 中心 x/y 漂移 | ≤原帧 3.5% | ≤原帧 5.5% |
| 框高占比变化 | ≤4.5 个百分点 | ≤6.5 个百分点 |
仍要求单人出镜、完整入镜、正确视角、基本距离/光线;最终 JPEG 必须通过同一套本地复验。没有删除质量检查、绕过检测、增加服务端质量拦截或网络往返。漏检/识别异常及超过 2 秒的检测间隔仍打断累计。已有模型不可用降级路径保持不变,后台明确标记未校验。
最低稳定等待减少 200ms;慢设备从至少三次合格观察减为两次,减少持续保持姿势的难度。实际速度仍取决于手机模型推理耗时,不能保证所有机型和环境的检测合格率。
## 标题
`h5/index.html` 的 HTML title、Open Graph 分享标题及页面描述统一改为 `1'me肌肤状态检测`。首页 PSD 原组合图已经是「肌肤状态检测」,无需改图。
## 验证与发布
本地已通过:
- 参数边界:上一版偏头/俯仰/偏位/亮度/轻微漂移等拒绝样例现在通过,多人/错误视角/裁切/大幅移动仍拒绝;450ms 两帧准确触发,短于 450ms 不触发,漏检/超时间隔重置。
- 真实模型自动三连拍及最终 JPEG 本地复验,正脸不能冒充侧脸、错误方向不能推进,诊断照片归属正确,成功后释放摄像头。
- 隔离服务器后台诊断保存 v2 配置快照;页面与分享标题正确;quality=3 不阻断报告,游客状态、历史快照、五种屏宽及实际一轮扫描动画通过。
- 报告持久化、后台/H5/CSV 一致、账号隔离、皱纹原文;账号/OAuth 单测;原算法 86 条及推荐规则 126 用例;请求超时与震动安全回归。
部署目标:`ubuntu@1me.xajuzi.com:/home/ubuntu/juzi`,部署基线 `3f165bf`,通过 Git bundle 快进同步、不代用户 push。重启 `juzi-h5.service` 以保存新版后端参数快照。生产验证仅 GET 请求及浏览器内存拦截,不创建线上账号/报告,不调用收费 Face++。
+22 -22
View File
@@ -4,20 +4,20 @@
"2": "app.js",
"3": "skin-score.js",
"4": "customer-feedback-second.js",
"5": "r",
"6": "extractFaces",
"7": "customer-feedback-third.mjs",
"5": "n",
"6": "extractFaceTensors",
"7": ".forwardInput",
"8": "admin.js",
"9": "__awaiter",
"10": "FaceLandmarks68",
"10": "wechat-browser.mjs",
"11": "lib/accounts.js",
"12": "server.js",
"13": "files",
"14": ".draw",
"15": "getEnv",
"15": "extractFaces",
"16": "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范",
"17": "k",
"18": "1'me 在线肌肤测试 H5",
"17": "report-persistence.mjs",
"18": "1'me肌肤状态检测 H5",
"19": "customer-feedback-fourth.js",
"20": "build_fixtures.py",
"21": "disposeUnusedWeightTensors",
@@ -42,7 +42,7 @@
"40": "第四轮反馈修改与验收",
"41": "NeuralNetwork",
"42": "layer_at",
"43": "convLayer",
"43": "normalize",
"44": "start-local.sh",
"45": "PredictAllAgeAndGenderWithFaceAlignmentTask",
"46": "PredictAllFaceExpressionsWithFaceAlignmentTask",
@@ -54,7 +54,7 @@
"52": "region-data.js",
"53": "护理建议(全量文本,按需求摘抄)",
"54": "UI 对齐 10.09 设计稿:调研与实施计划",
"55": "i",
"55": "pn",
"56": "report-ui.js",
"57": "regression.js",
"58": ".dispose",
@@ -65,46 +65,46 @@
"63": "e2e-browser.mjs",
"64": "account-client.js",
"65": "微信登录接入与公网部署说明",
"66": ".div",
"66": "FaceLandmarks68",
"67": "ui-20261009.mjs",
"68": "analyze-timeout.js",
"69": "misans/README.md",
"70": "extractConvParamsFactory",
"71": ".predictAgeAndGender",
"71": "extractAllFacesAndComputeResults",
"72": "build_report_copy.py",
"73": "camera-geometry.js",
"74": "report-copy.js",
"75": "prepare_h5_assets.py",
"76": "TinyYolov2Base",
"77": "drawFaceExpressions",
"78": "n",
"76": ".toString",
"77": ".runAndExtendWithFaceDetections",
"78": "i",
"79": "登录反馈与加载优化(2026-10-12)",
"80": "线上微信登录部署记录",
"81": "wechat-production.mjs",
"82": ".forwardInput",
"82": "拍摄约束放宽与标题更新",
"83": "10.09 新版 H5 实施与验收",
"84": "微信登录联调:2026-10-12",
"85": "test/accounts.js",
"86": "normalize",
"86": "FaceExpressionNet",
"87": "产品推荐规则:肤龄基准与边界调整",
"88": "ui-20261012/manifest.json",
"89": "傅艳NEW与项思炼的会话 2026年10月12日.md",
"90": "customer-feedback.mjs",
"91": "wi",
"92": "客户反馈修改(2026-10-12)",
"93": ".runAndExtendWithFaceDetections",
"93": ".runAndExtendWithFaceDetection",
"94": "第三轮反馈:PSD 对齐、验收及上线记录",
"95": ".forwardInput",
"96": ".run",
"95": "login-performance.mjs",
"96": "production-loop.mjs",
"97": "PredictSingleAgeAndGenderWithFaceAlignmentTask",
"98": "PredictSingleFaceExpressionsWithFaceAlignmentTask",
"99": "DetectSingleFaceLandmarksTask",
"100": "extractAllFacesAndComputeResults",
"101": ".toBatchTensor",
"100": "ComputeAllFaceDescriptorsTask",
"101": "drawFaceExpressions",
"102": "TinyYolov2",
"103": "第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md",
"104": "export_fourth_feedback_assets.py",
"105": "capture-quality-browser.mjs",
"106": "第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md",
"107": "rescaleAndNormalize"
"107": "ComputeSingleFaceDescriptorTask"
}
+1 -1
View File
@@ -1 +1 @@
{"0": "cd6fa4c897b52500", "1": "cb6ea50bd4e4a5ca", "2": "b159712c832c558e", "3": "1f737ca8edb978f2", "4": "56bf12952b0de42b", "5": "d50faa21d10efe9c", "6": "7162685b110caea8", "7": "325690c89d34f602", "8": "a689fcb62e7754af", "9": "1215838433447012", "10": "68c52c80f38f8b2c", "11": "7c739fd002680640", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "bf75bfc99d4f9e34", "15": "bbd2902bccbcfc9c", "16": "95add6a975c0cb72", "17": "68c8c4a53446cb29", "18": "38bb0b185ceed116", "19": "39abb1eef19df707", "20": "aecf2fce44ba49c5", "21": "d4699b3ebc30431b", "22": "bb2dc34c7d87256a", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "6039f82328197b7e", "27": "eab427ce9a5219e9", "28": "0018a4ff1d5fe668", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "0a737a97b7cae9fc", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "d96493a5f110bd4c", "42": "916170b9f5d7346b", "43": "4eaa3e8b9eed3bf1", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "e594ecaa8e7d1538", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "a4d51d71a07be14b", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "55bfa752b54c63f4", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "57344ed1fd5c260f", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "ce0e68dab611f022", "65": "74ae017de0ffd36b", "66": "09b9b4b462ad944a", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "af5864c4ac65160d", "71": "a2b13160223b5a98", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "4880b6c8e510f062", "77": "d5ce16a18b491627", "78": "390555e7d7f0d203", "79": "c6cfdfe181232ecc", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "315b401775d71d8e", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "4bc370be8afd7db1", "86": "8824c7a0092ee36e", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "84a88fb74d1ce216", "94": "713d04996c4627df", "95": "5fec2bbca3634aec", "96": "1511ecae12413095", "97": "01f68dfc34a29d32", "98": "8c471c7baeaaca54", "99": "23f1d406f6c779ae", "100": "b8eb9c4c56ab3204", "101": "0c404de2055f9462", "102": "c96276581a8c98d4", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "0567be4dc8048b9c", "106": "8bc72c179793e1c2", "107": "8d5f4e992bac9095"}
{"0": "65cda48e6d2f67a3", "1": "cb6ea50bd4e4a5ca", "2": "b159712c832c558e", "3": "bcbfbc0b66f44357", "4": "56bf12952b0de42b", "5": "8e50b0e3c19c0a11", "6": "e154e5d8b03e5206", "7": "880a74c6d255e5cc", "8": "a689fcb62e7754af", "9": "63bb420d48d7a255", "10": "2b037735e1f5b0f6", "11": "4a490abd5e503318", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "bf75bfc99d4f9e34", "15": "b1a71a621511068d", "16": "95add6a975c0cb72", "17": "e042d7fe7351bcb9", "18": "f93d5d85e671fcee", "19": "39abb1eef19df707", "20": "aecf2fce44ba49c5", "21": "59485c317afc6c2b", "22": "512a2535c88c4284", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "041694c55d6f8a30", "27": "eab427ce9a5219e9", "28": "d3fab0fa540bd925", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "d96493a5f110bd4c", "42": "916170b9f5d7346b", "43": "7986b9d5a423a3fc", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "e594ecaa8e7d1538", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "82703230bd5ed1e1", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "803dac08b323abc2", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "0890ad2144e37304", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "ce0e68dab611f022", "65": "74ae017de0ffd36b", "66": "b6d6154e2dd14637", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "af5864c4ac65160d", "71": "06e9a65e05e71d35", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "74310612e8e059f4", "77": "2ee521497bb8506a", "78": "5cb44ae4ec05213b", "79": "c6cfdfe181232ecc", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "4bc370be8afd7db1", "86": "e858d75e995c92e5", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "8bd61518dd267641", "94": "713d04996c4627df", "95": "59c31f858c7b6970", "96": "4195478d5c528371", "97": "01f68dfc34a29d32", "98": "8c471c7baeaaca54", "99": "23f1d406f6c779ae", "100": "3dd68cce72af5314", "101": "a0bc88eb14b70170", "102": "29790cf710874dd0", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "0567be4dc8048b9c", "106": "8bc72c179793e1c2", "107": "7ac475ea9bc280f8"}
+103 -111
View File
@@ -1,16 +1,16 @@
# Graph Report - juzi (2026-10-12)
## Corpus Check
- 92 files · ~4,888,834 words
- 93 files · ~4,889,148 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1382 nodes · 2810 edges · 108 communities (84 shown, 24 thin omitted)
- 1387 nodes · 2814 edges · 108 communities (82 shown, 26 thin omitted)
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 189 edges (avg confidence: 0.54)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `92a3965b`
- Built from commit: `3f165bf9`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -20,20 +20,20 @@
- app.js
- skin-score.js
- customer-feedback-second.js
- r
- extractFaces
- customer-feedback-third.mjs
- n
- extractFaceTensors
- .forwardInput
- admin.js
- __awaiter
- FaceLandmarks68
- wechat-browser.mjs
- lib/accounts.js
- server.js
- files
- .draw
- getEnv
- extractFaces
- 巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范
- k
- 1'me 在线肌肤测试 H5
- report-persistence.mjs
- 1'me肌肤状态检测 H5
- customer-feedback-fourth.js
- build_fixtures.py
- disposeUnusedWeightTensors
@@ -56,7 +56,7 @@
- 第四轮反馈修改与验收
- NeuralNetwork
- layer_at
- convLayer
- normalize
- start-local.sh
- PredictAllAgeAndGenderWithFaceAlignmentTask
- PredictAllFaceExpressionsWithFaceAlignmentTask
@@ -66,7 +66,7 @@
- call_skinanalyze_3view.sh
- 护理建议(全量文本,按需求摘抄)
- UI 对齐 10.09 设计稿:调研与实施计划
- i
- pn
- report-ui.js
- regression.js
- .dispose
@@ -76,42 +76,42 @@
- e2e-browser.mjs
- account-client.js
- 微信登录接入与公网部署说明
- .div
- FaceLandmarks68
- ui-20261009.mjs
- analyze-timeout.js
- misans/README.md
- extractConvParamsFactory
- .predictAgeAndGender
- extractAllFacesAndComputeResults
- build_report_copy.py
- TinyYolov2Base
- drawFaceExpressions
- n
- .toString
- .runAndExtendWithFaceDetections
- i
- 登录反馈与加载优化(2026-10-12)
- 线上微信登录部署记录
- wechat-production.mjs
- .forwardInput
- 拍摄约束放宽与标题更新
- 10.09 新版 H5 实施与验收
- 微信登录联调:2026-10-12
- test/accounts.js
- normalize
- FaceExpressionNet
- 产品推荐规则:肤龄基准与边界调整
- ui-20261012/manifest.json
- 傅艳NEW与项思炼的会话 2026年10月12日.md
- wi
- 客户反馈修改(2026-10-12)
- .runAndExtendWithFaceDetections
- .runAndExtendWithFaceDetection
- 第三轮反馈:PSD 对齐、验收及上线记录
- .forwardInput
- .run
- login-performance.mjs
- production-loop.mjs
- PredictSingleAgeAndGenderWithFaceAlignmentTask
- PredictSingleFaceExpressionsWithFaceAlignmentTask
- DetectSingleFaceLandmarksTask
- extractAllFacesAndComputeResults
- .toBatchTensor
- ComputeAllFaceDescriptorsTask
- drawFaceExpressions
- TinyYolov2
- 第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md
- 第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md
- rescaleAndNormalize
- ComputeSingleFaceDescriptorTask
## God Nodes (most connected - your core abstractions)
1. `__awaiter()` - 59 edges
@@ -140,11 +140,11 @@
## Import Cycles
- None detected.
## Communities (108 total, 24 thin omitted)
## Communities (108 total, 26 thin omitted)
### Community 0 - "face-api.js"
Cohesion: 0.02
Nodes (34): boxPredictionLayer(), cn(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), decodeBoxesLayer(), depthwiseConvLayer(), dr() (+26 more)
Nodes (51): bgrToRgbTensor(), cn(), computeReshapedDimensions(), createBrowserEnv(), createFaceDetectionNet(), createSsdMobilenetv1(), Dimensions(), dr() (+43 more)
### Community 1 - "线上闭环测试(不含真实微信授权)"
Cohesion: 0.25
@@ -155,36 +155,40 @@ Cohesion: 0.05
Nodes (85): applyMirror(), armNudge(), beginDetect(), bindBackdropClose(), busyWithFlow(), captureFromVideo(), captureHaptic(), checkStill() (+77 more)
### Community 3 - "skin-score.js"
Cohesion: 0.12
Cohesion: 0.11
Nodes (45): acneGrade(), buildViewModel(), calcAcne(), calcContour(), calcSens(), calcSmooth(), calcTone(), calcWrinkles() (+37 more)
### Community 4 - "customer-feedback-second.js"
Cohesion: 0.17
Nodes (10): assert, context, face, fs, geometry, original, path, source (+2 more)
### Community 5 - "r"
Cohesion: 0.14
Nodes (34): a(), ae(), bf(), cf(), d(), drawContour(), dt(), ea() (+26 more)
### Community 5 - "n"
Cohesion: 0.12
Nodes (43): a(), ae(), ba(), bf(), cf(), d(), da(), drawContour() (+35 more)
### Community 6 - "extractFaces"
Cohesion: 0.10
Nodes (15): bgrToRgbTensor(), Box(), DetectAllFaceLandmarksTask(), Dimensions(), extendWithFaceLandmarks(), extractFaces(), extractFaceTensors(), imageTensorToCanvas() (+7 more)
### Community 6 - "extractFaceTensors"
Cohesion: 0.16
Nodes (5): Box(), extendWithFaceLandmarks(), extractFaceTensors(), FaceLandmarks(), minBbox()
### Community 7 - "customer-feedback-third.mjs"
Cohesion: 0.14
Nodes (9): actualLines(), errors, guideLines, manifest, rect(), requests, root, widths (+1 more)
### Community 7 - ".forwardInput"
Cohesion: 0.22
Nodes (9): boxPredictionLayer(), decodeBoxesLayer(), depthwiseConvLayer(), getCenterCoordinatesAndSizesLayer(), getStridesForLayerIdx(), mobileNetV1(), outputLayer(), pointwiseConvLayer() (+1 more)
### Community 8 - "admin.js"
Cohesion: 0.20
Nodes (29): ageText(), api(), bind(), captureQualityHtml(), closeDetail(), collectFilters(), dimsHtml(), doRetry() (+21 more)
### Community 9 - "__awaiter"
Cohesion: 0.14
Nodes (13): __awaiter(), ComposableTask(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow(), __generator(), getModelUris() (+5 more)
Cohesion: 0.12
Nodes (14): __awaiter(), bufferToImage(), ComposableTask(), extendWithFaceExpressions(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow() (+6 more)
### Community 10 - "wechat-browser.mjs"
Cohesion: 0.22
Nodes (6): createAccounts, h5, ReportUI, require, root, SkinScore
### Community 11 - "lib/accounts.js"
Cohesion: 0.06
Nodes (37): atomic(), body(), cookie(), cookies(), crypto, fs, json(), login() (+29 more)
Cohesion: 0.17
Nodes (20): atomic(), body(), cookie(), cookies(), crypto, fs, json(), login() (+12 more)
### Community 12 - "server.js"
Cohesion: 0.08
@@ -194,21 +198,21 @@ Nodes (52): accounts, appendRecord(), callFacepp(), CaptureDiagnostics, CRED, cr
Cohesion: 0.12
Nodes (15): files, analyze-fail, analyzing, countdown-321, hint-far, hint-front, hint-hold, hint-near (+7 more)
### Community 15 - "getEnv"
Cohesion: 0.27
Nodes (14): awaitMediaLoaded(), bufferToImage(), createCanvas(), createCanvasFromMedia(), extractImagePatches(), getContext2dOrThrow(), getEnv(), getMediaDimensions() (+6 more)
### Community 15 - "extractFaces"
Cohesion: 0.24
Nodes (15): awaitMediaLoaded(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractImagePatches(), getContext2dOrThrow(), getEnv(), getMediaDimensions() (+7 more)
### Community 16 - "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范"
Cohesion: 0.15
Nodes (12): 1. 基准与尺寸换算, 2. 色彩规范, 3. 字体规范, 4. 组件切图(assets/svg/), 5. 图片素材(assets/images/,共 15 张), 6. 页面清单(design/ 下有整屏 PNG 预览), 7. 目录结构, 8. 还原建议 (+4 more)
### Community 17 - "k"
Cohesion: 0.15
Nodes (15): b(), ba(), ca(), da(), fa(), k(), ne(), pa() (+7 more)
### Community 17 - "report-persistence.mjs"
Cohesion: 0.29
Nodes (6): child, dataRoot, fx, h5, ReportUI, require
### Community 18 - "1'me 在线肌肤测试 H5"
### Community 18 - "1'me肌肤状态检测 H5"
Cohesion: 0.13
Nodes (15): 10.09 更新, 1'me 在线肌肤测试 H5, Admin API(统一返回 `{ok:false,message}` 错误), Windows 本机部署(提测用), 后台查看(admin.html), 密钥配置, 已知占位清单, 技术说明 (+7 more)
Nodes (15): 10.09 更新, 1'me肌肤状态检测 H5, Admin API(统一返回 `{ok:false,message}` 错误), Windows 本机部署(提测用), 后台查看(admin.html), 密钥配置, 已知占位清单, 技术说明 (+7 more)
### Community 19 - "customer-feedback-fourth.js"
Cohesion: 0.06
@@ -219,7 +223,7 @@ Cohesion: 0.40
Nodes (9): build_case1(), build_case2(), build_truth(), col(), dump(), main(), parse_value(), set_path() (+1 more)
### Community 21 - "disposeUnusedWeightTensors"
Cohesion: 0.09
Cohesion: 0.10
Nodes (24): disposeUnusedWeightTensors(), extractorsFactory$3(), extractorsFactory$5(), extractorsFactory$7(), extractorsFactory$9(), extractParamsFromWeigthMap(), extractParamsFromWeigthMap$1(), extractParamsFromWeigthMap$2() (+16 more)
### Community 23 - "facepp-skinanalyze(旷视皮肤分析专业版调用技能)"
@@ -235,8 +239,8 @@ Cohesion: 0.20
Nodes (16): _arrayLikeToArray(), _arrayWithHoles(), _arrayWithoutHoles(), _classCallCheck(), _createClass(), _defineProperties(), h(), _iterableToArray() (+8 more)
### Community 26 - ".extractWeights"
Cohesion: 0.12
Nodes (16): createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractFCParamsFactory(), extractorsFactory$2() (+8 more)
Cohesion: 0.14
Nodes (14): createFaceRecognitionNet(), createMtcnn(), createTinyFaceDetector(), createTinyYolov2(), extractFCParamsFactory(), extractorsFactory$2(), extractorsFactory$4(), extractorsFactory$8() (+6 more)
### Community 27 - "gen_voice.py"
Cohesion: 0.30
@@ -244,7 +248,7 @@ Nodes (11): ndarray, Path, gen_countdown(), main(), normalize_clip(), 倒计时
### Community 28 - "C"
Cohesion: 0.09
Nodes (34): ao(), Bo(), C(), cd(), cl(), co(), Do(), eo() (+26 more)
Nodes (36): ao(), Bo(), C(), cd(), cl(), co(), Do(), e() (+28 more)
### Community 29 - "face-api.js 本地化依赖(h5/vendor/faceapi/)"
Cohesion: 0.29
@@ -259,8 +263,8 @@ Cohesion: 0.60
Nodes (4): main(), 递归缩放 result 内全部坐标:{left,top,width,height} 矩形与 {x,y} 点,与原数据同为整数。, scale_coords(), to_data_url()
### Community 35 - "customer-feedback.js"
Cohesion: 0.11
Nodes (18): alternate, approved, assert, crop, details, excellent, fs, model (+10 more)
Cohesion: 0.06
Nodes (27): alternate, approved, assert, crop, details, excellent, fs, model (+19 more)
### Community 37 - "UI 外发版 10.09 图片资源"
Cohesion: 0.40
@@ -278,9 +282,9 @@ Nodes (8): PSD 来源, 前端参数及限制, 后台校准留痕, 已确认并
Cohesion: 0.18
Nodes (11): list, bounds(), layer_at(), main(), save_image(), composite(), main(), sha256() (+3 more)
### Community 43 - "convLayer"
Cohesion: 0.23
Nodes (11): convLayer(), convWithBatchNorm(), depthwiseSeparableConv$1(), fullyConnectedLayer(), leaky(), ONet(), PNet(), prelu() (+3 more)
### Community 43 - "normalize"
Cohesion: 0.07
Nodes (28): conv(), conv$1(), convDown(), convLayer(), convLayer$1(), convNoRelu(), convWithBatchNorm(), denseBlock3() (+20 more)
### Community 44 - "start-local.sh"
Cohesion: 0.50
@@ -302,9 +306,9 @@ Nodes (19): 5\.1 轮廓(紧塑力), 5\.2 皱纹(紧塑力), 5\.3 肤色
Cohesion: 0.11
Nodes (17): 1. 结论和依据, 2. 统一比较口径, 3. 逐页差异和调整内容, 4. 报告数据如何接到新 UI, 5. 登录与“我的报告”的真实能力缺口, 6. 原稿中需要整理的内容, 7. 实施顺序与交付物, 8. 验收定义 (+9 more)
### Community 55 - "i"
Cohesion: 0.09
Nodes (22): bn(), dn(), ee(), Ef(), et(), fn(), i(), je() (+14 more)
### Community 55 - "pn"
Cohesion: 0.25
Nodes (8): bn(), fn(), ln(), pd(), pn(), un(), wn(), xn()
### Community 56 - "report-ui.js"
Cohesion: 0.50
@@ -315,8 +319,8 @@ Cohesion: 0.60
Nodes (5): diffCtx(), loadJSON(), main(), okNum(), okStr()
### Community 58 - ".dispose"
Cohesion: 0.17
Nodes (12): computeReshapedDimensions(), extractBoundingBoxes(), iou(), isEven(), nonMaxSuppression(), nonMaxSuppression$1(), pyramidDown(), scale() (+4 more)
Cohesion: 0.15
Nodes (9): FaceRecognitionNet(), NetInput(), nonMaxSuppression(), scale(), SsdMobilenetv1(), stage1(), stage2(), stage3() (+1 more)
### Community 62 - "提交前 Review(2026-10-11)"
Cohesion: 0.40
@@ -334,9 +338,9 @@ Nodes (5): browser(), init(), read(), rememberKind(), request()
Cohesion: 0.22
Nodes (7): 公网服务器配置, 公网真机验收清单, 反向代理与后台保护, 已完成的链路, 微信登录接入与公网部署说明, 数据、迁移与备份, 本机开发环境限制(不代表线上状态)
### Community 66 - ".div"
Cohesion: 0.21
Nodes (4): FaceLandmark68NetBase(), FaceLandmarks5(), getCenterPoint(), Point()
### Community 66 - "FaceLandmarks68"
Cohesion: 0.10
Nodes (8): FaceLandmark68NetBase(), FaceLandmarks5(), FaceLandmarks68(), getCenterPoint(), Point(), pt(), vt(), wl()
### Community 68 - "analyze-timeout.js"
Cohesion: 0.20
@@ -346,21 +350,21 @@ Nodes (7): assert, end, fs, path, source, start, vm
Cohesion: 0.20
Nodes (9): extractConvParamsFactory(), extractorsFactory(), extractorsFactory$1(), extractorsFactory$6(), extractParams(), extractParams$2(), extractParams$6(), extractParamsTiny() (+1 more)
### Community 71 - ".predictAgeAndGender"
Cohesion: 0.28
Nodes (4): extendWithAge(), extendWithGender(), PredictAllAgeAndGenderTask(), PredictSingleAgeAndGenderTask()
### Community 71 - "extractAllFacesAndComputeResults"
Cohesion: 0.29
Nodes (5): extendWithAge(), extendWithFaceDescriptor(), extendWithGender(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult()
### Community 76 - "TinyYolov2Base"
Cohesion: 0.40
Nodes (3): sigmoid(), TinyYolov2Base(), validateConfig()
### Community 76 - ".toString"
Cohesion: 0.33
Nodes (5): createFileSystem(), createNodejsEnv(), FaceMatch(), la(), pr()
### Community 77 - "drawFaceExpressions"
Cohesion: 0.22
Nodes (8): DrawBox(), drawDetections(), drawFaceExpressions(), DrawFaceLandmarks(), FaceExpressions(), isWithFaceDetection(), isWithFaceExpressions(), isWithFaceLandmarks()
### Community 77 - ".runAndExtendWithFaceDetections"
Cohesion: 0.18
Nodes (8): DetectAllFacesTask(), DrawBox(), drawDetections(), DrawFaceLandmarks(), extendWithFaceDetection(), isWithFaceDetection(), isWithFaceLandmarks(), resizeResults()
### Community 78 - "n"
Cohesion: 0.10
Nodes (33): be(), bi(), ce(), de(), e(), er(), fe(), Ie() (+25 more)
### Community 78 - "i"
Cohesion: 0.06
Nodes (45): b(), be(), bi(), ca(), ce(), de(), dn(), ee() (+37 more)
### Community 79 - "登录反馈与加载优化(2026-10-12)"
Cohesion: 0.25
@@ -374,9 +378,9 @@ Nodes (6): 初次部署时的凭据查找记录(现已由用户提供), 初
Cohesion: 0.60
Nodes (4): api(), base, callback(), start()
### Community 82 - ".forwardInput"
Cohesion: 0.31
Nodes (6): conv(), denseBlock4(), depthwiseSeparableConv(), mainBlock(), reductionBlock(), TinyXception()
### Community 82 - "拍摄约束放宽与标题更新"
Cohesion: 0.40
Nodes (4): 参数对照, 拍摄约束放宽与标题更新, 标题, 验证与发布
### Community 83 - "10.09 新版 H5 实施与验收"
Cohesion: 0.29
@@ -390,10 +394,6 @@ Nodes (6): 后续真机确认与体验优化, 微信登录联调:2026-10-12,
Cohesion: 0.22
Nodes (9): assert, create, fs, http, os, path, ReportUI, SkinScore (+1 more)
### Community 86 - "normalize"
Cohesion: 0.20
Nodes (5): denseBlock3(), FaceFeatureExtractor(), normalize(), __spreadArrays(), TinyFaceFeatureExtractor()
### Community 87 - "产品推荐规则:肤龄基准与边界调整"
Cohesion: 0.33
Nodes (6): 产品推荐规则:肤龄基准与边界调整, 决策顺序, 后台与前端一致性, 实施与验证范围, 已明确的要求, 已确认的三个条件
@@ -410,44 +410,36 @@ Nodes (9): Ci(), Ei(), Fi(), Ii(), ki(), pe(), ri(), wi() (+1 more)
Cohesion: 0.33
Nodes (5): 公网部署与复测, 客户反馈修改(2026-10-12), 皱纹数据说明, 逐项处理, 验证
### Community 93 - ".runAndExtendWithFaceDetections"
Cohesion: 0.19
Nodes (8): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), detectAllFaces(), DetectAllFacesTask(), DetectSingleFaceTask(), extendWithFaceDetection(), resizeResults()
### Community 93 - ".runAndExtendWithFaceDetection"
Cohesion: 0.23
Nodes (6): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), DetectAllFaceLandmarksTask(), detectAllFaces(), DetectSingleFaceTask()
### Community 94 - "第三轮反馈:PSD 对齐、验收及上线记录"
Cohesion: 0.33
Nodes (5): 原稿与导出可追溯性, 已完成, 本地验证, 生产部署及只读验收, 第三轮反馈:PSD 对齐、验收及上线记录
### Community 95 - ".forwardInput"
Cohesion: 0.48
Nodes (6): conv$1(), convDown(), convLayer$1(), convNoRelu(), residual(), residualDown()
### Community 96 - "production-loop.mjs"
Cohesion: 0.50
Nodes (3): artifacts, errors, samples
### Community 96 - ".run"
Cohesion: 0.29
Nodes (3): extendWithFaceExpressions(), PredictAllFaceExpressionsTask(), PredictSingleFaceExpressionsTask()
### Community 100 - "extractAllFacesAndComputeResults"
Cohesion: 0.11
Nodes (8): ComputeAllFaceDescriptorsTask(), ComputeSingleFaceDescriptorTask(), extendWithFaceDescriptor(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult(), FaceLandmarks(), FaceRecognitionNet(), minBbox()
### Community 107 - "rescaleAndNormalize"
Cohesion: 0.67
Nodes (3): getSizesForScale(), normalize$1(), rescaleAndNormalize()
### Community 101 - "drawFaceExpressions"
Cohesion: 0.50
Nodes (3): drawFaceExpressions(), FaceExpressions(), isWithFaceExpressions()
## Knowledge Gaps
- **285 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+280 more)
- **288 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+283 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **24 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **26 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `v()` connect `r` to `report-ui.js`, `face-api.js`, `app.js`, `C`?**
_High betweenness centrality (0.069) - this node is a cross-community bridge._
- **Why does `k()` connect `k` to `face-api.js`, `app.js`, `r`, `lib/accounts.js`, `n`, `C`?**
_High betweenness centrality (0.063) - this node is a cross-community bridge._
- **Why does `fill()` connect `report-ui.js` to `r`?**
_High betweenness centrality (0.055) - this node is a cross-community bridge._
- **Why does `v()` connect `n` to `report-ui.js`, `face-api.js`, `app.js`, `C`?**
_High betweenness centrality (0.068) - this node is a cross-community bridge._
- **Why does `k()` connect `n` to `face-api.js`, `app.js`, `lib/accounts.js`, `i`, `C`?**
_High betweenness centrality (0.062) - this node is a cross-community bridge._
- **Why does `fill()` connect `report-ui.js` to `n`?**
_High betweenness centrality (0.054) - this node is a cross-community bridge._
- **Are the 24 inferred relationships involving `n()` (e.g. with `newRecordId()` and `ae()`) actually correct?**
_`n()` has 24 INFERRED edges - model-reasoned connections that need verification._
- **Are the 27 inferred relationships involving `r()` (e.g. with `handleExport()` and `handleRecordsQuery()`) actually correct?**
@@ -455,4 +447,4 @@ _Questions this graph is uniquely positioned to answer:_
- **Are the 9 inferred relationships involving `i()` (e.g. with `da()` and `f()`) actually correct?**
_`i()` has 9 INFERRED edges - model-reasoned connections that need verification._
- **What connects `source`, `document_size`, `pages` to the rest of the system?**
_285 weakly-connected nodes found - possible documentation gaps or missing edges._
_288 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because one or more lines are too long
+699 -600
View File
File diff suppressed because it is too large Load Diff
+23 -18
View File
@@ -45,8 +45,8 @@
"semantic_hash": ""
},
"h5/js/head-pose.js": {
"mtime": 1791800353.8152337,
"ast_hash": "dcba5437419c26c1a0f2366b8e8beb68",
"mtime": 1791809137.1269257,
"ast_hash": "6c71a4db97fa9b08ec4f6ec5ca54d31b",
"semantic_hash": ""
},
"h5/js/skin-score.js": {
@@ -135,13 +135,13 @@
"semantic_hash": ""
},
"h5/README.md": {
"mtime": 1791807197.0309076,
"ast_hash": "1c839b61b6a74045b012d307d853e9f9",
"mtime": 1791809197.5909035,
"ast_hash": "4e597d6be915f6cd0b7fbd4bf34e5a96",
"semantic_hash": ""
},
"h5/index.html": {
"mtime": 1791800479.6232486,
"ast_hash": "5049684c7241b9a328cc9cf2891d2098",
"mtime": 1791809137.1269257,
"ast_hash": "505ec38f7f2120e7d4d1b61eb8328097",
"semantic_hash": ""
},
"h5/vendor/faceapi/README.md": {
@@ -1675,8 +1675,8 @@
"semantic_hash": ""
},
"h5/test/customer-feedback-second.js": {
"mtime": 1791800811.939266,
"ast_hash": "607b40839cdb0e6dc834b2d21016fb41",
"mtime": 1791809137.130926,
"ast_hash": "d494abe8a486bb753d671f9c4c8b533f",
"semantic_hash": ""
},
"h5/test/customer-feedback-second.mjs": {
@@ -1805,8 +1805,8 @@
"semantic_hash": ""
},
"h5/js/capture-quality.js": {
"mtime": 1791807197.0309076,
"ast_hash": "cca4d79b6df0b59ff24d1fa60ad05f6c",
"mtime": 1791809137.1269257,
"ast_hash": "ec66579ee0cde06a3fa045d612da69c1",
"semantic_hash": ""
},
"h5/lib/capture-diagnostics.js": {
@@ -1815,18 +1815,18 @@
"semantic_hash": ""
},
"h5/test/capture-quality-browser.mjs": {
"mtime": 1791807622.1829002,
"ast_hash": "e47f574f9e0254921d5f38be1e348b71",
"mtime": 1791809137.130926,
"ast_hash": "6daff225b0e02cec327882171408e4c5",
"semantic_hash": ""
},
"h5/test/customer-feedback-fourth.js": {
"mtime": 1791807197.0309076,
"ast_hash": "783be8c056950e7224436b0f004f8681",
"mtime": 1791809197.5909035,
"ast_hash": "818c6b58ba8b4771275457bb13604f1f",
"semantic_hash": ""
},
"h5/test/customer-feedback-fourth.mjs": {
"mtime": 1791807036.0188804,
"ast_hash": "e2a16a26dc7b6a9096a6f071544413a8",
"mtime": 1791809137.130926,
"ast_hash": "a07ebd722efaf2d8b6e382f566a5f649",
"semantic_hash": ""
},
"tools/export_fourth_feedback_assets.py": {
@@ -1835,8 +1835,8 @@
"semantic_hash": ""
},
"docs/\u5ba2\u6237\u53cd\u9988\u7b2c\u56db\u8f6e\u4fee\u6539-2026-10-12.md": {
"mtime": 1791807904.8348992,
"ast_hash": "cf06ddce0c92fd331e89edfe663d0f81",
"mtime": 1791808065.414884,
"ast_hash": "7117fd5a61f1c38159aa4ca4ac901a6c",
"semantic_hash": ""
},
"feedback/\u7b2c\u56db\u8f6e\u53cd\u9988/\u5085\u8273NEW\u4e0e\u9879\u601d\u70bc\u7684\u4f1a\u8bdd 2026\u5e7410\u670812\u65e5.md": {
@@ -1883,5 +1883,10 @@
"mtime": 1791801069.9552546,
"ast_hash": "d704649199e6785839683755f4e5b716",
"semantic_hash": ""
},
"docs/\u62cd\u6444\u7ea6\u675f\u653e\u5bbd\u4e0e\u6807\u9898\u66f4\u65b0-2026-10-12.md": {
"mtime": 1791809197.5949035,
"ast_hash": "62516ef04c3cba2f54e74c522d4e0b29",
"semantic_hash": ""
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
# 1'me 在线肌肤测试 H5
# 1'me肌肤状态检测 H5
引导式自动三连拍(正脸/右脸/左脸),调用 **Face++ 皮肤分析(专业版 skinanalyze_pro)** 三视图真实检测,即时生成三维度×6分类肌肤状态分析与个性化护理建议的移动端 H5。
@@ -62,9 +62,9 @@ node server.js # 默认 8080,可用 PORT=xxx 修改
三步严格按顺序,本步拍到合格照片才进入下一步(左右一律以用户本人为准):1/3 正对屏幕(target `front`)→ 2/3 右脸出镜(用户头向自己的左侧转,target `right`,产物作 `right_image`)→ 3/3 左脸出镜(头向自己的右侧转,target `left`,产物作 `left_image`)。送检前 `photosByView` 按 target 重排为 [正, 左, 右]。页面内没有返回按钮,微信/浏览器返回键经 History API 回到档案页并释放摄像头。
- **取景蒙版**:内联 SVG(`#cam-mask`)按步骤切换轮廓——正脸步为正脸剪影(含耳部),侧脸步为对应方向的侧脸剪影;**轮廓路径由 Figma 拍照示意图(`design/assets/images/*-拍照示意.png` 的 alpha 镂空区)逐像素描摹生成**,坐标为 Figma 375×812 整帧,viewBox 只取 y 86–812;框外 85% 黑,轮廓线 2.5px 半透明白。检测运行时轮廓内有自上而下的扫描带(`#cam-clip` 由 app.js 复制轮廓生成);未检测到人脸时轮廓呼吸动画;条件合格时轮廓变绿。
- **预览铺满**:`#cam-video` 保持现有 `object-fit:cover` 铺满与镜像策略,第四轮不改取景视觉,仅收紧下述本地门限。
- **动态提示**:第四轮收紧本地判定,单人、检测置信度≥0.5、人脸框高占原始画面 34%~64%、水平中心偏离≤12% 屏宽、完整入镜且距原始帧边缘≥2%。正脸 |yaw|≤10°/|pitch|≤12°,侧脸转头幅度 35°~65°/|pitch|≤15°,两者 |roll|≤10°;脸部平均亮度 75~225。参数集中在 `js/capture-quality.js` 与 `js/head-pose.js`,均为经验初值,不等价于服务端质量分。
- **合格即拍**:连续稳定至少 650ms 且至少 3 个合格检测帧,漏检、姿态/位置漂移或帧间隔超过 2 秒均重置稳定累计;白屏抓拍后在本地复验实际 JPEG,合格才推进。**无倒计时、无手动快门**(已有降级模式除外)。
- **预览铺满**:`#cam-video` 保持现有 `object-fit:cover` 铺满与镜像策略,不改取景视觉。
- **动态提示**:根据真机难拍反馈适度放宽(参数版本 `2026-10-12-fourth-v2`),单人、检测置信度≥0.4、人脸框高占原始画面 30%~68%、水平中心偏离≤16% 屏宽、完整入镜且距原始帧边缘≥1%。正脸 |yaw|≤15°/|pitch|≤18°,侧脸转头幅度 30°~70°/|pitch|≤20°,两者 |roll|≤15°;脸部平均亮度 65~235。参数集中在 `js/capture-quality.js` 与 `js/head-pose.js`,均为经验值,不等价于服务端质量分。
- **合格即拍**:连续稳定至少 450ms 且至少 2 个合格检测帧;容许 yaw/pitch 漂移≤8°、中心移动≤原帧 5.5%、框高占比变化≤6.5 个百分点。漏检、超限漂移或帧间隔超过 2 秒均重置稳定累计;白屏抓拍后在本地复验实际 JPEG,合格才推进。**无倒计时、无手动快门**(已有降级模式除外)。
- **两张之间停顿**:正脸、右脸拍完后中央弹出停顿卡约 2s(「正脸拍摄完成 / 下一张:右脸出镜,请向左转头」),停顿期间不检测,结束后再切到下一步并播下一步引导,让用户对下一张有心理预期。
- **实时检测**:face-api 从本地 `vendor/faceapi/` 加载(`tinyFaceDetector` + `faceLandmark68Net`),进入拍照页开始加载(状态提示「检测引擎加载中…」)。检测循环 ~150ms 一帧,特征点取自原始帧(预览 CSS 镜像不影响),个别前置帧已镜像的机型由侧脸自校准翻转 `sideYawFlip`;连续漏检 ≥3 帧才报「未检测到人脸」。
- **镜像策略**:前置镜头预览镜像(照镜子习惯);后置预览不镜像,侧脸轮廓随之翻转(`.cam-mask.flip`)。**抓帧一律不镜像**,三张照片都是摄像头原始视角。
+3 -3
View File
@@ -4,12 +4,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="description" content="1'me 在线肌肤测试:拍摄正面与左右侧面照片,即时获取六维肌肤状态分析与个性化护理建议。">
<meta name="description" content="1'me肌肤状态检测:拍摄正面与左右侧面照片,即时获取六维肌肤状态分析与个性化护理建议。">
<meta name="theme-color" content="#101823">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta property="og:title" content="1'me 在线肌肤测试">
<meta property="og:title" content="1'me肌肤状态检测">
<meta property="og:description" content="拍摄正面与左右侧面照片,即时获取六维肌肤状态分析与个性化护理建议。">
<title>1'me 在线肌肤测试</title>
<title>1'me肌肤状态检测</title>
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="preload" as="image" href="assets/img/ui-20261009/bg-login-h5.webp" fetchpriority="high">
<link rel="stylesheet" href="vendor/mobile-select/mobile-select.css">
+6 -4
View File
@@ -4,10 +4,12 @@
else root.CaptureQuality = factory(root.HeadPose);
})(typeof window !== 'undefined' ? window : this, function(HeadPose) {
'use strict';
var config = Object.freeze({ version: '2026-10-12-fourth-v1', score: 0.5,
near: 0.34, far: 0.64, center: 0.12, edge: 0.02,
dark: 75, bright: 225, holdMs: 650, minFrames: 3, maxGapMs: 2000,
yawDrift: 5, pitchDrift: 5, centerDrift: 0.035, sizeDrift: 0.045 });
// 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-12-fourth-v2', score: 0.4,
near: 0.30, far: 0.68, center: 0.16, edge: 0.01,
dark: 65, bright: 235, 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 evaluate(d, options) {
var failed = function(hint) { return { ok: false, hint: hint }; };
+14 -14
View File
@@ -62,14 +62,14 @@
var PITCH_COEFF = 180; // 归一化偏移 → 角度 的增益
// ---- check 阈值(角度制) ----
// Fourth feedback: conservative initial local gates. Empirical pose estimates
// are not upstream quality scores; use recorded samples to calibrate further.
var FRONT_YAW_MAX = 10;
var FRONT_PITCH_MAX = 12;
var SIDE_YAW_MIN = 35;
var SIDE_YAW_MAX = 65;
var SIDE_PITCH_MAX = 15;
var ROLL_MAX = 10;
// Real-phone feedback: relaxed alignment, without accepting frontal photos
// as side views. These empirical angles are not upstream quality scores.
var FRONT_YAW_MAX = 15;
var FRONT_PITCH_MAX = 18;
var SIDE_YAW_MIN = 30;
var SIDE_YAW_MAX = 70;
var SIDE_PITCH_MAX = 20;
var ROLL_MAX = 15;
var HINT_KEEP = '保持不动';
var HINT_TURN_LEFT = '请向左转头'; // 用户向自己的左侧转
@@ -158,10 +158,10 @@
/**
* check(points, target, mirrored) -> { ok, hint }
* target: 'front' | 'left' | 'right'
* 'front' : |yaw|<=10 且 |pitch|<=12、|roll|<=10;不 ok 时给方向提示
* 'front' : |yaw|<=15 且 |pitch|<=18、|roll|<=15;不 ok 时给方向提示
* (UI 反馈 2026-10:动态提示「请向左转头」「请略微抬头」等,取代此前只提示回正)
* 'left' : 画面中看到左脸 = 被摄者头向右转(语义 yaw ∈ [-65°,-35°]),|pitch|<=15
* 'right' : 画面中看到右脸 = 被摄者头向左转(语义 yaw ∈ [35°,65°]),|pitch|<=15
* 'left' : 画面中看到左脸 = 被摄者头向右转(语义 yaw ∈ [-70°,-30°]),|pitch|<=20
* 'right' : 画面中看到右脸 = 被摄者头向左转(语义 yaw ∈ [30°,70°]),|pitch|<=20
* ok=true 时 hint='保持不动';输入非法(含 estimate 返回 null 的退化特征点)时 ok=false、hint='请正对屏幕'。
*/
function check(points, target, mirrored) {
@@ -341,15 +341,15 @@ if (typeof module !== 'undefined' && typeof require !== 'undefined' && require.m
ok(HeadPose.estimate(tiny, false) === null, 'faceHeight 过小 → estimate 返回 null');
ok(HeadPose.check(tiny, 'front', false).hint === '请正对屏幕', 'faceHeight 过小 check 兜底');
// 13) Fourth feedback: 10° local frontal gate rejects the former 15° edge.
ok(HeadPose.config.FRONT_YAW_MAX === 10, 'config.FRONT_YAW_MAX = 10');
// 13) Phone feedback: restore the 15° frontal margin, still reject 16°+.
ok(HeadPose.config.FRONT_YAW_MAX === 15, 'config.FRONT_YAW_MAX = 15');
var slight = makeFrontal();
slight[30] = { x: 205.6, y: 140 }; // 偏移 5.6 → yaw≈14.9°(YAW_COEFF=3.8 标定)
slight[33] = { x: 205.6, y: 161.5 };
var se = HeadPose.estimate(slight, false);
ok(se.yaw > 13.5 && se.yaw <= 15, '轻微偏头 yaw∈(13.5,15] (got ' + se.yaw.toFixed(2) + ')');
var sc2 = HeadPose.check(slight, 'front', false);
ok(sc2.ok === false, 'yaw≈14.9° front 拒绝(第四轮收紧)');
ok(sc2.ok === true, 'yaw≈14.9° front 通过(真机反馈放宽)');
var slight2 = makeFrontal();
slight2[30] = { x: 206.2, y: 140 }; // 偏移 6.2 → yaw≈16.2°
slight2[33] = { x: 206.2, y: 161.5 };
+6 -5
View File
@@ -48,6 +48,7 @@ try {
await route.fulfill({ json: fixture });
});
await page.goto(base); await page.click('[data-action="open-privacy"]');
assert.equal(await page.title(), "1'me肌肤状态检测");
await page.click('#privacy-check'); await page.click('[data-action="agree-continue"]');
for (const selector of ['#pick-birth', '#pick-gender', '#pick-region']) {
await page.click(selector); await page.click('.ms-mobile-select.ms-show .ms-ensure'); await page.waitForTimeout(400);
@@ -100,12 +101,12 @@ try {
for (const key of ['front', 'left', 'right']) {
const row = body.capture_diagnostics.views[key];
assert.equal(row.mode, 'live'); assert.equal(row.direction_checked, true);
assert(row.score >= .5 && row.luma >= 75 && row.luma <= 225);
assert(Math.abs(row.roll) <= 10);
assert(row.score >= .4 && row.luma >= 65 && row.luma <= 235);
assert(Math.abs(row.roll) <= 15);
}
assert(Math.abs(body.capture_diagnostics.views.front.yaw) <= 10);
assert(body.capture_diagnostics.views.left.yaw <= -35);
assert(body.capture_diagnostics.views.right.yaw >= 35);
assert(Math.abs(body.capture_diagnostics.views.front.yaw) <= 15);
assert(body.capture_diagnostics.views.left.yaw <= -30);
assert(body.capture_diagnostics.views.right.yaw >= 30);
assert(await page.evaluate(() => document.querySelector('#cam-video').srcObject === null), 'camera released after success');
assert.deepEqual(errors, []);
console.log('PASS: actual local models + automatic three-view capture + final JPEG revalidation; front/wrong-direction frames blocked locally; all three approved samples recorded; camera released; no paid call or backend writes');
+26 -13
View File
@@ -28,10 +28,14 @@ const options = { count: 1, target: 'front', width: 600, height: 600, offsetX: 0
const check = (d, changes) => Quality.evaluate(d, { ...options, ...changes });
assert(check(face()).ok);
assert(!check(null).ok); assert(!check(face(), { count: 2 }).ok);
for (const yaw of [-14, 14]) assert(!check(face(yaw)).ok);
for (const pitch of [-18, 18]) assert(!check(face(0, pitch)).ok);
assert.equal(check(face(0, 0, 15)).hint, '请勿歪头');
for (const yaw of [-14, 14]) assert(check(face(yaw)).ok, 'former strict front limit now passes');
for (const yaw of [-16, 16]) assert(!check(face(yaw)).ok);
for (const pitch of [-17, 17]) assert(check(face(0, pitch)).ok);
for (const pitch of [-20, 20]) assert(!check(face(0, pitch)).ok);
assert(check(face(0, 0, 12)).ok, 'normal tilt now passes');
assert.equal(check(face(0, 0, 16)).hint, '请勿歪头');
for (const yaw of [25, 72]) assert(!check(face(yaw), { target: 'right' }).ok);
for (const yaw of [32, 68]) assert(check(face(yaw), { target: 'right' }).ok);
assert(check(face(45), { target: 'right' }).ok);
assert(!check(face(45), { target: 'left' }).ok);
assert(check(face(-45), { target: 'left' }).ok);
@@ -39,22 +43,29 @@ assert(check(face(-45), { target: 'right', mirrored: true }).ok);
assert(check(face(-45), { target: 'right', ignoreDirection: true }).ok);
assert(!check(face(25), { target: 'left', ignoreDirection: true }).ok);
for (const luma of [null, 50, 240, NaN]) assert(!check(face(), { luma }).ok);
assert(!check(face(), { offsetX: .15 }).ok);
for (const changes of [{ height: 180 }, { height: 410 }, { x: -10 }, { y: 5 }]) {
for (const luma of [70, 230]) assert(check(face(), { luma }).ok);
assert(check(face(), { offsetX: .15 }).ok);
assert(!check(face(), { offsetX: .17 }).ok);
for (const changes of [{ height: 190 }, { height: 400 }, { y: 8 }]) {
const d = face(); Object.assign(d.detection.box, changes); assert(check(d).ok, 'former framing limits now pass');
}
for (const changes of [{ height: 179 }, { height: 410 }, { x: -10 }, { y: 5 }]) {
const d = face(); Object.assign(d.detection.box, changes); assert(!check(d).ok);
}
const weak = face(); weak.detection.score = .4; assert(!check(weak).ok);
const weak = face(); weak.detection.score = .45; assert(check(weak).ok);
weak.detection.score = .35; assert(!check(weak).ok);
const obs = check(face()).observation;
let held = Quality.hold(null, obs, 1000); assert(!held.ready);
held = Quality.hold(held.state, obs, 1400); assert(!held.ready);
assert(Quality.hold(held.state, obs, 1650).ready);
assert(!Quality.hold(Quality.hold(null, obs, 1000).state, obs, 1700).ready, 'two frames never pass');
assert(!Quality.hold(held.state, obs, 1449).ready);
assert(Quality.hold(held.state, obs, 1450).ready);
assert(Quality.hold(Quality.hold(null, obs, 1000).state, obs, 1450).ready, 'two stable frames at 450ms now pass');
assert.equal(Quality.hold(held.state, null, 1700).state, null);
assert.equal(Quality.hold(held.state, obs, 3500).state.frames, 1, 'gap resets');
const slow = Quality.hold(Quality.hold(null, obs, 1000).state, obs, 2100);
assert(!slow.ready, 'slow device still needs three frames');
assert(Quality.hold(slow.state, obs, 3200).ready, '1100ms inference must not permanently prevent a stable capture');
for (const change of [{ yaw: 6 }, { pitch: 6 }, { ratio: .6 }, { center_x: .55 }, { center_y: .56 }]) assert.equal(Quality.hold(held.state, { ...obs, ...change }, 1500).state.frames, 1, 'motion resets');
assert(slow.ready, '1100ms inference now only needs two checked frames');
for (const change of [{ yaw: 7 }, { pitch: 7 }, { ratio: .56 }, { center_x: .55 }, { center_y: .55 }]) assert(Quality.hold(held.state, { ...obs, ...change }, 1500).ready, 'small movement no longer resets');
for (const change of [{ yaw: 9 }, { pitch: 9 }, { ratio: .6 }, { center_x: .56 }, { center_y: .58 }]) assert.equal(Quality.hold(held.state, { ...obs, ...change }, 1500).state.frames, 1, 'large motion still resets');
for (const [dims, key] of [
[[10, 50, 90], 'firm'], [[90, 10, 50], 'vita'], [[90, 50, 10], 'stab'],
[[50, 50, 90], 'firm'], [[null, 50, 50], 'vita'], [[50.1, 50, 90], 'vita']
@@ -74,8 +85,10 @@ assert.equal(Diagnostics.assess(raw).status, 'invalid');
assert.equal(Diagnostics.assess({}).status, 'unavailable');
assert.equal(Diagnostics.assess(raw).views.right.status, 'valid');
const normalized = Diagnostics.normalize({ version: Quality.config.version, secret: 'discard', views: { front: { mode: 'live', yaw: 1, score: .9, luma: Infinity, secret: 'discard' }, left: { mode: 'unchecked', reason: 'model-unavailable' } } });
assert.equal(normalized.policy.capture.holdMs, 650);
assert.equal(normalized.policy.capture.holdMs, 450);
assert.equal(normalized.policy.capture.minFrames, 2);
assert.equal(normalized.version, '2026-10-12-fourth-v2');
assert.equal(normalized.views.front.luma, undefined);
assert(!JSON.stringify(normalized).includes('discard'));
assert.equal(Diagnostics.normalize({ version: '<script>' }).version, 'unknown');
console.log('PASS: stricter local pose/framing/light/single-face gates; 650ms/3-frame stability/motion/miss/gap reset; all focus cases/ties/missing/saved scores; no wrinkle mutations; backend quality marking and bounded client diagnostics');
console.log('PASS: former strict pose/framing/light/motion limits now pass; invalid views/multiple faces/cropping/large movement still rejected; 450ms/2-frame stability/miss/gap reset; focus/history/backend diagnostics');
+4 -1
View File
@@ -43,6 +43,8 @@ try {
});
await page.route('**/vendor/faceapi/face-api.js', route => route.abort());
await page.goto(base); await page.waitForLoadState('networkidle');
assert.equal(await page.title(), "1'me肌肤状态检测");
assert.equal(await page.locator('meta[property="og:title"]').getAttribute('content'), "1'me肌肤状态检测");
assert.equal((await page.locator('[data-action="login-browser"]').textContent()).trim(), '游客登录');
assert(!requests.some(p => /scan-(base|face)|\.woff2$|faceapi/.test(p)), 'no heavy/scanning entry requests');
await page.check('#login-consent'); await page.click('[data-action="login-browser"]');
@@ -64,7 +66,8 @@ try {
assert.equal(admin.capture_quality.status, 'invalid');
assert.equal(admin.capture_quality.views.right.quality, 3);
assert.equal(admin.capture_diagnostics.version, CaptureQuality.config.version);
assert.equal(admin.capture_diagnostics.policy.pose.SIDE_YAW_MIN, 35);
assert.equal(admin.capture_diagnostics.policy.pose.SIDE_YAW_MIN, 30);
assert.equal(admin.capture_diagnostics.policy.capture.minFrames, 2);
assert.equal(admin.capture_diagnostics.ignore, undefined);
assert(admin.notes.some(n => n.includes('quality=3')));
const wrinkle = admin.presentation.views.find(view => view.key === 'wrinkles');
+4 -4
View File
@@ -33,7 +33,7 @@ assert(context.captureHaptic({ vibrate: p => { pattern = Array.from(p); return t
assert.deepEqual(pattern, [70, 40, 70]);
// Exercise the actual readiness state machine; only camera/pose inputs and clock
// are stubs; the fourth-round shared hold policy now requires 650ms/3 frames.
// are stubs; the relaxed shared hold policy now requires 450ms/2 frames.
let now = 10000, buzzes = 0, captures = 0;
const state = {
missFrames: 0, MISS_FRAMES_MAX: 3, camVideo: { videoHeight: 600 }, FACE_NEAR: .30, FACE_FAR: .68,
@@ -51,14 +51,14 @@ vm.runInNewContext(extract('camera-haptic-pure') + source.slice(source.indexOf('
const face = { detection: { box: { height: 250 } }, landmarks: { positions: [] } };
state.onDetect(face); assert.equal(buzzes, 1); assert.equal(captures, 0);
now += 200; state.onDetect(face); assert.equal(buzzes, 1); assert.equal(captures, 0);
now += 450; state.onDetect(face); assert.equal(buzzes, 1); assert.equal(captures, 1);
now += 250; state.onDetect(face); assert.equal(buzzes, 1); assert.equal(captures, 1);
now += 100; state.onDetect({ ...face, detection: { box: { height: 20 } } });
now += 100; state.onDetect(face); assert.equal(buzzes, 1, 'jitter must not repeatedly buzz');
now += 2100; state.onDetect({ ...face, detection: { box: { height: 20 } } });
state.onDetect(face); assert.equal(buzzes, 2);
state.navigator.vibrate = () => { throw Error('blocked'); };
now += 300; state.onDetect(face);
now += 350; state.onDetect(face); assert.equal(captures, 2, 'vibration failure must not prevent capture');
now += 150; state.onDetect(face); assert.equal(captures, 2, 'vibration failure must not prevent capture');
for (const manual of [true, false]) {
let manualBuzzes = 0, scheduled = [];
const shutter = { shutterLock: false, camLive: true, capturing: false, camSeq: 1,
@@ -71,4 +71,4 @@ for (const manual of [true, false]) {
assert(shutter.shutterLock && shutter.capturing);
assert.deepEqual(scheduled, [300], 'blocked haptic must still schedule camera capture');
}
console.log('PASS: three bounded rounded contours; haptic safety, pre-capture cue, jitter throttling; fourth-round 650ms/3-frame hold');
console.log('PASS: three bounded rounded contours; haptic safety, pre-capture cue, jitter throttling; relaxed 450ms/2-frame hold');