stroke4 几何探针 — 外部关键点能不能救正反手

★ 新:把动作分解图当参考图喂给模型 → clear acc 0.693→0.792,超过此前所有方案:看这一页
→ 2026-09-22 新规范(持拍手/中线/拍面 重新定义)+ 3D pose 对比:看这一页
⚠ 结论:不能,判据没过。这一页存在的理由是让你自己看一眼几何抽得对不对 —— 整个否定结论都压在 「检测器读出的几何是否可信」上,所以它必须可查验,而不是只给一张汇总表。
注意:下面「符号翻转的真正原因」一节修正了我先前的判断 —— 10.6% 的翻转是我的符号锚点 在双手握拍时失效,不是这条线索本身不可用。融合无增量的结论仍成立,但需要修完锚点再复核一遍。
绿框=球员 · 橙框=球拍 · 白点/黄线=17 关键点 · 红线=肩线 · 粉线=躯干中线。左边是原图,右边是镜像。 镜像不改变网球的正反手标签,所以两边读出的几何本该完全一致。

为什么做这个

p1–p4 四版 prompt 都是让 VLM 自己从像素里读身体和球拍的相对几何,而它读得不稳: 把图水平翻转后 16.6% 的答案会变(标签本不该变)。在这些翻转样本上正反手准确率只有 0.486,等于抛硬币;而在镜像稳定的样本上是 0.872。
所以设想是:用 racketeer + 姿态检测器把这些几何量显式算出来再喂给模型,绕开它不稳的读数。

结果一:几何抽得出来,但关键那一位是噪声

指标值读法
能抽到持拍球员 + 可用关键点86.0%失败 102 例全是 no_holding_player
原图与镜像都抽到84.8%—
关键路由位「球拍是否越过身体中线」符号翻转10.6% 这是 p2/p4 路由表的主轴,也是本方案的主线索
躯干扭转角偏移 >10% 尺度51.6%阶段线索基本不可用

结果二:只用几何比 VLM 还差,而且主线索权重≈0

方法正反手 acc说明
多数类基线0.585—
只用几何(13 维,按摄影师分组 5 折 CV)0.708有信号,但弱
Opus 5 / p4 prompt0.796同一批 368 张
线性权重里 racket_along(球拍是否过身体中线)= −0.009,约等于零。 整个方案建立在这条线索上,而它在检测器读出的几何里没有预测力。 真正带信号的是 racket_area_ratio / wrist_gap / offhand_to_racket —— 也就是「双手握把 / 非持拍手在哪」,恰好是 p4 已经用得很好的那条。

结果三:融合没有增量

特征acc相对 p4 自读
p4 的答案 + 它自己报的证据字段0.881—
+ 全部 13 维几何0.859−0.022
+ 仅镜像稳定的 5 维几何0.876−0.005
oracle(逐张二选一的理论上限)是 +9.1 点,但真实融合拿不到,噪声特征还倒扣。 唯一的亮点:在 p4 镜像翻转的 60 张上,几何 0.517 vs p4 0.467,能救回 15 张 —— 方向对,强度不够穿过融合噪声。

一个已修正的错误结论

过程中我一度报过「用 VLM 自己的证据字段重新路由 → +16 点、救 38 张坏 0 张」。那是泄漏。 重路由器是二分类、只能输出正手/反手,而被测样本里 VLM 有一部分答的是 serve/other —— 4 类口径算错,二分类口径被强行折算成正反手之一,等于白送了「这张一定是正反手」这个信息。
改成同口径 4 类后:0.688 → 0.713(+2.5,救 10 坏 0,p=0.002), 而在无泄漏的正反手子问题上(n=187)是 0.856 → 0.856,救 0 坏 0。 那 +2.5 点全部来自修「是不是底线球」的边界,在正反手决策上一点没动。

怎么判「过中线 / 没过中线」

三步,全在 stroke4_geom_extract.py:geometry() 里:
① 定中线 —— 取左右肩关键点(COCO 5/6)连成红线, 其中点作垂线 = 粉线,即躯干中线。
② 定持拍手 —— 左右手腕(9/10)里离球拍框中心更近的那只。 这一步决定正负号朝哪边:「正」永远指向持拍肩一侧,所以它与画面左右无关,镜像也不该改变它。
③ 投影 —— 把球拍框中心投到肩线方向,量它到粉线的距离,除以肩宽归一化。

读角标:同侧 +1.97 = 球拍在持拍肩那一侧,没过中线; 过线 −2.03 = 越到了另一侧,过中线。数值单位是肩宽, −0.8 就是越过中线约 0.8 个肩宽。阈值是硬性的 0,没有死区。

一张图要看的是:左右两个角标的符号是否一致。镜像不改变网球的正反手, 所以「同侧」在两边都该是「同侧」。符号不一致 = 这张图上这条线索不可信。

符号翻转的真正原因(我上一版的判断是错的)

我原先说翻转说明「这条线索本身模棱两可」。量过之后不是。
 翻转 (33)未翻转 (279)
判定时离中线距离 |值| 中位2.02 肩宽1.97 肩宽
两手腕间距 wrist_gap 中位0.621.46
原图/镜像持拍手位置差 >0.5 肩宽28/33—
翻转样本离中线一点都不近(2 个肩宽),所以不是阈值噪声 —— 死区阈值放到 0.30 仍有 9.4% 翻转。 真正原因在上面的第 ② 步:28/33 的翻转是「换了只手认持拍」。 双手握把时两只手腕挨在一起(wrist_gap 中位 0.62),「离球拍最近的手腕」就是掷硬币, 一旦选反,整套正负号定义整体倒过来。佐证:翻转样本里反手占 61%(全体 42%), 而 wrist_gap<0.6 的样本有 69% 是反手。

这是我实现里的锚点选错了,不是这条线索不可用。可修:改用「球拍手臂挂在哪个肩上」定号 (p2 原本就是这么写的),或把双手握把单独分桶。修完需要重跑一遍探针,才知道 racket_along 权重≈0 这个结论里有多少是我的 bug 造成的。

逐图查验