analysis: diagnose tracking flicker and identify the 2.4.0 pin-style path
这个提交包含在:
1 父节点
b58fec1a86
当前提交
623dcb878b
8 个文件已更改
+5866
没有与搜索条件相匹配的文件
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"scope": "Source and isolated authored synthetic diagnostics plus static REA of the requested 2.4.0 binary; no native host comparison",
|
||||||
|
"sourceBaseline": "b58fec1a86db7a2e474e1cc65a5911447b5906b9",
|
||||||
|
"installedVersion": "1.3.2 (17)",
|
||||||
|
"productSourceUnchanged": true,
|
||||||
|
"selectedTarget": {
|
||||||
|
"path": "/Users/dayu/Downloads/MSE18V2.app",
|
||||||
|
"version": "2.4.0",
|
||||||
|
"build": "20260724150000",
|
||||||
|
"universalPluginSha256": "577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109",
|
||||||
|
"arm64Sha256": "afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df"
|
||||||
|
},
|
||||||
|
"comparisonCaution": "Existing auto-follow-report.json describes the older /Applications target; its temporal weights/history must not be asserted as 2.4.0 default behavior",
|
||||||
|
"evidenceRecordsRetained": 17,
|
||||||
|
"checks": {
|
||||||
|
"FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m": "cca826d5602b45b2c79ea3c3c78be7c78ad1defe7b062ed8ec54c311d53ac395",
|
||||||
|
"FCP-BeautyGirl/FCP-BeautyGirl/Plugin/FCPBeautyGirlPlugIn.m": "9d3d30d1f7f6467c634e8d6e832d2330618b9ff240de37f57de9bc48e2792de8",
|
||||||
|
"scripts/diagnose-tracking.m": "bc758bf5ec4825006930be8997889404f63c28da1fe06ad8872b0901b2795d2c",
|
||||||
|
"docs/analysis/tracking-flicker-20261022/synthetic-results.json": "5fdbad4887dd78658c06f5c6d8e70c9e53fd7b54e11a9cc31ee1958631cbc1a8",
|
||||||
|
"docs/analysis/tracking-flicker-20261022/rea-evidence.json": "4b367759388c8e061e138623184ae8a6875dbab22659eefd4e474b141b8868fa",
|
||||||
|
"scripts/diagnose-tracking.sh": "c1993ca91cee596a21db1e4f86ba224be85c37495c783f9c3b21872aff210765"
|
||||||
|
},
|
||||||
|
"limits": [
|
||||||
|
"No user frames or complete user landmark arrays used or uploaded",
|
||||||
|
"No target process executed or host UI controlled",
|
||||||
|
"Static reachable/default-configuration branches do not prove runtime branch execution",
|
||||||
|
"Same-frame raw cache, hold and out-of-order behavior were isolated using our actual current implementation",
|
||||||
|
"No APP rebuild, install, package or article publication for this analysis",
|
||||||
|
"User clarified current FCP timeline: sun-hat subject, 1080p25, fast multi off, faceThin1, bigEye about0.90; exact source/frame analysis in progress"
|
||||||
|
]
|
||||||
|
}
|
||||||
文件差异过大,未显示。
加载差异
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"authoredTwoFaceCrossing" : {
|
||||||
|
"rawIndex0" : "same authored identity A moved by .10",
|
||||||
|
"rawIndex1" : "different authored identity B moved within .01 of old center",
|
||||||
|
"selectedRawIndex" : 1
|
||||||
|
},
|
||||||
|
"defaultAppearanceOnDetectionLoss" : {
|
||||||
|
"geometryFacesAfter" : 1,
|
||||||
|
"geometryFacesBefore" : 1,
|
||||||
|
"meanColorMaskAfter" : 0,
|
||||||
|
"meanColorMaskBefore" : 0.39522577568114048,
|
||||||
|
"meanSkinMaskAfter" : 0,
|
||||||
|
"meanSkinMaskBefore" : 0.29670316041502076
|
||||||
|
},
|
||||||
|
"landmarkWeightsByFrameRate" : [
|
||||||
|
{
|
||||||
|
"currentLandmarkWeight" : 0.89999999999999925,
|
||||||
|
"fps" : 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"currentLandmarkWeight" : 0.89999999999999925,
|
||||||
|
"fps" : 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"currentLandmarkWeight" : 0.5,
|
||||||
|
"fps" : 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"currentLandmarkWeight" : 0.25,
|
||||||
|
"fps" : 120
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outOfOrderCachedMissingFrame" : {
|
||||||
|
"initial40OutputFaces" : 0,
|
||||||
|
"ordered0Then40OutputFaces" : 1,
|
||||||
|
"replay40AfterAdding0OutputFaces" : 0
|
||||||
|
},
|
||||||
|
"pureBBoxTranslationJitter" : {
|
||||||
|
"fps" : 25,
|
||||||
|
"frames" : 20,
|
||||||
|
"maximumOutputVsRawPointError" : 0,
|
||||||
|
"outputOverRawAmplitude" : 1,
|
||||||
|
"outputStepNormalized" : 0.0080000000000000071,
|
||||||
|
"rawStepNormalized" : 0.0080000000000000071
|
||||||
|
},
|
||||||
|
"reacquisition" : {
|
||||||
|
"continuousPoint69x" : 0.60288473677011323,
|
||||||
|
"rawPoint69x" : 0.60568473677011325,
|
||||||
|
"reacquiredPoint69x" : 0.60568473677011325
|
||||||
|
},
|
||||||
|
"scope" : "Authored synthetic geometry and image only; actual current PGFaceProcessor implementation linked; no target binary or user media.",
|
||||||
|
"topologyValidMissingValid" : {
|
||||||
|
"held" : [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"pixelDifferencesFromUnwarpedInput" : [
|
||||||
|
{
|
||||||
|
"changedChannelsAbove1e_6" : 85835,
|
||||||
|
"maxChannelDifference" : 0.89999997615814209,
|
||||||
|
"meanChannelDifference" : 0.055795646515334418
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"changedChannelsAbove1e_6" : 0,
|
||||||
|
"maxChannelDifference" : 0,
|
||||||
|
"meanChannelDifference" : 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"changedChannelsAbove1e_6" : 85835,
|
||||||
|
"maxChannelDifference" : 0.89999997615814209,
|
||||||
|
"meanChannelDifference" : 0.055795646515334418
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"points76Counts" : [
|
||||||
|
76,
|
||||||
|
0,
|
||||||
|
76
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# 瘦脸跟踪闪烁:1.3.2 与目标 2.4.0 的对齐缺口
|
||||||
|
|
||||||
|
2026-10-22。用户实测同一段素材,本插件瘦脸跟踪闪烁,目标插件稳定。本次分析基线为 `b58fec1a86db7a2e474e1cc65a5911447b5906b9`,已安装版仍为 1.3.2(17)。只做源码审查、隔离合成复现和静态二进制对照,没有修改产品源码、重新编译 APP 或操作宿主。
|
||||||
|
|
||||||
|
主要缺口在跨帧几何的有效性判定、坏帧保持与缓存更新。此前恢复单帧瘦脸位移场,并没有完整对齐新版目标的连续跟踪链。以下区分已经复现的本品行为、目标的静态分支,以及尚未定位到用户素材具体帧的部分。
|
||||||
|
|
||||||
|
## 目标版本与旧分析的边界
|
||||||
|
|
||||||
|
本次目标是用户指定的 `/Users/dayu/Downloads/MSE18V2.app`,版本 **2.4.0 / 20260724150000**。插件通用二进制 SHA-256 为 `577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109`,arm64 切片为 `afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df`。切片逐字节核对过本次目标,REA 使用 Ghidra 12.1.4;未执行目标。
|
||||||
|
|
||||||
|
原有 [auto-follow-report.json](auto-follow-report.json) 分析的是 `/Applications/MSE18V2.app` 的旧版 `1.0.20260617-170517`,通用二进制 SHA 为 `6c5d74bc844a5c5f745718b48ea10ec6635d08aa08a5b6dc6e62653980849f8a`。其中 temporal 混合系数和历史优先级不能直接当成 2.4.0 的默认瘦脸实现。
|
||||||
|
|
||||||
|
新版 `renderDestinationImage`(`0x10002179c`)的单主要人脸几何分支直接调用 **`mss_pinStyleFacePoints…`(`0x10003d69c`)**。后者在筛选当前几何后,选择接纳新点或回用上一组有效点,并可向 `trackedFaceBox…`(`0x10003cc78`)请求连续跟踪框。旧 `temporalFacePoints…` 方法仍存在,但当前提供者没有解析到它的直接调用者;这不能排除任意 Objective-C 间接调用,却足以纠正“只按旧 temporal 方法调平滑系数即可对齐新版”的假设。
|
||||||
|
|
||||||
|
对应 Evidence:render `ev_2330b66132cd6539b8c66313c8dc43ca6ff5eb3e0881abbbe26857f99de6a83b`;pin-style `ev_58dee36fddf4e4c98ce3d880a0bb98190351d1269c960b3ccc275aef81265e8f`;直接调用关系 `ev_568a93f2165e5dc5046e610f47c23e9cebc33f55b5d12d3ee005a1e99a7191f6`、`ev_a8cf6f3aec6fbcf2b698ab8ec52313e20a6a5e0d636bc922c4293e436861db7e`、`ev_7c53c39d245e0063cab89358ab8948cdd734488460464d5f455674bb80a1515a`。
|
||||||
|
|
||||||
|
## 已确定的差异
|
||||||
|
|
||||||
|
| 环节 | 当前 1.3.2 | 目标 2.4.0 的静态证据 | 对闪烁的意义 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 部分关键点失效 | 有合法脸框就接纳;当前缺 76 点时删去完整拓扑,且不标为 held | 接纳前要求足量点、轮廓、有限坐标、有效五官布局和置信度;不合格时可回用上一组有效点 | 当前会在部分失效帧直接关闭瘦脸,目标有完整形状保持路径 |
|
||||||
|
| 框与几何稳定 | 每次重新检测,旧点先映射到当前原始框;没有连续对象框跟踪器 | pin-style 可调用 VNSequenceRequestHandler / VNTrackObjectRequest,坏帧可把旧点映射到可用跟踪框 | 当前框噪声完全进入几何;目标另外维护连续框和上次有效形状 |
|
||||||
|
| 异常姿态与跳点 | 主要判断眼轴翻转;合法有限 76 点一般直接进入混合 | 额外检查眼/鼻/口/下轮廓结构、前后几何兼容及归一化点位跳变的中位数/P90 | 当前会接受点数足够但位置不可靠的帧 |
|
||||||
|
| 同帧检测复用 | 每个请求新建 Vision handler;几何缓存要求 raw 也完全相同才命中 | 人脸检测有按帧时间、输入范围和 tile 范围复用的缓存,并有重试分支 | 当前重复请求可能产生不同 raw 后重新计算;未测得其在用户素材中的发生频率 |
|
||||||
|
| 丢检时的外观支持 | 默认几何可以保持,但皮肤/牙齿/眼睛蒙版仍取 raw 检测 | 本次只完整核对目标的瘦脸主路径,未恢复其全部外观蒙版稳定策略 | 本品已复现几何保持时外观蒙版清零,可能叠加视觉闪烁;不把目标外观路径宣称为已完全对齐 |
|
||||||
|
|
||||||
|
pin-style 的接纳分支复制本帧合格点,保持分支使用旧有效点并尝试框重映射。因此**不能据旧报告把“目标平滑更强”或某个固定混合权重当作这次原因**。应优先恢复它筛掉坏几何、保留有效几何的机制。
|
||||||
|
|
||||||
|
目标跟踪器还有开关与时间管理:静态初始化检查 `MSE18_DISABLE_FACE_THIN_VISION_TRACKER`,通常未设置时启用;已核对初始化汇编的 OR/EOR 写入。跟踪方法包含同帧复用、时间倒退/间隔后的重置、跟踪结果置信度检查和短期保框分支。这里证明默认配置下的可达控制流,没有记录用户宿主内实际环境变量或运行命中次数,不能称为运行验证,也不能仅添加一个 VNTrackObjectRequest 就声称完整复刻。
|
||||||
|
|
||||||
|
## 隔离复现结果
|
||||||
|
|
||||||
|
探针直接链接当前 `PGFaceProcessor.m`,用自造 256×256 图与合成关键点调用实际选择器、变形和蒙版实现。没有使用用户画面、真实人脸点阵或目标运行结果。运行 `zsh scripts/diagnose-tracking.sh` 输出 JSON;它是诊断统计,退出码 0 只表示执行完成。
|
||||||
|
|
||||||
|
1. **76 → 缺 76 → 76,瘦脸中间一帧完全归零。** 三帧一直有输出脸框,点数为 `76/0/76`,held 全为 false。只开瘦脸时,相对原图的变化通道数为 `85835/0/85835`,中间帧像素完全直通。源码入口:`PGFaceProcessor.m` 208–227、308、532–554、645–646。这证明了一个足以造成闪烁的路径;没有测量它在用户视频中的出现率。
|
||||||
|
2. **纯脸框抖动衰减为 0%。** 25fps、20 帧,框的 X 方向交替 ±0.004,框内点保持一致;输入和稳定后的点位步幅均为 0.008,输出/输入振幅比 `1.0`。源码 177–186、196、214–217 的重映射原样继承了框平移。目标对各种轻微框噪声的最终响应尚未做数值对照,不能把此数值直接说成两插件的实测差值。
|
||||||
|
3. **当前点混合在 25/30fps 时采用 90% 新点。** 60fps 为 50%,120fps 为 25%;现有 10ms 用例只检查 30% 新点。掉检后重获还跳过 76 点混合而直接采用 raw。源码 186、211–225、308;现有 `slim_tests.m` 408–410 明确要求重获采用 raw。这解释当前实现为何对扰动敏感,不是目标 2.4.0 权重的证据。
|
||||||
|
4. **空缓存会保留第一次请求顺序的结果。** 先请求 40ms 空帧,再补入 0ms 有效脸,重播 40ms 仍输出 0 脸;按 0→40ms 请求则输出 1 脸。源码 279 的命中直接返回,插入早期帧不会更新后续结果。宿主锁只串行化到达顺序;并发测试只验证安全和重复一致,没有要求不同到达顺序获得相同几何。尚未记录用户宿主实际请求顺序。
|
||||||
|
5. **几何保持不等于美容蒙版保持。** 默认路径掉检时几何脸数仍为 1,colorMask 均值从 `0.3952` 变为 `0`,skinMask 从 `0.2967` 变为 `0`。生产入口强制 singleFace=0,走源码 664–666 的 raw 外观支持;单人 held 外观分支并未在该默认路径使用。
|
||||||
|
6. **多人仍有额外边界。** 默认关联只按防跳区域内距上一框中心最近者选脸;自造交叉可选到陌生人。fastMulti 直接返回 raw,不做当前单主要人脸的保持。此项没有证明用户单人素材发生了换脸,修复时应单列验收。
|
||||||
|
|
||||||
|
原 1901 项回归主要证明单帧图像、几何和接口行为。已有跟随测试检查完全空检测、10ms 扰动与缓存重复安全,没有覆盖“76 点短暂不完整”的连续帧,也没有以常用帧率约束抖动幅度。此前每两秒取一帧、共 11 帧的真实视频检查同样不能替代连续序列稳定性验收。
|
||||||
|
|
||||||
|
## 建议修复顺序与验证要求
|
||||||
|
|
||||||
|
- **先处理部分失效和异常几何。** 在接纳新主脸前检查完整拓扑与五官布局;不合格时进入上一组有效形状的保持/重映射路径。明确区分有效检测时间、输出时间和保持状态,避免仅靠刷新输出时间无限保持。
|
||||||
|
- **再对齐连续框、重获和时间管理。** 结合新版默认可达跟踪链处理同帧、倒退、跳时、正常运动及重获;从转头、遮挡恢复检查形状是否突然跳回 raw。先逐条核对机制,再决定系数,避免用增加滞后来掩盖坏点。
|
||||||
|
- **补齐缓存和外观同步。** 明确同帧结果与新增前帧历史的失效关系、源切换隔离及默认蒙版跟随;以顺序/倒序/重复/多线程请求对照输出。
|
||||||
|
- **用同一段素材做连续帧验收。** 锁定宿主、原始帧率、预览/导出条件和相同单项参数;记录检测有效性、保持原因、框/点扰动与效果开关变化,和目标逐帧对照。单张图正常、自动用例通过都不足以宣布闪烁修复。
|
||||||
|
|
||||||
|
用户随后指定当前时间线的草帽人物素材,已在 Final Cut Pro 确认项目为1080p25、多人模式关闭、瘦脸1.00,且大眼约0.90;正在对源素材做连续帧核对。现有结论已定位实现缺口与可复现机制,尚未将某一种机制认定为该素材闪烁的唯一原因。本次不构建新 APP,不更新安装包或文章。
|
||||||
|
|
||||||
|
## 保留资料
|
||||||
|
|
||||||
|
- [诊断结果](tracking-flicker-20261022/synthetic-results.json)
|
||||||
|
- [目标版本、源码指纹和范围](tracking-flicker-20261022/manifest.json)
|
||||||
|
- [选取的完整 REA Evidence 记录](tracking-flicker-20261022/rea-evidence.json),含原始/规范化结果与提供者限制;为互操作研究资料,不包括目标 APP 或 metallib。
|
||||||
|
- 探针源码 `scripts/diagnose-tracking.m` 与入口 `scripts/diagnose-tracking.sh`。构建依赖与项目一致,需 macOS 的 Core Image/Vision;生成文件位于忽略的 build 下。
|
||||||
|
|
||||||
|
Ghidra 对部分 CGRect、CMTime 和返回寄存器的恢复不完整。报告依据渲染调用关系、完整点门槛、接纳/保持内存分支及初始化汇编作结构判断;未声称所有阈值布尔表达式与目标逐条等价。REA 会话已保存并关闭,证据可离线复查。
|
||||||
+4
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
1.2.0 交接整理前,工作区干净,`main`、`origin/main` 与 `github/main` 均为 `189a5d7634aa69db54330c90f8711b05d8e44b10`。这是文档新增前的基线;最新文档节点以 `git log -1` 和远端引用为准。
|
1.2.0 交接整理前,工作区干净,`main`、`origin/main` 与 `github/main` 均为 `189a5d7634aa69db54330c90f8711b05d8e44b10`。这是文档新增前的基线;最新文档节点以 `git log -1` 和远端引用为准。
|
||||||
|
|
||||||
|
## 最新待修问题:瘦脸跟踪闪烁(2026-10-22)
|
||||||
|
|
||||||
|
用户实测同一段素材,本插件有闪烁、目标稳定。本次完成源码/合成诊断和 Downloads 2.4.0 的静态复核,未修改产品源码或安装版。新版单主要人脸的渲染路径为 pin-style 质量筛选、条件连续框跟踪和坏帧保持;原 auto-follow-report 的旧 Applications 版本 temporal 系数不能直接沿用。已复现部分76点缺失导致瘦脸归零、框抖动无衰减、首次请求空帧缓存不随补入前帧修正,以及几何保持时默认外观蒙版清零。用户已确认当前FCP时间线的草帽人物素材(1080p25、瘦脸1、大眼约0.9、多人关闭),具体连续帧触发率和宿主实际请求序列尚未测量,不宣称已修复。见[跟踪闪烁对齐分析](analysis/跟踪闪烁对齐分析.md),复现入口 `zsh scripts/diagnose-tracking.sh`;这次不是新发行版,无需重发安装包或文章。
|
||||||
|
|
||||||
## 先读和先查
|
## 先读和先查
|
||||||
|
|
||||||
接手时先查看 `git status --short --branch`、`git remote -v` 和最新提交,确认没有其他聊天的未提交改动。只读核对正式 APP 版本与模板目录,再决定是否需要编译或操作宿主。单纯修改文档不需要重建、重新公证或打开 Motion/FCP。
|
接手时先查看 `git status --short --branch`、`git remote -v` 和最新提交,确认没有其他聊天的未提交改动。只读核对正式 APP 版本与模板目录,再决定是否需要编译或操作宿主。单纯修改文档不需要重建、重新公证或打开 Motion/FCP。
|
||||||
|
|||||||
+7
@@ -159,3 +159,10 @@ PKG由本机PKG工坊CLI从正式APP与用户指定已安装的「大鱼磨皮
|
|||||||
PKG 工坊 CLI 基础包包含指定已安装的「大鱼磨皮瘦脸」分类四项文件;加入本产品身份迁移/回滚段后重新签名、公证 Accepted `4a831775-2317-4372-a4cc-102415657ea3` 并粘附票据。最终包 4,889,706 bytes,SHA256 `20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1`。有效载荷与正式 APP、四项模板一致,模板锁定、更新和失败回滚隔离检查通过;未实际执行本 PKG 安装器。
|
PKG 工坊 CLI 基础包包含指定已安装的「大鱼磨皮瘦脸」分类四项文件;加入本产品身份迁移/回滚段后重新签名、公证 Accepted `4a831775-2317-4372-a4cc-102415657ea3` 并粘附票据。最终包 4,889,706 bytes,SHA256 `20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1`。有效载荷与正式 APP、四项模板一致,模板锁定、更新和失败回滚隔离检查通过;未实际执行本 PKG 安装器。
|
||||||
|
|
||||||
origin/github 的 main、工作分支和 v1.3.2 标签已推送并回读。匿名独立 GitHub 克隆核对 271 个文件字节/blob/执行位,strict/full fsck、Xcode 工程读取和恢复出的 PKG 签名/票据/Gatekeeper 通过,无 LFS/子模块。GitHub、大鱼 Release 和文章 15593 已更新;GitHub 与文章的包/源码共四项匿名下载哈希一致,正常及新请求前台的版本、安装命令和三项 SEO 核验通过。作者、封面、分类及免费规则保留,未下订单。大鱼 Release 仅回读附件名称和大小,保留登录网关边界。完整回执在 verification/v1.3.2。
|
origin/github 的 main、工作分支和 v1.3.2 标签已推送并回读。匿名独立 GitHub 克隆核对 271 个文件字节/blob/执行位,strict/full fsck、Xcode 工程读取和恢复出的 PKG 签名/票据/Gatekeeper 通过,无 LFS/子模块。GitHub、大鱼 Release 和文章 15593 已更新;GitHub 与文章的包/源码共四项匿名下载哈希一致,正常及新请求前台的版本、安装命令和三项 SEO 核验通过。作者、封面、分类及免费规则保留,未下订单。大鱼 Release 仅回读附件名称和大小,保留登录网关边界。完整回执在 verification/v1.3.2。
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.2 跟踪闪烁分析(2026-10-22,尚未修复)
|
||||||
|
|
||||||
|
用户报告目标稳定而本插件闪烁。新目标2.4.0的静态REA复核发现瘦脸单主要人脸改走 pin-style 筛选与保持,条件可达连续对象框跟踪。现有旧版自动跟随报告不能作为该新版主路径完全对齐的依据。直接链接当前产品源码的合成探针确认:76点完整/缺失/完整使变形通道85835/0/85835;纯框抖动输出/输入振幅比1;先请求40ms空帧、再补0ms有效脸后,缓存40ms仍空,与顺序请求不同;默认外观蒙版在几何保持时可清零。这些均为合成诊断,不是用户素材或目标运行统计。
|
||||||
|
|
||||||
|
见 analysis/跟踪闪烁对齐分析.md 和同目录 tracking-flicker-20261022;诊断入口 `zsh scripts/diagnose-tracking.sh`。产品源码与正式1.3.2保持相同,未构建APP、未安装、未重发PKG或文章。用户随后确认当前FCP草帽人物素材,已定位源文件,连续帧核对进行中;原1901项回归不代表连续跟踪稳定性已通过。
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreImage/CoreImage.h>
|
||||||
|
#import "PGFaceProcessor.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
@interface PGRealtimeFaceTracker : NSObject
|
||||||
|
- (NSDictionary *)resultForGeometry:(NSArray *)raw settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent;
|
||||||
|
@end
|
||||||
|
@interface PGFaceProcessor (ProbeWarp)
|
||||||
|
- (CIImage *)warpImage:(CIImage *)image geometry:(NSArray *)geometry settings:(PGBeautySettings)s extent:(CGRect)extent;
|
||||||
|
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)s error:(NSError **)error;
|
||||||
|
@end
|
||||||
|
@interface ProbeFaceProcessor : PGFaceProcessor
|
||||||
|
@property (nonatomic,copy) NSArray<VNFaceObservation *> *authoredDetections;
|
||||||
|
@end
|
||||||
|
@implementation ProbeFaceProcessor
|
||||||
|
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)s error:(NSError **)error {return self.authoredDetections?:@[];}
|
||||||
|
@end
|
||||||
|
|
||||||
|
static NSDictionary *Geometry(CGRect box, double localJitter, BOOL has76) {
|
||||||
|
NSMutableDictionary *g=[@{@"box":@[@(box.origin.x),@(box.origin.y),@(box.size.width),@(box.size.height)]} mutableCopy];
|
||||||
|
for(NSString *name in @[@"leftEye",@"rightEye",@"leftBrow",@"rightBrow",@"nose",@"lips",@"mouth",@"contour"]){
|
||||||
|
double x=[name isEqual:@"rightEye"]?.7:.3;
|
||||||
|
g[name]=@[@[@(box.origin.x+(x+localJitter)*box.size.width),@(box.origin.y+.65*box.size.height)]];
|
||||||
|
}
|
||||||
|
if(has76){
|
||||||
|
NSMutableArray *p=[NSMutableArray new];
|
||||||
|
for(NSUInteger i=0;i<76;i++)[p addObject:@[@.5,@.5]];
|
||||||
|
p[6]=@[@.34,@.65];p[13]=@[@.66,@.65];
|
||||||
|
p[46]=@[@.5,@.68];p[47]=@[@.5,@.60];p[48]=@[@.5,@.52];p[49]=@[@.5,@.45];p[52]=@[@.5,@.42];
|
||||||
|
p[29]=@[@.5,@.39];p[36]=@[@.54,@.36];p[37]=@[@.5,@.35];p[38]=@[@.46,@.36];
|
||||||
|
p[26]=@[@.42,@.40];p[34]=@[@.58,@.40];p[35]=@[@.57,@.36];p[39]=@[@.43,@.36];
|
||||||
|
for(NSUInteger i=0;i<17;i++){double t=M_PI+M_PI*i/16;p[59+i]=@[@(.5+.29*cos(t)),@(.65+.43*sin(t))];}
|
||||||
|
for(NSUInteger i=0;i<76;i++)p[i]=@[@(box.origin.x+([p[i][0] doubleValue]+localJitter)*box.size.width),@(box.origin.y+[p[i][1] doubleValue]*box.size.height)];
|
||||||
|
g[@"points76"]=p;
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
static NSDictionary *Live(PGRealtimeFaceTracker *t,NSArray *g,PGBeautySettings s,int64_t tick){return[t resultForGeometry:g settings:s time:CMTimeMake(tick,1000) extent:CGRectMake(0,0,256,256)];}
|
||||||
|
static double PointX(NSDictionary *r){return[r[@"output"][0][@"points76"][69][0] doubleValue];}
|
||||||
|
static NSData *Pixels(CIContext *context,CIImage *image){
|
||||||
|
CGRect e=image.extent;NSUInteger w=e.size.width,h=e.size.height;NSMutableData *d=[NSMutableData dataWithLength:w*h*4*sizeof(float)];
|
||||||
|
[context render:image toBitmap:d.mutableBytes rowBytes:w*4*sizeof(float) bounds:e format:kCIFormatRGBAf colorSpace:NULL];return d;
|
||||||
|
}
|
||||||
|
static NSDictionary *Difference(NSData *a,NSData *b){
|
||||||
|
const float *x=a.bytes,*y=b.bytes;NSUInteger n=a.length/sizeof(float),changed=0;double maximum=0,sum=0;
|
||||||
|
for(NSUInteger i=0;i<n;i++){double v=fabs(x[i]-y[i]);sum+=v;maximum=fmax(maximum,v);if(v>1e-6)changed++;}
|
||||||
|
return@{@"maxChannelDifference":@(maximum),@"meanChannelDifference":@(sum/n),@"changedChannelsAbove1e_6":@(changed)};
|
||||||
|
}
|
||||||
|
static double MeanRed(CIContext *context,CIImage *image){NSData *d=Pixels(context,image);const float *p=d.bytes;NSUInteger n=d.length/(4*sizeof(float));double sum=0;for(NSUInteger i=0;i<n;i++)sum+=p[4*i];return sum/n;}
|
||||||
|
static CIImage *SyntheticPattern(void){
|
||||||
|
NSMutableData *d=[NSMutableData dataWithLength:256*256*4*sizeof(float)];float *p=d.mutableBytes;
|
||||||
|
for(NSUInteger y=0;y<256;y++)for(NSUInteger x=0;x<256;x++){NSUInteger i=4*(y*256+x);p[i]=x/255.;p[i+1]=y/255.;p[i+2]=((x/5+y/5)%2)*.9+.05;p[i+3]=1;}
|
||||||
|
return[CIImage imageWithBitmapData:d bytesPerRow:256*4*sizeof(float) size:CGSizeMake(256,256) format:kCIFormatRGBAf colorSpace:NULL];
|
||||||
|
}
|
||||||
|
int main(void){@autoreleasepool{
|
||||||
|
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;
|
||||||
|
CGRect b=CGRectMake(.15,.15,.7,.65);NSDictionary *good=Geometry(b,0,YES),*missing=Geometry(b,0,NO);
|
||||||
|
PGRealtimeFaceTracker *topology=[PGRealtimeFaceTracker new];NSMutableArray *counts=[NSMutableArray new],*held=[NSMutableArray new],*diffs=[NSMutableArray new];
|
||||||
|
PGFaceProcessor *processor=[PGFaceProcessor new];CIContext *context=[CIContext contextWithOptions:nil];CIImage *image=SyntheticPattern();NSData *original=Pixels(context,image);
|
||||||
|
NSArray *inputs=@[@[good],@[missing],@[good]];
|
||||||
|
for(NSUInteger i=0;i<inputs.count;i++){
|
||||||
|
NSDictionary *r=Live(topology,inputs[i],s,i*40);NSArray *output=r[@"output"];
|
||||||
|
[counts addObject:@([output[0][@"points76"] count])];[held addObject:r[@"held"]];
|
||||||
|
CIImage *warped=[processor warpImage:image geometry:output settings:s extent:image.extent];
|
||||||
|
[diffs addObject:Difference(original,Pixels(context,warped))];
|
||||||
|
}
|
||||||
|
NSMutableArray *rawX=[NSMutableArray new],*outX=[NSMutableArray new];PGRealtimeFaceTracker *jitter=[PGRealtimeFaceTracker new];
|
||||||
|
for(NSUInteger i=0;i<20;i++){
|
||||||
|
double dx=(i%2?.004:-.004);CGRect moved=b;moved.origin.x+=dx;NSDictionary *g=Geometry(moved,0,YES),*r=Live(jitter,@[g],s,i*40);
|
||||||
|
[rawX addObject:g[@"points76"][69][0]];[outX addObject:@(PointX(r))];
|
||||||
|
}
|
||||||
|
double rawAmplitude=0,outAmplitude=0,maxError=0;for(NSUInteger i=1;i<rawX.count;i++){rawAmplitude=fmax(rawAmplitude,fabs([rawX[i] doubleValue]-[rawX[i-1] doubleValue]));outAmplitude=fmax(outAmplitude,fabs([outX[i] doubleValue]-[outX[i-1] doubleValue]));maxError=fmax(maxError,fabs([rawX[i] doubleValue]-[outX[i] doubleValue]));}
|
||||||
|
PGRealtimeFaceTracker *late=[PGRealtimeFaceTracker new];NSDictionary *lateFirst=Live(late,@[],s,40);Live(late,@[good],s,0);NSDictionary *lateReplay=Live(late,@[],s,40);
|
||||||
|
PGRealtimeFaceTracker *ordered=[PGRealtimeFaceTracker new];Live(ordered,@[good],s,0);NSDictionary *ordered40=Live(ordered,@[],s,40);
|
||||||
|
PGRealtimeFaceTracker *reacquired=[PGRealtimeFaceTracker new];Live(reacquired,@[good],s,0);Live(reacquired,@[],s,40);NSDictionary *shift=Geometry(b,.04,YES),*r80=Live(reacquired,@[shift],s,80);
|
||||||
|
PGRealtimeFaceTracker *continuous=[PGRealtimeFaceTracker new];Live(continuous,@[good],s,0);NSDictionary *continuous80=Live(continuous,@[shift],s,80);
|
||||||
|
NSMutableArray *weights=[NSMutableArray new];for(NSNumber *fps in @[@25,@30,@60,@120]){
|
||||||
|
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];Live(t,@[good],s,0);
|
||||||
|
NSDictionary *r=[t resultForGeometry:@[shift] settings:s time:CMTimeMake(1,fps.intValue) extent:CGRectMake(0,0,256,256)];
|
||||||
|
double before=[good[@"points76"][69][0] doubleValue],after=[shift[@"points76"][69][0] doubleValue];
|
||||||
|
[weights addObject:@{@"fps":fps,@"currentLandmarkWeight":@((PointX(r)-before)/(after-before))}];
|
||||||
|
}
|
||||||
|
ProbeFaceProcessor *drop=[ProbeFaceProcessor new];PGBeautySettings defaultSettings=PGBeautyDefaults();
|
||||||
|
drop.authoredDetections=@[[VNFaceObservation faceObservationWithRequestRevision:VNDetectFaceLandmarksRequestRevision3 boundingBox:b roll:nil yaw:nil pitch:nil]];
|
||||||
|
PGFaceFrame *detected=[drop processImage:image settings:defaultSettings time:kCMTimeZero error:nil];
|
||||||
|
drop.authoredDetections=@[];PGFaceFrame *lost=[drop processImage:image settings:defaultSettings time:CMTimeMake(1,25) error:nil];
|
||||||
|
PGRealtimeFaceTracker *crossing=[PGRealtimeFaceTracker new];NSDictionary *initialA=Geometry(CGRectMake(.2,.25,.25,.5),0,YES);
|
||||||
|
Live(crossing,@[initialA],s,0);NSDictionary *movingA=Geometry(CGRectMake(.30,.25,.25,.5),.04,YES),*strangerB=Geometry(CGRectMake(.21,.25,.25,.5),-.04,YES);
|
||||||
|
NSDictionary *crossed=Live(crossing,@[movingA,strangerB],s,40);
|
||||||
|
NSDictionary *report=@{@"scope":@"Authored synthetic geometry and image only; actual current PGFaceProcessor implementation linked; no target binary or user media.",
|
||||||
|
@"topologyValidMissingValid":@{@"points76Counts":counts,@"held":held,@"pixelDifferencesFromUnwarpedInput":diffs},
|
||||||
|
@"pureBBoxTranslationJitter":@{@"frames":@20,@"fps":@25,@"rawStepNormalized":@(rawAmplitude),@"outputStepNormalized":@(outAmplitude),@"outputOverRawAmplitude":@(outAmplitude/rawAmplitude),@"maximumOutputVsRawPointError":@(maxError)},
|
||||||
|
@"outOfOrderCachedMissingFrame":@{@"initial40OutputFaces":@([lateFirst[@"output"] count]),@"replay40AfterAdding0OutputFaces":@([lateReplay[@"output"] count]),@"ordered0Then40OutputFaces":@([ordered40[@"output"] count])},
|
||||||
|
@"reacquisition":@{@"reacquiredPoint69x":@(PointX(r80)),@"rawPoint69x":shift[@"points76"][69][0],@"continuousPoint69x":@(PointX(continuous80))},
|
||||||
|
@"defaultAppearanceOnDetectionLoss":@{@"geometryFacesBefore":@(detected.deformationGeometry.count),@"geometryFacesAfter":@(lost.deformationGeometry.count),@"meanColorMaskBefore":@(MeanRed(context,detected.colorMask)),@"meanColorMaskAfter":@(MeanRed(context,lost.colorMask)),@"meanSkinMaskBefore":@(MeanRed(context,detected.skinMask)),@"meanSkinMaskAfter":@(MeanRed(context,lost.skinMask))},
|
||||||
|
@"authoredTwoFaceCrossing":@{@"rawIndex0":@"same authored identity A moved by .10",@"rawIndex1":@"different authored identity B moved within .01 of old center",@"selectedRawIndex":crossed[@"index"]},
|
||||||
|
@"landmarkWeightsByFrameRate":weights};
|
||||||
|
NSData *json=[NSJSONSerialization dataWithJSONObject:report options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:nil];fwrite(json.bytes,1,json.length,stdout);fputc('\n',stdout);
|
||||||
|
}return 0;}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
# Diagnostic statistics for authored inputs; a zero exit is not a stability pass.
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
WORK="$ROOT/build/tracking-diagnostics"
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
xcrun clang -fobjc-arc -fmodules -Wall -Wextra -Wno-unused-parameter \
|
||||||
|
-DCI_SILENCE_GL_DEPRECATION -mmacosx-version-min=13.0 \
|
||||||
|
-I "$ROOT/FCP-BeautyGirl/FCP-BeautyGirl/Plugin" \
|
||||||
|
-framework Foundation -framework CoreImage -framework CoreGraphics \
|
||||||
|
-framework CoreMedia -framework Metal -framework Vision -framework ImageIO \
|
||||||
|
"$ROOT/FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m" \
|
||||||
|
"$ROOT/scripts/diagnose-tracking.m" -o "$WORK/tracking-probe"
|
||||||
|
"$WORK/tracking-probe"
|
||||||
在新工单中引用
屏蔽一个用户