Files
AnyImage/openspec/changes/unify-reference-depth/design.md

76 lines
7.0 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.
## Context
参考深度是深度几何的零点,同时是 `Uniform Scale` 的归一化基准:`depth_uniform_scale()` 用 `参考深度 × fx / (宽度 - 1)` 把模型相机单位换算成 Image Empty 的局部距离。同一张深度图因此只能有一个基准,否则投影尺度与位移零点会互相矛盾。
当前有两个算法。服务端 `write_depth_metadata()` 以「模型有效性 ∧ 深度有限为正 ∧ 源图 Alpha ≥ 阈值」取中位数写入 `depth.json`;Cutout 的 `metric_depth_reference()` 以「选区 ∩ 源图 Alpha ≥ 阈值」取 95 百分位,并在选区透明像素不足 5% 时改用固定值 `8.0`。Depth Plane 与 Relief Plane 用前者,Depth Solid 与 Depth Symmetry 用后者,Relief Plane 又用 `Depth Offset = 1` 把中位数基准向后补偿。
`depth.exr` 的 Alpha 是「源图 Alpha × 模型有效性」,随深度纹理一起被 Blender 读入,`camera_depth_values()` 已经在读它的 Alpha 与相机 Z,服务端那次计算没有额外信息可用。
## Goals / Non-Goals
**Goals:**
- 参考深度只有一处实现、一条有效性判据、一个统计量。
- 服务端只提供相机内参和图像尺寸,不再判断参考区域。
- 删除 Cutout 的透明比例闸门与固定基准兜底。
- 让 Relief Plane 的基准平面由参考深度决定,而不是由 `Depth Offset` 的默认值补偿。
**Non-Goals:**
- 不改变 AI 推理、深度纹理协议、节点组接口与 Depth Symmetry 的方向拟合行为。
- 不为旧 `depth.json` 字段保留兼容读取或转发函数。
- 不把参考深度暴露成新的用户参数。
## Decisions
### 参考深度在 Blender 侧由深度纹理计算
`common/depth.py` 新增 `reference_depth(image, mask=None)`,读取 `camera_depth_values()` 的相机 Z 与 Alpha,在 `Alpha > 0.5 且深度有限且为正` 的像素上取 95 百分位,返回模型单位。调用方再乘 `Uniform Scale`。服务端删除参考深度写入,`depth.json` 只保留 `image_size` 与 `intrinsics`。
服务端没有选区信息,也无法在加载时知道几何目标的意图;把判据放在读取深度纹理的一侧,才能让「有效性」只有 Alpha 一个来源。备选是保留服务端字段作为默认值,但这会让同一张深度图继续存在两个基准。
### 有效性只由 Alpha 与深度值决定
Alpha 已经包含源图 Alpha 与模型有效性两个条件,因此不再需要 `alpha_threshold` 参与参考深度,也不再需要 `validity` 与 Alpha 双重过滤。有效性阈值取 `0.95`:模型的有效性信号接近二值,抬高阈值主要排除源图 Alpha 的半透明边缘,让参考深度只统计完全不透明且模型判定有效的像素。代价是毛发、玻璃等本身就半透明的主体可能没有可用像素,此时几何创建明确失败而不是回退到固定基准。非有限与非正深度仍被排除,因为它们无法参与深度换算。
### 统计量统一为 95 百分位
基准平面取有效深度的上分位,主体整体落在基准之前:Depth Solid、Depth Symmetry 与 Relief Plane 的位移与厚度归一化因此都从「内容后端」起算,Depth Plane 与 Relief Plane 不再需要靠 `Depth Offset` 人工后移。作为 `Uniform Scale` 的锚点,这个取值同时决定近处内容相对源图轮廓的缩进量,属于可见的行为变化,由几何测试与人工检查确认。
备选是统一取中位数:实现同样简单,但 Depth Solid 与 Depth Symmetry 会有一半内容被钳制在深度零点,厚度归一化也随之改变。
### 参考深度丢弃超出中位数倍数的样本
参考深度同时是 `Uniform Scale` 的锚点,`Uniform Scale` 与参考深度成反比,所以远景会把整个重建结果缩小并整体推向基准之前。风景图没有主体,中位数与上分位都落在远景上,这个效应最明显。因此统计前丢弃距离超过 `REFERENCE_DEPTH_RANGE_FACTOR`(`1.1`)倍中位数的样本,让基准由主要内容的距离决定;被丢弃的内容仍保留在深度纹理里,只影响几何投影,不影响标定。
上限取中位数的倍数而不是固定米数,因为固定米数假设「主要内容在多少米以内」,一旦主体整体在固定上限之外,基准会落到主体之前,`reference − depth` 全为负而被钳制,几何直接消失。倍数上限随内容尺度变化:中位数永远落在上限之内,所以只要存在有效像素,统计集合一定非空,也不会因为题材远近而失效。代价是风景图的上限随内容放大,远景被推后的力度比固定小上限时轻。
### 参考域可选,Cutout 传入选区
`reference_depth(image, mask=None)` 的 `mask` 由目标自己决定:Cutout 传入当前选区,Plane 转换不传。选区只是参考深度的作用域,不再参与透明度比例判断。
全幅照片没有透明区域时,若忽略选区,参考深度会落在背景深度上,投影归一化会让被套索的主体缩小并与源图轮廓错开。备选是完全按整幅深度图计算,但会破坏套索全幅照片这一既有用法。
### 计算函数返回模型单位
`depth_uniform_scale()` 需要未缩放的参考深度才能求出 `uniform_scale`,因此 `reference_depth()` 返回模型单位,由调用方乘以尺度后写入 Modifier。
### Relief Plane 的 Depth Offset 默认值改为 0
基准平面已由 95 百分位放到内容后端,`Depth Offset` 恢复为纯粹的微调参数。默认值属于节点组接口,修改构建脚本后重新生成 `O_AnyImage.blend`。
### 删除 Cutout 标定模块
`depth_calibration.py` 的参考深度计算与透明比例闸门被 `reference_depth()` 取代;其中的基础形状 UV 采样与平面方向拟合下沉到 `common/depth.py` 的 `fit_symmetry_depth_direction()`,与 `fit_depth_direction()` 同处。`geometry.py` 的 `selection_calibration()` 只服务于旧标定,随之删除。
### 方向拟合沿用同一条选择规则
`fit_depth_direction()` 在没有可用样本时返回无倾斜的规范方向 `(0, 1, 0)`,而不是报错;Relief Plane 与 Depth Symmetry 因此都得到 `(0, 0, 1)` 的 `Direction`。兜底放在拟合函数里而不是调用点,是因为「有效像素选不出来」对参考深度和方向是同一个问题,放在一处可以删掉 Plane 转换里原有的 `try/except`,也让两个深度目标行为一致。
## Risks / Trade-offs
- [Depth Plane 与 Relief Plane 的基准从内容中位数移到上分位,几何尺度变化明显] → 这是本次明确要求的行为变化,用几何测试锁定 `Reference Depth` 与投影结果,不增加兼容分支。
- [套索选区在源图全不透明时决定参考深度] → 保留可选 `mask` 参数,让 Cutout 继续以选区为参考域。
- [节点组默认值与发布资产不一致] → 修改构建脚本后运行 `uv run node-group build`。
- [已有场景保存的 Modifier 仍带旧 `Reference Depth` 与 `Depth Offset`] → 已保存数值按原样保留,只有重新生成深度几何时才使用新基准。