Analyze contour-based reference slimming and profile deformation gap; preserve installed 1.0.4
这个提交包含在:
1 父节点
302dc20c0a
当前提交
2a856c669a
7 个文件已更改
+592
-1
没有与搜索条件相匹配的文件
+4
@@ -36,3 +36,7 @@ git switch --detach v1.0.4
|
||||
FCP模板从 `Templates/大鱼人像美化/` 恢复,将 `.moef` 和 `Drop Zone.tiff` 一起放入 `~/Movies/Motion Templates.localized/Effects.localized/大鱼/大鱼人像美化/`;只放 `.moef` 会缺少标准效果源输入资源。正式APP仅安装在 `/Library/Plug-Ins/FxPlug/beatygril.app`,不是从工作树或构建目录运行。没有LFS或子模块,必要工程资源、NASA测试人像及配套模板资源均在Git中。
|
||||
|
||||
用户用于宿主验收的外置卷视频及build内的验收工程/截图属于测试素材与可重建产物,不属于产品构建依赖,也没有远程上传。原MSE18V2 APP与原版MS模板仅为只读参考,不作为运行依赖。远端恢复记录及实际宿主测试范围见 `docs/验证记录.md`。
|
||||
|
||||
## 瘦脸与侧脸问题的分析节点
|
||||
|
||||
`v1.0.4`之后的分析提交包含`docs/analysis/脸型变形与转脸分析.md`、`slim-pose-report.json`、REA证据包及独立Vision离线检测工具`scripts/inspect-face-geometry.m`。产品源码与安装版没有因该分析改变;已查明的问题仍存在于1.0.4。复现检测工具的依赖与命令见分析文档,用户视频、完整人脸坐标和帧图保留本地,不是产品恢复依赖。
|
||||
@@ -71,3 +71,7 @@ REA 直接打开内嵌插件。原包为 arm64/x86_64 通用 Mach-O,Ghidra 无
|
||||
本机原生观察:Motion中使用同一蓝发视频,原版UI默认多人关闭(单人)、防跳180%;启用检测框并调瘦脸/大眼后,播放时自动更新人脸框,没有先执行离线分析。此观察只覆盖该单人片段,不能替代多人遮挡与所有参数对照。
|
||||
|
||||
1.0.4已在Motion实际加载与不同播放头位置观察默认自动跟随输出;用户随后接管Motion/FCP自测,FCP该版自动跟随及显式OSC原生点击尚未完成自动验收。已完成范围与剩余验证见 [验证记录](../验证记录.md)。
|
||||
|
||||
## 最大瘦脸与转脸差异(用户复测反馈)
|
||||
|
||||
用户反馈跟踪正常,但最大瘦脸与原版不一致,侧脸出现异常变形。补充REA与Metal AIR分析确认,原版瘦脸以76点加中心点输入,执行17组轮廓到鼻部/瞳孔锚点的局部逆向采样;当前复刻采用矩形框水平对称场,两者没有对齐。已检查的GPU瘦脸路径没有显式yaw角度切换或侧脸衰减,不能将局部二维点自适应描述为完整3D姿态识别。详见 [脸型变形与转脸分析](脸型变形与转脸分析.md) 及 [结构报告](slim-pose-report.json)。本轮未修改安装版的变形代码。
|
||||
@@ -0,0 +1 @@
|
||||
{"artifacts":[{"architecture":"arm64","digest":{"sha256":"f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb"},"format":"mach-o"}],"captures":[],"environments":[],"providers":[{"id":"ghidra","name":"Ghidra","version":"12.1.4"}],"records":[{"analysis_profile":{"digest":"20565088524a2d0359b9b96f46ac48e679d774c7b0092c56abc06de8fdbec11c","parameters":{"analyzer_preset":"ghidra-default","annotation_policy":"atomic-function-entry-metadata-v1","architecture":"arm64","available_architectures":["arm64"],"compiler_spec_id":"auto-default","decompiler_jump_loads":true,"function_body_evidence":"complete-inclusive-ranges-v1","function_boundary_observations":"ghidra-terminal-call-limitations-v1","function_references":"complete-body-and-entry-reference-manager-v2","import_mode":"ephemeral-source-immutable","instruction_flow_evidence":"decoded-return-pcode-v1","jump_table_evidence":"typed-case-default-blocks-v1","language_id":"auto-from-header","load_image_observations":"source-mappings-entry-context-v2","loader":"auto-from-header","location_resolution":"explicit-address-exact-entry-symbol-first-v3","no_return_repair":"returning-imports-decoded-return-v2","process_launch":"inspected-jvm-launch-support-v1","string_inventory_evidence":"defined-data-coverage-v1","target_format":"mach-o","target_kind":"executable"},"provider":{"id":"ghidra","name":"Ghidra","version":"12.1.4"}},"authority":"shipped-artifact","confidence":"observed","environment":null,"evidence_id":"ev_02f734a0d72f1b7b77a3112d1de297b9e99ea43d5377349e6eebb71ab017d650","evidence_links":[],"limitations":["The session serves operations only after Ghidra reports default auto-analysis complete; incomplete analysis does not expose partial results.","The imported Program and temporary project are ephemeral and deleted on close. Annotation operations edit session database metadata; original executable bytes are never written.","Cancelling an active Ghidra request terminates its ephemeral database and discards session annotations. The selected target remains open; the next provider query imports the original artifact again.","Default-space addresses use lowercase 0x-prefixed hexadecimal; other address spaces use <percent-encoded-space>:0x<hex>.","Pseudocode is Ghidra decompiler output, not original source and not text-equivalent to Hopper output.","External functions and functions without an analyzable body return null; other decompiler failures remain explicit.","Ghidra Listing reports a terminal call at 0x100016b84 to 0x10005c880 objc_stub::setUsesCPUOnly: (external=false, thunk=false, hasNoReturn=false); fallthrough is excluded by Ghidra's flow model. The reason for the terminal flow is not established by this observation.","macOS sessions require a matching executable Ghidra native decompiler; REA checks for it but does not build native components or change Gatekeeper quarantine state."],"locations":[],"normalized_result":"\nvoid FUN_100016a3c(ID param_1,ID param_2,ID param_3)\n\n{\n undefined8 uVar1;\n int iVar2;\n ID IVar3;\n ID IVar4;\n ID IVar5;\n \n if (param_1 == 0) {\n return;\n }\n if ((param_2 == 0) || (IVar3 = objc_stub::revision(param_2), IVar3 == 0)) {\n IVar5 = VNDetectFaceLandmarksRequest::supportedRevisions\n ((ID)&_OBJC_CLASS___VNDetectFaceLandmarksRequest);\n IVar3 = 3;\n IVar4 = VNDetectFaceLandmarksRequest::containsIndex_(IVar5,3);\n if ((IVar4 & 1) == 0) {\n IVar5 = VNDetectFaceLandmarksRequest::containsIndex_(IVar5,2);\n IVar3 = 1;\n if ((int)IVar5 != 0) {\n IVar3 = 2;\n }\n }\n }\n else {\n IVar3 = objc_stub::revision(param_2);\n }\n IVar5 = VNDetectFaceLandmarksRequest::supportedRevisions\n ((ID)&_OBJC_CLASS___VNDetectFaceLandmarksRequest);\n IVar5 = VNDetectFaceLandmarksRequest::containsIndex_(IVar5,IVar3);\n if ((IVar5 & 1) == 0) {\n IVar3 = VNDetectFaceLandmarksRequest::supportedRevisions\n ((ID)&_OBJC_CLASS___VNDetectFaceLandmarksRequest);\n IVar5 = VNDetectFaceLandmarksRequest::containsIndex_(IVar3,3);\n if ((int)IVar5 == 0) {\n IVar3 = VNDetectFaceLandmarksRequest::containsIndex_(IVar3,2);\n uVar1 = 1;\n if ((int)IVar3 != 0) {\n uVar1 = 2;\n }\n objc_stub::setRevision_(param_1,uVar1);\n goto LAB_100016b4c;\n }\n objc_stub::setRevision_(param_1,3);\n }\n else {\n objc_stub::setRevision_(param_1,IVar3);\n if (IVar3 != 3) goto LAB_100016b4c;\n }\n IVar3 = VNDetectFaceLandmarksRequest::revision_supportsConstellation_\n ((ID)&_OBJC_CLASS___VNDetectFaceLandmarksRequest,3,2);\n if ((int)IVar3 != 0) {\n objc_stub::setConstellation_(param_1,2);\n }\nLAB_100016b4c:\n IVar3 = objc_stub::count(param_3);\n iVar2 = 0;\n if (IVar3 != 0) {\n IVar3 = objc_stub::setInputFaceObservations_(param_1,param_3);\n iVar2 = (int)IVar3;\n }\n FUN_10003b084();\n if (iVar2 == 0) {\n return;\n }\n objc_stub::setUsesCPUOnly_(param_1,1Line truncated
|
||||
@@ -0,0 +1,385 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"date": "2026-10-21",
|
||||
"request": "最大瘦脸差异与人物转脸时的变形机制",
|
||||
"reference": {
|
||||
"plugin_sha256": "6c5d74bc844a5c5f745718b48ea10ec6635d08aa08a5b6dc6e62653980849f8a",
|
||||
"arm64_sha256": "f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb",
|
||||
"metallib_sha256": "4e8c77db8be94ae169337a2e646407f69520b63afb248faa653e0e068237aaae"
|
||||
},
|
||||
"air_observation": {
|
||||
"authority": "static Apple metal-objdump AIR disassembly; not observed shader execution",
|
||||
"command": "xcrun metal-objdump --metallib --disassemble-symbols=faceThinFragmentShader <reference default.metallib>",
|
||||
"full_output_sha256": "f97705b88f5491057df2117aef457ffca84e11020574cd57742b0670db4484d8",
|
||||
"full_output_bytes": 274496,
|
||||
"local_output": "build/pose-analysis/face-thin.air.txt",
|
||||
"module": "faceThinFragmentShader",
|
||||
"module_metallib_offset": "0x5d5e",
|
||||
"function": "mssFaceShapePinCoordinate",
|
||||
"active_branch": "faceShapeParamsActive selects Shape helper; inactive branch alone selects legacy Thin helper",
|
||||
"local_pin_triplets": [
|
||||
[
|
||||
75,
|
||||
74,
|
||||
6
|
||||
],
|
||||
[
|
||||
59,
|
||||
60,
|
||||
13
|
||||
],
|
||||
[
|
||||
74,
|
||||
73,
|
||||
47
|
||||
],
|
||||
[
|
||||
60,
|
||||
61,
|
||||
47
|
||||
],
|
||||
[
|
||||
73,
|
||||
72,
|
||||
47
|
||||
],
|
||||
[
|
||||
61,
|
||||
62,
|
||||
47
|
||||
],
|
||||
[
|
||||
72,
|
||||
71,
|
||||
48
|
||||
],
|
||||
[
|
||||
62,
|
||||
63,
|
||||
48
|
||||
],
|
||||
[
|
||||
71,
|
||||
70,
|
||||
48
|
||||
],
|
||||
[
|
||||
63,
|
||||
64,
|
||||
48
|
||||
],
|
||||
[
|
||||
70,
|
||||
69,
|
||||
49
|
||||
],
|
||||
[
|
||||
64,
|
||||
65,
|
||||
49
|
||||
],
|
||||
[
|
||||
69,
|
||||
68,
|
||||
49
|
||||
],
|
||||
[
|
||||
65,
|
||||
66,
|
||||
49
|
||||
],
|
||||
[
|
||||
68,
|
||||
67,
|
||||
52
|
||||
],
|
||||
[
|
||||
66,
|
||||
67,
|
||||
52
|
||||
],
|
||||
[
|
||||
67,
|
||||
65,
|
||||
47
|
||||
]
|
||||
],
|
||||
"triplet_usage": "first=origin, third=anchor; second unused in thin loop",
|
||||
"thin_cpu_scale": 0.025,
|
||||
"size_gain": "clamp(280/max(length((P75-P59)*imageSize),2*length((P67-(P74+P60)/2)*imageSize),1),1,2.25)",
|
||||
"effective_pin_gain": "clamp(UI thin,0,1) * strengthMultiplier * 0.025 * size_gain",
|
||||
"radius": "length(origin-anchor) in normalized image UV",
|
||||
"falloff": "clamp(1-length(currentSampleCoordinate-origin)/radius,0,1)",
|
||||
"inverse_sample_step": "q = clamp(q-(anchor-origin)*effective_pin_gain*falloff,0,1)",
|
||||
"iteration": "17 sequential updates; not 17 independent offsets evaluated at one original coordinate",
|
||||
"bounded_negative_findings": [
|
||||
"No yaw/roll/pitch input to the checked GPU helpers",
|
||||
"No explicit side-view angle gate in the checked thin branch",
|
||||
"No explicit eye/nose restore mask in the checked thin branch; each single pin has zero weight at its own anchor"
|
||||
],
|
||||
"limits": [
|
||||
"This is a 2D local landmark deformation, not proof of 3D head pose estimation",
|
||||
"Static paths are not a capture of the exact runtime branch used in the supplied screenshots",
|
||||
"Other contour controls include fixed horizontal offsets; not all controls share rotation-equivariant behavior"
|
||||
]
|
||||
},
|
||||
"vision_probe": {
|
||||
"authority": "Independent public Apple Vision / AVFoundation invocation; no reference app or plug-in executed",
|
||||
"script": "scripts/inspect-face-geometry.m",
|
||||
"revision": 3,
|
||||
"constellation": 76,
|
||||
"maximum_dimension": 1280,
|
||||
"coordinate_system": "full-image normalized, y up",
|
||||
"region_indices": {
|
||||
"contour": [
|
||||
59,
|
||||
60,
|
||||
61,
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70,
|
||||
71,
|
||||
72,
|
||||
73,
|
||||
74,
|
||||
75
|
||||
],
|
||||
"innerLips": [
|
||||
42,
|
||||
40,
|
||||
43,
|
||||
45,
|
||||
41,
|
||||
44
|
||||
],
|
||||
"leftEye": [
|
||||
0,
|
||||
4,
|
||||
5,
|
||||
1,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"leftPupil": [
|
||||
6
|
||||
],
|
||||
"nose": [
|
||||
47,
|
||||
56,
|
||||
54,
|
||||
53,
|
||||
52,
|
||||
51,
|
||||
50,
|
||||
55
|
||||
],
|
||||
"noseCrest": [
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
58,
|
||||
57
|
||||
],
|
||||
"outerLips": [
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
26
|
||||
],
|
||||
"rightEye": [
|
||||
7,
|
||||
11,
|
||||
12,
|
||||
8,
|
||||
10,
|
||||
9
|
||||
],
|
||||
"rightPupil": [
|
||||
13
|
||||
]
|
||||
},
|
||||
"frames": [
|
||||
{
|
||||
"time": 0,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 2,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0.7853981852531433
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 4,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
1.5707963705062866
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 6,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 8,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 10,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 12,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 14,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 16,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 18,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
1.5707963705062866
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 20,
|
||||
"face_count": 1,
|
||||
"all_point_counts": [
|
||||
76
|
||||
],
|
||||
"yaw_radians": [
|
||||
0.7853981852531433
|
||||
],
|
||||
"pitch_radians": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"result": "All 11 sampled frames returned one face and 76 points; 4s/18s reported yaw approx pi/2; pitch unavailable",
|
||||
"privacy": "Source frames, complete landmarks and source path remain ignored under build/pose-analysis/vision and are not included in Git"
|
||||
},
|
||||
"reconstruction_baseline": {
|
||||
"commit": "e03e460a9b3e5ad489aad24095485ba10ceb987d",
|
||||
"installed_version": "1.0.4 (6)",
|
||||
"root_causes": [
|
||||
"thin deformation uses bounding box x/y/size rather than saved contour/nose points",
|
||||
"horizontal symmetric inverse warp is applied at full strength for any valid box",
|
||||
"no contour/landmark completeness guard or pose metadata preservation",
|
||||
"fixed coefficients do not match reference local-pin scaling"
|
||||
],
|
||||
"direction_check": "Positive thin samples outward and moves image features inward; the sign is not reversed",
|
||||
"test_gap": "Existing geometry tests establish pixel changes, extent and persistence, not reference likeness or profile shape preservation"
|
||||
},
|
||||
"checklist": {
|
||||
"why_maximum_slim_differs": "answered: different geometric model, locality and effective strength",
|
||||
"how_profile_is_handled": "answered within checked active thin path: projected 2D landmarks, local directions/radii; no established explicit yaw gate",
|
||||
"full_native_visual_equivalence": "unverified; screenshots are not a synchronized isolated-parameter capture",
|
||||
"product_fix": "not changed by this analysis; installed 1.0.4 retains the reported issue"
|
||||
},
|
||||
"host_boundary": "User controls Motion/FCP; no UI interaction, host shutdown, app install or plug-in replacement in this analysis."
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# 最大瘦脸与转脸变形分析
|
||||
|
||||
2026-10-21。用户已反馈自动跟随正常;本次检查四张实测图指出的脸型差异。当前安装版仍为1.0.4(6),本次没有修改产品变形代码、替换APP或操作Motion/FCP。
|
||||
|
||||
## 结论
|
||||
|
||||
我们的瘦脸按矩形脸框建立左右对称的水平收缩场;原版主要瘦脸路径根据完整二维面部关键点,顺序执行17组局部位移。两者的作用范围、方向和强度定义均不同。侧脸继续使用正面矩形场,是当前鼻尖、下巴和邻近区域异常变形的直接原因;跟踪框跟对人不能解决这个问题。
|
||||
|
||||
原版已检查的瘦脸路径没有读取yaw/roll/pitch后切换侧脸算法,也没有专门的侧脸角度衰减。它每帧获得随姿态变化的二维轮廓与鼻部位置,让局部收缩方向和作用半径随这些点变化。这属于二维关键点自适应,不能表述成已恢复完整3D姿态估计。
|
||||
|
||||
## 原版证据链
|
||||
|
||||
只读目标与上一轮一致:插件原件SHA-256为`6c5d74bc844a5c5f745718b48ea10ec6635d08aa08a5b6dc6e62653980849f8a`;arm64分析切片为`f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb`。本次开始后重新计算原件摘要,未发生变化。Metal库摘要为`4e8c77db8be94ae169337a2e646407f69520b63afb248faa653e0e068237aaae`。
|
||||
|
||||
|步骤|直接静态观察|REA Evidence|
|
||||
|---|---|---|
|
||||
|检测设置|`0x100016a3c`优先配置Vision revision 3及76点constellation;不支持时存在旧revision回退|`ev_02f734a0d72f1b7b77a3112d1de297b9e99ea43d5377349e6eebb71ab017d650`|
|
||||
|当前帧检测|`cachedFaceObservations…`发起`VNDetectFaceLandmarksRequest`,另有重试与缓存|`ev_61ea8c6e7117f18e47fe6cf820c9ec0a59865dedaf3d9c09813be0a5bd7a79bc`|
|
||||
|有效点筛选|`0x1000230cc`要求至少76点;`0x100022c70`转换76点并添加第77个中心点;`0x100017cd8`检查有限数值、空间范围及轮廓分布|`ev_63bb7c8fb07dbe456027f4d4e6be52118338b91ab39dbf9ed6620de9090bc260`、`ev_5f27f174c793c94cd45219d6c405e6b54b96e2650c7462ccc5eeaea14f2604c3`、`ev_1c62b4dab3cf8b661eb5c31b034a0b3cadf9f1f6cb512aace8b3b0838bc74b5c`|
|
||||
|输入几何|`0x1000274d0`打包77个float2,616字节,转换到输出纹理坐标并检查越界点/轮廓尺度|`ev_8a33a7bb27a1a966f9e4ee26380501f1cb009d7d4665ed93b73bd3f630436556`|
|
||||
|参数缩放|`0x1000279f4`将瘦脸滑块值乘0.025及渲染强度,写入十float脸型结构的第一项|`ev_449e1fc8883b8afe27240f672f98f300d4132ac4994db5e1590a4f7ca4e21278`|
|
||||
|GPU连接|`FxMTLDeviceCacheItem::initWithDevice:pixelFormat:`将`faceThinFragmentShader`接入瘦脸pipeline;`0x100027d38/0x10002807c`上传点阵、数量及脸型参数到该pipeline|`ev_1520c2d83845004994f2be940c8c5ea3f4a62daf2d8e3e06278f381ffca1a010`、`ev_dc4487de4891c4a663dab370f2e207799bec7e2ad305a98a1ed579e5de6ef263`、`ev_7d2cc9ff3bf8ed3ceea2d91ffb7c2c47ad45337a792f101c1d989272f37023b1`|
|
||||
|
||||
完整REA记录见[证据包](rea-slim-pose.json)。伪代码是Ghidra生成的静态分析结果,寄存器参数与CGRect返回类型部分恢复不完整;此处没有将其当作原始源码或实际执行记录。
|
||||
|
||||
## Metal瘦脸计算
|
||||
|
||||
用本机Apple工具对只读`default.metallib`输出AIR中间代码。它不是字符串猜测:`faceThinFragmentShader`在`faceShapeParamsActive`为真时调用`mssFaceShapePinCoordinate`,只有inactive分支调用旧`mssFaceThinPinCoordinate`。开启瘦脸后第一项脸型参数非零,因此可建立Shape分支的静态路径。原版截图当时具体走哪条宿主渲染分支没有运行捕获。
|
||||
|
||||
Shape helper中的瘦脸部分有17组点操作,每组实际读取origin和anchor,三元表中的第二项在这一循环中未使用。origin均为59–75号脸轮廓点,anchor为6、13、47、48、49、52号点;独立Vision区域对应检查确认,6/13是左右瞳孔,47/48/49/52位于鼻部或鼻梁。不能把所有锚点都叫下颌点或鼻部点。
|
||||
|
||||
单组逆向采样的结构为:
|
||||
|
||||
```text
|
||||
r = distance(origin, anchor)
|
||||
w = clamp(1 - distance(q, origin) / r, 0, 1)
|
||||
q = clamp(q - (anchor - origin) * effectiveStrength * w, 0, 1)
|
||||
```
|
||||
|
||||
17组顺序更新同一个采样坐标q,不能将其视为一次整脸等比缩放。这里的距离在整幅纹理归一化UV中计算;它并非完整按像素各向同性的旋转不变模型。单组在其anchor处权重为零,但其他组仍可能影响该处,不能说整个鼻部或所有五官被锁死。
|
||||
|
||||
有效强度另含尺寸补偿:`k=clamp(280/max(轮廓两端像素距离, 下巴到上侧轮廓中点的像素距离×2, 1), 1, 2.25)`,单pin系数约为`滑块×0.025×k×渲染强度`。它对小脸加大效果,**不是侧脸质量衰减**。最大单组系数0.05625也不是最终整脸缩窄百分比。
|
||||
|
||||
AIR结构摘要、全部17组索引、尺寸公式、输出摘要和复现命令在[机器可读报告](slim-pose-report.json)。完整AIR文本留在本地`build/pose-analysis/face-thin.air.txt`,未把原版metallib或原版程序加入产品或仓库。
|
||||
|
||||
## 为什么转脸时更自然
|
||||
|
||||
已证实的机制是:轮廓投影变化→局部origin、anchor间向量变化→各处的收缩方向、半径随之变化;并非先证明“人物转头90度”,再换一套固定对称瘦脸公式。另有上一轮已分析的时间点阵稳定、有效性检查和失检参考保持,这些可减少跳动;它们与瘦脸位移模型是两个层面。
|
||||
|
||||
本次检查的GPU瘦脸分支没有yaw/roll/pitch输入、左右脸对称比门限、侧脸强度开关或末端五官恢复蒙版。原生函数名检索也未发现`objc_stub::yaw/roll/pitch` getter;这是有界负面结果,不能据此宣称整个APP绝无任何姿态相关逻辑。其他脸型项仍有固定画面X方向位移,不能把瘦脸的局部点特性扩张成所有控制都具有相同的转头适应性。
|
||||
|
||||
## 同一素材的独立验证
|
||||
|
||||
运行独立的[离线检测工具](../../scripts/inspect-face-geometry.m),从用户视频在0、2、4、6、8、10、12、14、16、18、20秒各取一帧。请求使用Vision revision 3 / 76点,图像最长边1280。全部11帧均返回1张脸、76点;4秒和18秒的Vision yaw约为π/2,pitch均不可用。
|
||||
|
||||
这证明明显侧脸仍可以获得完整二维点阵,也校验了59–75号点确为轮廓、6/13号为瞳孔、47/48/49/52号为鼻部相关点。yaw为检测器返回值,不能当作实测头部角度。工具没有调用或执行原版滤镜,不是原版宿主像素等价测试;本机Vision版本、检测尺寸与宿主缓存可能使点坐标与原版当帧不同。Vision本身报告yaw,也不能说明原版瘦脸实际使用该属性。
|
||||
|
||||
用户素材、截图、导出的帧及完整人脸坐标均只保留在被忽略的本地build目录,没有上传远程仓库。入库的报告仅含汇总计数、区域索引和角度信息。
|
||||
|
||||
工具复现:
|
||||
|
||||
```sh
|
||||
xcrun clang -fobjc-arc -Wall -Wextra \
|
||||
-framework Foundation -framework AVFoundation -framework Vision \
|
||||
-framework ImageIO -framework CoreGraphics -framework CoreMedia \
|
||||
scripts/inspect-face-geometry.m -o /tmp/inspect-face-geometry
|
||||
/tmp/inspect-face-geometry '<用户视频路径>' '<本地输出目录>/geometry.json' 0 2 4 6 8 10 12 14 16 18 20
|
||||
```
|
||||
|
||||
## 当前实现差异与修复落点
|
||||
|
||||
`PGFaceProcessor.m:290–305`的瘦脸只用框中心/宽高,按画面水平轴同时处理左右;`warpImage:geometry:settings:extent:`额外读取的双眼仅用于大眼。已有`contour/nose/lips`未参与瘦脸,缺失轮廓时仍会按有效框足额变形。pose信息也没有贯通提取、净化、实时稳定和离线存储。
|
||||
|
||||
正值采样向外、视觉向内收的符号本身正确;简单反号无法修复。当前公式在框高35%处的测试位置将脸框两边缘间距缩小约20.2%,作用域还可延伸到框外。这个局部计算是当前公式的解析检查,不代表与原版截图的实测宽度差。
|
||||
|
||||
后续修正应让瘦脸直接使用稳定后的轮廓和鼻部控制点,按局部方向/半径位移,并校准尺寸补偿和滑块映射。若另加显式姿态或质量衰减,应标记为本项目设计,不能当作已证实的原版算法。完整点阵必须贯通实时与持久化路径,不能只在检测入口新增字段后被后续步骤丢弃。
|
||||
|
||||
现有测试只能证明各几何参数改变像素、范围保持和跟踪恢复一致,尚未验证最大瘦脸外观或侧脸形状。修复验证至少应包括同帧正面/半侧/全侧、向左右转脸、歪头、轮廓缺失、正逆播放、100%瘦脸与多个脸型项叠加,和原版在隔离相同其他参数后的对照。
|
||||
|
||||
本轮完成机制分析及独立检测,未宣称已修复安装版,也未将用户的“跟踪没问题”扩张为其他未覆盖的原生验收通过。
|
||||
+9
-1
@@ -22,7 +22,7 @@ Motion:独立普通视频项目,源PTS0、1920×1080/25fps/10秒,媒体通
|
||||
|
||||
用户给出的MS超级美颜V2.moef共发布15个入口,没有跟踪、前向或后向分析控件。没有额外分析按钮不影响原版默认自动跟随;活跃render路径与Motion运行均已确认这一点。复刻版的三目标离线分析与持久化属于独立扩展,应与默认实时跟随分开理解。
|
||||
|
||||
六种肤色风格采用自写调色变换,脸型使用基于Vision关键点的近似局部反向采样;未复制原版私有LUT或算法资源。没有完成原版逐像素对齐、全部脸型强度主观比对、多人交叉遮挡、所有宿主版本及Intel硬件验收。源码层保存/恢复测试不代替两宿主所有工程关闭重开场景。
|
||||
六种肤色风格采用自写调色变换,脸型当前使用Vision脸框场与眼部点位置的近似反向采样;瘦脸本身没有使用下颌轮廓点。未复制原版私有LUT或算法资源。没有完成原版逐像素对齐、全部脸型强度主观比对、多人交叉遮挡、所有宿主版本及Intel硬件验收。源码层保存/恢复测试不代替两宿主所有工程关闭重开场景。
|
||||
|
||||
## 自动化与恢复
|
||||
|
||||
@@ -41,3 +41,11 @@ Motion:独立普通视频项目,源PTS0、1920×1080/25fps/10秒,媒体通
|
||||
原版完整数学系数未恢复,本版使用自有平滑公式与past-only时间策略;当前检测框未做原版77点网格级稳定,没有真正sourceID,不能声称任意同尺寸换源或首次乱序完全一致。原生1.0.4:Motion成功加载正式路径插件,实际视频在不同播放头位置正常显示提亮、脸型和黄色主要人脸框;项目未执行离线分析,“使用跟踪数据”读取为0。用户随后明确接管Motion/FCP进行自测,自动化已停止,因此尚未完成1.0.4的FCP自动跟随、显式OSC点击回写及两宿主复杂场景验收。FCP黑屏输入链与提亮此前已原生复测,不能把它们混称为1.0.4所有自动跟随场景通过。详见分析说明的具体边界。
|
||||
|
||||
代码节点已从远端独立克隆恢复,51项受跟踪文件与9项必要资源逐字节/对象/文件模式一致;380项检查、严格fsck与Xcode工程读取全部通过,无LFS或子模块。验收报告见 [独立恢复摘要](verification/recovery-e03e460.txt) 与 [完整记录](verification/recovery-e03e460.json)。最终文档标签另作独立克隆核验,源码构建依赖没有因文档更新改变。
|
||||
|
||||
## 用户复测与瘦脸专项分析
|
||||
|
||||
用户反馈“跟踪没问题”,同时提供最大瘦脸与侧脸变形对照。记录为用户实测反馈,不扩张为显式OSC点击、两宿主复杂场景或全部脸型验收通过。
|
||||
|
||||
后续静态REA和Apple Metal AIR分析确认:原版瘦脸以17组轮廓到鼻部/瞳孔锚点的局部位移驱动,当前瘦脸按框水平对称变形,两者的方向、范围与强度模型不同。独立Vision76点离线探针在同一视频11个采样时刻均检出1脸76点,含明显侧脸;它只证明关键点仍可获得,不代替原版宿主输出对照。探针使用Wall/Wextra编译成功并完成采样,没有执行原版APP、替换安装版或操作Motion/FCP。
|
||||
|
||||
分析报告见 [脸型变形与转脸分析](analysis/脸型变形与转脸分析.md)。当前安装1.0.4的变形问题尚未修复,本轮的诊断代码/证据/文档不改变已安装产品。
|
||||
@@ -0,0 +1,109 @@
|
||||
// Offline diagnostic only. It uses Apple's public Vision API, never a host UI
|
||||
// or the reference plug-in. User media and generated observations stay local.
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <Vision/Vision.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#include <math.h>
|
||||
|
||||
static NSArray *PGProbePoint(CGPoint p) { return @[@(p.x), @(p.y)]; }
|
||||
static NSArray *RegionIndices(VNFaceLandmarkRegion2D *region, VNFaceLandmarkRegion2D *all) {
|
||||
NSMutableArray *indices = [NSMutableArray array];
|
||||
for (NSUInteger i=0; i<region.pointCount; i++) {
|
||||
CGPoint p=region.normalizedPoints[i];
|
||||
double best=INFINITY; NSUInteger index=NSNotFound;
|
||||
for (NSUInteger j=0; j<all.pointCount; j++) {
|
||||
CGPoint q=all.normalizedPoints[j];
|
||||
double d=hypot(p.x-q.x,p.y-q.y);
|
||||
if (d<best) { best=d; index=j; }
|
||||
}
|
||||
// Preserve an unmatched result instead of inventing a point identity.
|
||||
[indices addObject:best<1e-6 ? @(index) : NSNull.null];
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) { @autoreleasepool {
|
||||
if (argc<4) {
|
||||
fprintf(stderr,"usage: inspect-face-geometry <video> <output.json> <seconds>...\n");
|
||||
return 2;
|
||||
}
|
||||
NSURL *source=[NSURL fileURLWithPath:@(argv[1])];
|
||||
NSURL *output=[NSURL fileURLWithPath:@(argv[2])];
|
||||
if (![NSFileManager.defaultManager fileExistsAtPath:source.path]) return 2;
|
||||
NSError *error=nil;
|
||||
if (![NSFileManager.defaultManager createDirectoryAtURL:[output URLByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||
fprintf(stderr,"%s\n",error.description.UTF8String); return 1;
|
||||
}
|
||||
AVURLAsset *asset=[AVURLAsset URLAssetWithURL:source options:nil];
|
||||
AVAssetImageGenerator *generator=[[AVAssetImageGenerator alloc] initWithAsset:asset];
|
||||
generator.appliesPreferredTrackTransform=YES;
|
||||
generator.maximumSize=CGSizeMake(1280,1280);
|
||||
generator.requestedTimeToleranceBefore=kCMTimeZero;
|
||||
generator.requestedTimeToleranceAfter=kCMTimeZero;
|
||||
NSMutableArray *frames=[NSMutableArray array];
|
||||
for (int a=3; a<argc; a++) { @autoreleasepool {
|
||||
char *end=NULL; double seconds=strtod(argv[a],&end);
|
||||
if (!end || end==argv[a] || *end || !isfinite(seconds) || seconds<0) return 2;
|
||||
CMTime actual=kCMTimeInvalid;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
CGImageRef image=[generator copyCGImageAtTime:CMTimeMakeWithSeconds(seconds,60000)
|
||||
actualTime:&actual error:&error];
|
||||
#pragma clang diagnostic pop
|
||||
if (!image) { fprintf(stderr,"%s\n",error.description.UTF8String); return 1; }
|
||||
VNDetectFaceLandmarksRequest *request=[VNDetectFaceLandmarksRequest new];
|
||||
if (![VNDetectFaceLandmarksRequest.supportedRevisions containsIndex:VNDetectFaceLandmarksRequestRevision3] ||
|
||||
![VNDetectFaceLandmarksRequest revision:VNDetectFaceLandmarksRequestRevision3
|
||||
supportsConstellation:VNRequestFaceLandmarksConstellation76Points]) {
|
||||
CGImageRelease(image); fprintf(stderr,"Vision revision 3 / 76 points unavailable\n"); return 1;
|
||||
}
|
||||
request.revision=VNDetectFaceLandmarksRequestRevision3;
|
||||
request.constellation=VNRequestFaceLandmarksConstellation76Points;
|
||||
VNImageRequestHandler *handler=[[VNImageRequestHandler alloc] initWithCGImage:image options:@{}];
|
||||
if (![handler performRequests:@[request] error:&error]) {
|
||||
CGImageRelease(image); fprintf(stderr,"%s\n",error.description.UTF8String); return 1;
|
||||
}
|
||||
NSMutableArray *faces=[NSMutableArray array];
|
||||
for (VNFaceObservation *face in request.results) {
|
||||
VNFaceLandmarks2D *lm=face.landmarks;
|
||||
VNFaceLandmarkRegion2D *all=lm.allPoints;
|
||||
CGRect box=face.boundingBox;
|
||||
NSMutableArray *points=[NSMutableArray array];
|
||||
for (NSUInteger i=0; i<all.pointCount; i++) {
|
||||
CGPoint p=all.normalizedPoints[i];
|
||||
[points addObject:PGProbePoint(CGPointMake(box.origin.x+p.x*box.size.width,
|
||||
box.origin.y+p.y*box.size.height))];
|
||||
}
|
||||
NSDictionary *regions=@{@"contour":RegionIndices(lm.faceContour,all),
|
||||
@"leftEye":RegionIndices(lm.leftEye,all), @"rightEye":RegionIndices(lm.rightEye,all),
|
||||
@"leftPupil":RegionIndices(lm.leftPupil,all), @"rightPupil":RegionIndices(lm.rightPupil,all),
|
||||
@"nose":RegionIndices(lm.nose,all), @"noseCrest":RegionIndices(lm.noseCrest,all),
|
||||
@"outerLips":RegionIndices(lm.outerLips,all), @"innerLips":RegionIndices(lm.innerLips,all)};
|
||||
[faces addObject:@{@"box":@[@(box.origin.x),@(box.origin.y),@(box.size.width),@(box.size.height)],
|
||||
@"confidence":@(face.confidence), @"yaw":face.yaw?:NSNull.null,
|
||||
@"roll":face.roll?:NSNull.null, @"pitch":face.pitch?:NSNull.null,
|
||||
@"allPoints":points, @"regions":regions}];
|
||||
}
|
||||
NSString *name=[NSString stringWithFormat:@"frame-%0.3f.png",seconds];
|
||||
NSURL *png=[[output URLByDeletingLastPathComponent] URLByAppendingPathComponent:name];
|
||||
CGImageDestinationRef destination=CGImageDestinationCreateWithURL((__bridge CFURLRef)png,CFSTR("public.png"),1,NULL);
|
||||
if (!destination) { CGImageRelease(image); return 1; }
|
||||
CGImageDestinationAddImage(destination,image,NULL);
|
||||
BOOL saved=CGImageDestinationFinalize(destination); CFRelease(destination);
|
||||
[frames addObject:@{@"requestedSeconds":@(seconds), @"actualSeconds":@(CMTimeGetSeconds(actual)),
|
||||
@"width":@(CGImageGetWidth(image)), @"height":@(CGImageGetHeight(image)),
|
||||
@"image":name, @"faces":faces}];
|
||||
CGImageRelease(image); if (!saved) return 1;
|
||||
}}
|
||||
NSDictionary *result=@{@"schema":@1,@"source":source.path,@"coordinateSystem":@"full-image normalized, y up",
|
||||
@"authority":@"independent Apple Vision probe, not reference plug-in execution",
|
||||
@"requestRevision":@3,@"constellation":@76,@"frames":frames};
|
||||
NSData *json=[NSJSONSerialization dataWithJSONObject:result options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:&error];
|
||||
if (!json || ![json writeToURL:output options:NSDataWritingAtomic error:&error]) {
|
||||
fprintf(stderr,"%s\n",error.description.UTF8String); return 1;
|
||||
}
|
||||
printf("Saved %lu independent frame observations.\n",(unsigned long)frames.count);
|
||||
return 0;
|
||||
}}
|
||||
在新工单中引用
屏蔽一个用户