Files
orisys-app/docs/architecture/studio-finger-visual-feedback-plan.md
T
2026-09-20 21:15:39 +08:00

160 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Finger 显示一致性与低噪声读数实施方案
日期:2026-09-16。检查基线:orisys-app 07d7c3b。本文是实施计划,未修改运行代码;未连接实机,未验证目标 ARM 图形驱动。文中新增接口均为建议契约。
## 1. 决策
保留 Studio 的 QOpenGLWidget/PyOpenGL,移植旧示例的视觉计算和交互:深度热图、表面光流箭头、黄色接触法向箭头、相机和灯光预设。旧 Open3D 界面保留作对照工具。先证明同一输入能产生相同的接触区域、箭头和色标,再优化画质。
旧效果的主要实现不在 finger_pipeline_3dview.py 入口,而在 examples/viz/contact_arrow.py、flow_arrow.py、offscreen_mesh.py,以及 src/orisys/finger/mesh.py。几何计算大部分使用 NumPy;Open3D 部分负责网格对象、渲染和窗口。因此无需把整个旧 UI 移入 Studio。
如近期必须恢复桌面演示,可采用单独可选的 Open3D 进程,通过有界最新帧通道返回 RGB,Qt 显示图像并转发相机操作。旧 OffscreenFingerMeshRenderer 已有 RGB 输出接口,但还要处理进程生命周期、帧身份、超时、复制成本和平台支持。这是备选过渡方案,不作为默认 ARM 发布依赖;不要直接跨平台嵌入原生窗口句柄。
Open3D 官方 ARM 支持仍标为实验性,Linux OpenGL ES 的 Visualizer/GUI 不支持。现代渲染路径还要求 OpenGL 4.1;此要求不可无差别套到 legacy Visualizer。Jetson Orin Nano、QCS6490 都要按实际 Ubuntu/BSP/桌面环境分别验证,不能只依据 CPU 架构推断可用性。
参考:https://www.open3d.org/docs/release/arm.html ,https://www.open3d.org/docs/release/tutorial/visualization/cpu_rendering.html
## 2. 已确认的差异与待验证事项
|问题|代码证据|处理|
|---|---|---|
|箭头响应而三维不明显|scene3d/adapters.py 的 FingerFrameAdapter 默认 depth;二维 arrows.py 使用 flow,Finger 阈值 0.4、间距 10。旧二维间距 20、阈值也为 0.4|不能认定灵敏度提高来自阈值降低;先比较相同数据、网格密度、显示缩放和算法配置|
|三维看起来不动|当前 adapter 明确不输出 displacement;只原位着色|区分颜色、光流箭头与几何形变。先恢复前两者;几何形变另以“相对形变示意”实现|
|旧热图对比明显|SDK scalar_map_vertex_colors 对活动顶点采用每帧 P5/P95 范围|增加探索用稳健自动范围,以及比较用固定范围;始终显示实际范围|
|黄色箭头消失|旧 ContactArrowOverlay.compute 使用 centroid、局部法向、fnormal;当前 SdkPacket 没有接触中心字段|补接触观测契约和渲染叠加层|
|静止数字跳动|Finger sensor.json 默认中值窗口 1、低通 0;旧 CLI 默认曲线低通 5 Hz;当前数字 raw 一位、N 三位小数|先核对运行时覆盖设置,再比较滤波;未标定不是噪声的充分解释|
|纯色背景不是手指形状|Studio draw_arrow_display 将整个矩形填同色;旧 _arrow_background 用 valid_mask 区分内外颜色|用有效 UV 掩膜绘制轮廓,箭头使用同一坐标域|
|可能不同帧/过期字段|三维已有字段有效性与 frame_id 检查;AppWindow._render_run_image 构造 ImageFrame 未传入 flow_valid/depth_valid 等信息|二维补同等有效性;记录两视图呈现帧号,区分延迟与量不同|
没有硬件录制输入,尚不能确定“深度无响应”是深度算法输出小、色标压缩、场失效或呈现延迟。检查 process_chain:Studio 使用 check_motion=False;旧 pipeline 使用 check_motion=True, threshold=0。对照实验要固定这类参数,不能把算法差异归因于渲染库。
## 3. 数据流与模块边界
```text
SDK 每帧计算
→ SdkPacket(测量值、UV 域、字段有效性、接触中心、帧身份)
├─ 现有完整记录通道
├─ 现有全程力历史
└─ Finger 展示构建器(有界最新帧通道)
→ 同帧 VisualBundle
├─ 二维背景/箭头
├─ 三维标量/箭头几何 → GL
└─ 接触状态与诊断
力历史 → ForcePresentation → 曲线显示副本/数字卡
```
测量、展示政策、渲染后端单向依赖。Qt/GL 不进入 SDK;SDK 不导入 examples;shared 不导入 Studio。旧 NumPy 几何函数提取到正式模块并让旧示例复用,禁止 Studio 通过 sys.path 导入 examples。
先扩展现有 contracts、adapters、composer,不重建第二套设备/会话/回放系统。新增模块按责任合并为 visual_policy.py、overlays.py、force/presentation.py;只有实时同步需要时再增加 visual_presenter.py。文件数不是主要指标,修改阈值不应要求同时修改 worker、GL shader 和 app_window。
### 帧和有效性
复用 FrameIdentity,传播 source/session/generation/frame/time。新增字段接入已有数据类时使用可选值和兼容默认值;旧录制缺少接触信息时不伪造黄色箭头。
- `ContactObservation`: `centroid_uv_xy: tuple[float,float] | None`、`found: bool`、`valid: bool`、`frame_id: int`、`domain_id: str`。中心单位是 UV 像素,顺序 x,y。
- `VisualBundle`: identity、image_frame、surface_frame、contact、overlay_instances、policy_revision。每个数组须是不可变快照或有明确所有权;不能引用 SDK 下一帧会覆写的缓冲。
- `ArrowInstances`: origins[M,3]、directions[M,3]、lengths[M]、colors[M,4]、kind。位置与长度在 scene 的统一坐标单位下,方向归一化,数组有限且 M 有上限。
- 字段无效/过期只隐藏对应层;有深度无力时仍可显示深度,没有有效 FN 时隐藏法向强弱箭头。无效不等于 0。
默认实时保持低延迟,不让三维阻塞采集/记录。增加“同步比较”模式:同一 bundle 的二维和三维均准备完成后,在同一个 GUI 更新周期提交;保留至多一个工作中任务和一个最新等待帧,整体跳过中间展示帧。曲线保存全量,比较游标和数字取 bundle 对应时间。不能宣称不同 QWidget 在物理显示扫描上严格原子同步。
## 4. 阈值与空间一致性
不要设置一个数值同时作用于 flow(px)、depth_response(相对值)、force(raw/N)。统一的是配置入口、帧身份、空间域和视觉规则。
`VisualPolicy` 含 flow_on_px/flow_off_px、depth_on/depth_off、色标策略、箭头网格间距、长度上限、接触箭头样式。on > off 构成迟滞。数值由无载样本及轻触检出实验确定,不照搬不同设备的参数。
三维光流箭头与二维光流箭头共享同一 flow 阈值和采样点。深度色彩保留自己的有效区及阈值;轻微切向滑动可能只有光流响应,不应为了“同步”伪造深度。也不应以深度掩膜裁掉所有切向响应。
几何有效区、纹理有效区、接触区分开:
- geometry_mask:UV 映射有效且对应真实传感表面。
- texture_roi:确实需要排除无纹理边缘时,离线指定/人工确认并随几何资产版本保存。
- display_mask = geometry_mask ∩ texture_roi(无 texture_roi 时仅 geometry_mask)。
- contact_mask:随帧变化,只决定接触相关叠加,不改变静态手指轮廓。
禁止每帧通过非黑非白 RGB 判定有效区;光照、黑色标记、反光会改变它。掩膜形状或 domain 不匹配应报错,不通过任意 resize 掩盖问题。绘制纯色背景时分别设置内外颜色;箭头端点也裁到有效区。UV 翻转、裁剪、缩放只在明确的域转换处做一次,叠加显示边界以供验收。
## 5. 三维效果恢复
1. 表面热图:探索模式以有效活动值的稳健分位范围增强对比,设置最低色标跨度,缓慢更新范围;空场保持基底色。对比模式锁定范围。不要默认用每帧极小噪声铺满色谱。
2. 表面光流箭头:复用旧 flow_arrow 的采样与映射语义;初版保持相同点集和尺度。若另改成切向映射,须单独验收。原点加小量法向偏移避免闪烁。
3. 黄色法向箭头:恢复旧接触中心及局部法向,长度按 FN 的对数/幂律显示映射并限幅;注明“法向响应示意”,不声称长度线性对应牛顿。中心落在无效区时隐藏,不无条件吸附到远处顶点。
4. 三轴合力箭头是后续独立功能:只有统一轴定义、标定比例、力的参考坐标系及到 scene 的旋转确认后才生成。不能直接把未标定 FN/FX/FY 当成空间三维向量。
5. 外壳保持静态中性色,只给传感表面着色;坐标轴、相机、光照预设可复用旧版设计。
GL 初版用缓存圆柱/圆锥模板和每帧实例参数;也可用有限数量箭头合并网格,先测性能再引入 instancing。不要每帧新建数百个 Python/OpenGL 网格对象。隐藏三维时停组合和上传。
## 6. 数值稳定、零漂与曲线
先记录实际生效的 SDK FilterSettings。滤波只由一个层负责,沿用现有 worker 下发 SDK 的路径;shared ForceFilterChain 当前是对照实现,不应又在 UI 加一遍。
可用旧版 5 Hz 作为 A/B 起点,而非固定验收参数;截止频率必须低于实际采样率的一半,并测试短促接触峰值和延迟。中值窗口大于 1 仅在确认脉冲异常时启用。
区分四件事:
- 滤波:减少高频噪声,存在延迟。
- 无载归零:在用户确认无载、采样有效且稳定的窗口估计偏置,冻结到本次会话。慢漂不能靠固定死区无限修正;不要在持续静载时自动追零。
- 显示死区:只作用于展示副本;正负对称、带迟滞,不截掉负 FN。raw/N 各自保存阈值和偏置版本。切换单位/标定/设备/滤波时重置或重新验证政策。
- 数字格式:优先按噪声分辨率量化和稳定的小数位;raw 可起步 3–4 位有效数字或整数步长,N 依标定分辨率确定。固定宽度等宽数字,数字卡可降到 5–10 Hz,曲线仍用完整时间序列。处理负零及 NaN。
死区阈值可从无载残差的 MAD/分位数估计,必须给出噪声窗口、单位和适用配置。展示 0 时显示“零区”标识,可查看未抑制值。曲线默认显示滤波后的连续信号和淡色零区带;“零区压平”是明确开关,不建议默认把真实弱信号压没。
选区统计和导出使用测量序列,不能统计数字卡量化值;如支持展示信号统计,必须另行命名。核实 SDK raw 通道到底是滤波前还是仅标定前,不把现有 raw 列宣传成未滤波原始量。所有新增展示政策不回写现有记录值;政策/归零变更写带时间的事件和版本以便回放解释。
## 7. 文件及函数修改清单
以下路径以仓库根为起点。existing 表示改现有函数,new 表示建议新增接口;详细输入/输出规则见上文。
|路径|接口|职责及输入输出|
|---|---|---|
|orisys-app/shared/src/orisys_app_shared/acquisition/models.py|SdkPacket(existing)|增加可选 ContactObservation;帧字段与 UV 域一致|
|同目录 finger_worker.py|采集循环构造 SdkPacket 处(existing)|在 compute_contact 后读中心及 found;读失败标记不可用,复制后发布,不能由 UI 重新计算 SDK|
|同目录 image_backend.py|ImageFrame、plan_image、render_image(existing)|携带各字段有效性与身份;失效箭头/深度显示不可用|
|同目录 arrows.py|draw_arrow_display(existing)|增加 policy/内外色参数;背景、采样点和箭头裁剪共用 domain_mask|
|orisys-app/shared/src/orisys_app_shared/scene3d/visual_policy.py(new)|VisualPolicy; build_domain_mask(geometry_mask, texture_roi) -> bool[H,W]; select_flow_samples(flow, mask, policy) -> UVSamples|无 Qt/GL;严格检查形状、有限值和域;采样点两视图共用|
|同目录 overlays.py(new)|build_flow_overlay(samples, surface_mapping, style) -> ArrowInstances; build_normal_overlay(contact, fn, surface_mapping, style) -> ArrowInstances|只做 NumPy 几何;无接触/无有效力返回零实例,非法坐标返回诊断|
|同目录 contracts.py|SurfaceFieldFrame、RenderFrame(existing)|扩展可选向量/接触/箭头及描述信息,保持无 overlay 的旧调用兼容|
|同目录 composer.py|SceneComposer.compose(existing)|构建同帧表面和叠加;缓存静态映射/法向/模板,不读 SDK|
|orisys-app/apps/studio/src/orisys_studio/scene3d/adapters.py|FingerFrameAdapter.snapshot(existing)|同时验证需要的 flow/depth/contact 身份;选择上色场不应删除箭头所需 flow;保留显式降级提示|
|同目录 gl_backend.py|现有上传/绘制/资源释放路径(existing)|上传 ArrowInstances 或合并网格;只有此层依赖 GL;随上下文重建资源|
|同目录 view.py、coordinator.py|submit_packet、tick(existing)|携带 bundle 身份与策略版本;过期结果不覆盖新设置,提供已呈现帧号|
|同目录 panel.py|ThreeDPanel(existing)|上色量、光流箭头、接触法向箭头、固定/自动色标;诊断折叠|
|orisys-app/shared/src/orisys_app_shared/force/presentation.py(new)|ForcePresentation.configure(policy) -> None; accept(sample) -> DisplayForce; fit_zero(samples, context) -> ZeroEstimate; format_force(value, resolution, unit) -> str|独立展示状态;fit_zero 不隐式应用,返回偏置/噪声/样本数/质量;accept 不修改 sample|
|orisys-app/apps/studio/src/orisys_studio/ui/force_plots.py|format_force、_render_value_cards 及曲线展示入口(existing)|统一调用展示模型;删除两处重复格式规则;历史/导出不量化;选区统计明确口径|
|orisys-app/apps/studio/src/orisys_studio/app_window.py|_render_run_image、_show_image_frame(existing)|仅路由字段和政策,不加入阈值、滤波、中心计算算法|
|orisys-sdk/examples/viz/contact_arrow.py、flow_arrow.py|compute/几何构建函数(reference)|作为金标准验证移植;不复制 Open3D 生命周期到 Studio|
`ForcePresentation` 状态按设备/SN、session、单位、calibration revision、filter revision 分开。历史随机 seek 不得从上一 seek 的迟滞状态继续:从记录的政策边界和足够前导样本重放,或选择无状态死区并明确模式。事件不能混入常规力 CSV 数值列。
## 8. 顺序与验收
### A:先建立可比较输入,修正误导
用同一原视频、配置、初始化/预热步骤、SDK 版本分别驱动旧/新界面;最好捕获一次中间场 bundle 再供两套渲染器消费,排除算法与滤波差异。记录 flow/depth 范围、有效比例、中心、原始/输出力、实际滤波参数、帧号和呈现时间。
先交付 UV 轮廓、二维有效性、实际色标、集中数字格式及噪声诊断。这一阶段不换渲染器,也不引入全局阈值。
### B:恢复旧版关键视觉
交付同源 2D/3D 光流箭头、黄色法向箭头、相机/灯光;先 CPU 几何对比,再检查 GL 图像。UI 将三维画布作为主体,设置折叠;三维上色默认深度响应,叠加光流方向以解释切向滑动。提供二维/三维切换和并排诊断。
### C:显示抑制与同步比较
交付无载评估、显示死区、零区带、数字卡刷新率、同步比较模式及对应政策事件。先按实验数据选择阈值,再考虑产品预设“灵敏/稳定”;保留明确的参数查看入口。
### D:目标板验证
分别测试 Jetson Orin Nano、QCS6490 实际 GL/ES 上下文、shader、DPI、窗口隐藏/恢复、资产重载、长时间显存和内存、关闭中断、30 fps 预算。CPU 组合与 GPU 上传/绘制分别计时,记录 p50/p95 延迟和展示跳帧;硬件实测后再承诺帧率。
共同验收用例:无载、轻滑、法向压入、持续静载、释放、边缘接触、无效帧、断连、切换 raw/N、回放 seek。
- 同帧输入的二维/三维采样点、接触中心、掩膜边界符合几何映射,黄色箭头局部法向正确。
- 测量记录在开关显示死区/数字精度前后逐值一致;异常仍为无效,负值仍可检查。
- 稳定模式减少静止读数变化,但轻触检出、峰值偏差、端到端延迟符合团队事先约定指标;不能只用“看起来不跳”验收。
- 色标自动模式可见实际上下限;固定模式相同数值保持同色;空场不被放大成强接触。
- 同步比较 mode 的 frame_id 一致;常规实时 mode 的延迟可见且队列有界。
工作量按交付拆分评估:背景与数字格式较小;接触字段贯通及箭头几何为中等;同帧比较、回放状态和 ARM 实测为主要不确定项。先完成 B 的单段录制验证,再估算整体工期。直接加入 Open3D 也需要后一组工作,并不自动省掉它们。