Refactor depth reference handling and cleanup
- Unified reference depth calculation to use a single implementation in common/depth.py. - Removed server-side reference depth computation and adjusted depth.json to only include image_size and intrinsics. - Updated depth texture handling to ignore transparent, invalid, and non-positive pixels when calculating reference depth. - Changed reference depth calculation to use the 95th percentile of valid depth values. - Adjusted depth direction fitting to return a flat direction when no samples are available. - Removed cleanup threshold parameters from Cutout and related nodes, simplifying the interface. - Updated tests to verify new reference depth logic and ensure compatibility with existing functionality.
This commit is contained in:
2
openspec/changes/unify-reference-depth/.openspec.yaml
Normal file
2
openspec/changes/unify-reference-depth/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-15
|
||||
75
openspec/changes/unify-reference-depth/design.md
Normal file
75
openspec/changes/unify-reference-depth/design.md
Normal file
@@ -0,0 +1,75 @@
|
||||
## 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_LIMIT`(米)的样本,让基准由主要内容的距离决定;距离上限之外的内容仍保留在深度纹理里,只影响几何投影,不影响标定。
|
||||
|
||||
上限「选不出样本」和「没有有效像素」是同一个问题的两面——有效像素的选择多了一条距离条件——所以两者用同一条规则:没有样本剩下时,参考深度取上限本身。这样参考深度的取值域始终是 `(0, REFERENCE_DEPTH_LIMIT]`,同一张图不会因为少量近景像素出现而跳到另一个尺度,也不会因为深度不可信而失败或弹出提示。上限取固定米数而不是相对中位数的比例,因为米制深度是这个工具与 Blender 场景的既有接口,固定上限可以直接说明「主要内容在多少米以内」。
|
||||
|
||||
### 参考域可选,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`] → 已保存数值按原样保留,只有重新生成深度几何时才使用新基准。
|
||||
28
openspec/changes/unify-reference-depth/proposal.md
Normal file
28
openspec/changes/unify-reference-depth/proposal.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
参考深度现在有两条互不相干的算法:服务端按「源图 Alpha 阈值」取中位数写进 `depth.json`,Cutout 又按「选区透明比例」决定用 95 分位还是固定值 `8.0`。同一个深度场在不同目标下得到不同基准,历史补偿(`Depth Offset = 1`、固定兜底)也在互相抵消。深度纹理的 Alpha 已经同时携带源图 Alpha 与模型有效性,足以单独作为有效性判据。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**:服务端不再计算参考深度,`depth.json` 只保留 `image_size` 与 `intrinsics`。
|
||||
- 在 `common/depth.py` 提供唯一的参考深度计算:以 `depth.exr` 的 Alpha 大于 `0.95`、深度有限且为正为有效域,取相机 Z 的 95 百分位。
|
||||
- 统计前丢弃距离超过 `REFERENCE_DEPTH_LIMIT`(`8` 米)的样本,避免远景把参考深度与整个重建尺度拖到远处;选择后没有样本时参考深度取该上限,不报错也不提示。
|
||||
- 深度方向拟合没有可用样本时返回无倾斜方向,Relief Plane 与 Depth Symmetry 的 `Direction` 一律回退为 `(0, 0, 1)`,不再报错,也不在调用点各自兜底。
|
||||
- Depth Plane、Relief Plane、Depth Solid、Depth Symmetry 共用同一实现与同一统计量,不再区分「服务端基准」与「标定基准」;Cutout 把选区作为可选参考域传入,Plane 转换使用整幅深度图。
|
||||
- **BREAKING**:参考深度从内容中位数改为上分位,基准平面移动到场景后端,Depth Plane 与 Relief Plane 的几何结果随之变化。
|
||||
- 删除 Cutout 的透明比例闸门与固定基准兜底,并停止向服务端透传 Alpha Threshold;Alpha Threshold 只保留控制轮廓的既有职责。
|
||||
- 把 Relief Plane 的 `Depth Offset` 默认值从 `1` 改为 `0`,让基准由参考深度本身决定。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `reference-depth-calibration`: 定义参考深度的唯一来源、有效性判据、统计量,以及它作为深度几何与 `Uniform Scale` 归一化基准的职责。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
影响 `src/anyimage/server/geometry/depth_texture.py`、`geometry/prediction_artifacts.py`、`jobs/cutout.py`、`src/anyimage/common/depth.py`、Plane 转换与 Cutout 的对象创建逻辑、`tools/nodes/groups/image_relief_plane.py`、相关测试与 `src/anyimage/assets/O_AnyImage.blend`。不新增依赖、不改变 AI 推理与节点组接口;已有场景中保存的 Modifier 数值不变,但重新生成深度几何的结果会变化。
|
||||
@@ -0,0 +1,104 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reference depth has one implementation
|
||||
参考深度 SHALL 只由 `common/depth.py` 从 Depth 纹理计算。Server MUST NOT 计算或写出参考深度,`depth.json` SHALL 只包含 `image_size` 与 `intrinsics`。
|
||||
|
||||
#### Scenario: Write depth metadata
|
||||
- **WHEN** Server 写出 `depth.json`
|
||||
- **THEN** 文件包含 `image_size` 与 `intrinsics`
|
||||
- **AND** 文件不包含 `reference_depth`
|
||||
|
||||
#### Scenario: Load depth artifacts
|
||||
- **WHEN** Blender 加载 Depth 纹理与元数据
|
||||
- **THEN** 参考深度由 Depth 纹理计算
|
||||
- **AND** 元数据只提供相机内参与图像尺寸
|
||||
|
||||
### Requirement: Depth texture alpha and depth values define the reference domain
|
||||
参考深度 SHALL 只统计同时满足 `Alpha > 0.95`、深度有限且深度大于零的像素。系统 MUST NOT 使用 Cutout 的 Alpha Threshold 或选区透明度比例参与该统计。
|
||||
|
||||
#### Scenario: Exclude transparent and invalid pixels
|
||||
- **WHEN** 某像素的 Alpha 不大于 `0.95`,或相机 Z 非有限、非正
|
||||
- **THEN** 该像素不参与参考深度统计
|
||||
|
||||
#### Scenario: Exclude semi-transparent source edges
|
||||
- **WHEN** 源图 Alpha 与模型有效性的乘积落在 `0.5` 与 `0.95` 之间
|
||||
- **THEN** 该像素不参与参考深度统计
|
||||
|
||||
#### Scenario: Exclude everything
|
||||
- **WHEN** 没有任何像素满足有效条件
|
||||
- **THEN** 参考深度取 `REFERENCE_DEPTH_LIMIT`
|
||||
|
||||
### Requirement: Reference depth uses the upper depth percentile
|
||||
参考深度 SHALL 取有效像素相机 Z 的 95 百分位,并以模型单位返回。所有深度几何 SHALL 使用同一判据,MUST NOT 按目标区分统计量。
|
||||
|
||||
#### Scenario: Distant background is dropped before the percentile
|
||||
- **WHEN** 部分有效像素的距离超过 `REFERENCE_DEPTH_LIMIT` 米
|
||||
- **THEN** 这些像素不参与百分位统计
|
||||
- **AND** 参考深度只由上限之内的像素决定
|
||||
|
||||
#### Scenario: No sample survives the selection
|
||||
- **WHEN** 有效像素的距离全部超过 `REFERENCE_DEPTH_LIMIT` 米
|
||||
- **THEN** 参考深度取 `REFERENCE_DEPTH_LIMIT`
|
||||
- **AND** 系统不报告错误,也不提示基准来源
|
||||
|
||||
#### Scenario: Depth distribution is skewed
|
||||
- **WHEN** 有效深度在前后景之间分布不均
|
||||
- **THEN** 参考深度取有效深度的 95 百分位
|
||||
- **AND** 结果不小于同一深度场的中位数
|
||||
|
||||
#### Scenario: Share the reference across depth shapes
|
||||
- **WHEN** 同一 Depth 纹理分别用于 Depth Plane、Relief Plane、Depth Solid 与 Depth Symmetry
|
||||
- **THEN** 四者使用相同的有效域规则与相同的百分位
|
||||
|
||||
#### Scenario: Selection has little transparency
|
||||
- **WHEN** 参考域内几乎没有透明像素
|
||||
- **THEN** 系统仍按该参考域的有效像素计算 95 百分位
|
||||
- **AND** 不使用固定基准值
|
||||
|
||||
### Requirement: Depth direction fits fall back to a flat plane
|
||||
深度方向拟合 SHALL 在没有可用样本时返回无倾斜的规范方向,MUST NOT 报错。Relief Plane 与 Depth Symmetry 的 `Direction` SHALL 因此回退为 `(0, 0, 1)`,调用方 MUST NOT 各自实现兜底。
|
||||
|
||||
#### Scenario: Fit a relief without usable depth samples
|
||||
- **WHEN** 深度图没有可参与拟合的有效像素
|
||||
- **THEN** Relief Plane 仍成功创建
|
||||
- **AND** `Depth Direction` 为 `(0, 0, 1)`
|
||||
|
||||
#### Scenario: Fit depth symmetry without usable samples
|
||||
- **WHEN** 基础形状的采样点全部落在没有有效像素的区域
|
||||
- **THEN** Depth Symmetry 仍成功创建
|
||||
- **AND** `Direction` 为 `(0, 0, 1)`
|
||||
|
||||
### Requirement: Cutout scopes the reference depth to its selection
|
||||
Cutout SHALL 把当前选区作为参考域传入参考深度计算,并额外要求像素位于该选区内。Plane 转换 SHALL 使用整幅 Depth 纹理作为参考域。
|
||||
|
||||
#### Scenario: Selection covers part of an opaque image
|
||||
- **WHEN** 源图没有透明区域且用户只选中局部主体
|
||||
- **THEN** 参考深度只统计选区内的有效像素
|
||||
|
||||
#### Scenario: Convert a whole image to a depth plane
|
||||
- **WHEN** 用户把整幅图像转换为 Depth 或 Relief Plane
|
||||
- **THEN** 参考深度统计整幅 Depth 纹理的有效像素
|
||||
|
||||
### Requirement: Reference depth anchors the uniform scale
|
||||
参考深度 SHALL 作为 `Uniform Scale` 的归一化基准参与深度几何换算,MUST NOT 被预先乘以尺度。系统 MUST NOT 为参考深度新增用户参数。
|
||||
|
||||
#### Scenario: Convert model units to image plane units
|
||||
- **WHEN** 系统创建深度几何
|
||||
- **THEN** 参考深度以模型单位参与 `Uniform Scale` 计算
|
||||
- **AND** 已缩放的参考深度写入 `Reference Depth` Modifier 输入
|
||||
|
||||
### Requirement: Alpha Threshold only controls the outline
|
||||
Server MUST NOT 接收 Alpha Threshold 参数。Cutout 的 Alpha Threshold SHALL 只用于轮廓提取与内容有效性判定,MUST NOT 改变参考深度。
|
||||
|
||||
#### Scenario: Change alpha threshold
|
||||
- **WHEN** 用户改变 Alpha Threshold 并重新生成深度几何
|
||||
- **THEN** 轮廓按新阈值变化
|
||||
- **AND** 参考深度保持不变
|
||||
|
||||
### Requirement: Relief Plane places its base plane by reference depth
|
||||
`O Image Relief Plane` SHALL 使用默认值为 `0` 的 `Depth Offset`,基准平面由 95 百分位参考深度决定,`Depth Offset` 只保留微调职责。
|
||||
|
||||
#### Scenario: Create a relief plane
|
||||
- **WHEN** 用户新建 Relief Plane
|
||||
- **THEN** `Depth Offset` 为 `0`
|
||||
- **AND** 基准平面位于参考深度
|
||||
32
openspec/changes/unify-reference-depth/tasks.md
Normal file
32
openspec/changes/unify-reference-depth/tasks.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## 1. Shared reference depth
|
||||
|
||||
- [x] 1.1 Add `reference_depth(image, mask=None)` to `src/anyimage/common/depth.py`: take the 95th percentile of camera Z over pixels whose Depth texture Alpha is greater than `0.95` and whose depth is finite and positive, and return model units.
|
||||
- [x] 1.2 Drop `reference_depth` from `load_depth_metadata()` and change `depth_uniform_scale()` to take the computed reference depth instead of reading it from metadata.
|
||||
- [x] 1.3 Move the base-shape direction fit into `common/depth.py` as `fit_symmetry_depth_direction()` beside `fit_depth_direction()`, then delete `src/anyimage/operators/cutout_tool/depth_calibration.py`.
|
||||
- [x] 1.4 Delete `selection_calibration()` from `src/anyimage/operators/cutout_tool/geometry.py` and remove its test.
|
||||
- [x] 1.5 Drop reference samples farther than `REFERENCE_DEPTH_LIMIT` meters before the percentile and use that limit as the reference when the selection leaves no sample.
|
||||
- [x] 1.6 Return the flat canonical direction from `fit_depth_direction()` when no sample is usable, and delete the caller-side `try/except` in plane conversion.
|
||||
|
||||
## 2. Server artifacts
|
||||
|
||||
- [x] 2.1 Remove the `reference_valid` argument and the `reference_depth` field from `write_depth_metadata()` in `src/anyimage/server/geometry/depth_texture.py`.
|
||||
- [x] 2.2 Drop `alpha_threshold` from `generate_moge_artifacts()` and the cutout job parameters in `src/anyimage/server/jobs/cutout.py`.
|
||||
- [x] 2.3 Update the server metadata and artifact tests to expect a `depth.json` with only `image_size` and `intrinsics`.
|
||||
|
||||
## 3. Depth shape initialization
|
||||
|
||||
- [x] 3.1 Compute the reference depth in `create_depth_plane_object()` and `create_shape_object()`; pass the cutout selection as the reference mask and use the whole depth image for plane conversion.
|
||||
- [x] 3.2 Delete the transparency gate, `DEFAULT_DEPTH_BASELINE`, `DEFAULT_DEPTH_DIRECTION` fallback usage, and the adaptive calibration branch from the Cutout object creation path.
|
||||
- [x] 3.3 Update the operator and shared support fixtures so depth metadata no longer carries a reference depth and Cutout depth shapes build from the shared reference.
|
||||
- [x] 3.4 Add regression coverage that a skewed depth field returns the 95th percentile and that a selection mask narrows the reference domain.
|
||||
|
||||
## 4. Relief base plane
|
||||
|
||||
- [x] 4.1 Change the `Depth Offset` default to `0` in `tools/nodes/groups/image_relief_plane.py`.
|
||||
- [x] 4.2 Run `uv run node-group build` to rebuild and validate `src/anyimage/assets/O_AnyImage.blend`.
|
||||
- [x] 4.3 Update the Relief Plane node tests and interface inventory for the new default and the reference-depth-driven base plane.
|
||||
|
||||
## 5. Verification
|
||||
|
||||
- [x] 5.1 Run the affected `pytest` modules for `common.depth`, server artifacts, plane conversion, and cutout operators.
|
||||
- [x] 5.2 Search for stale `reference_depth` metadata reads, `depth_calibration` imports, and fixed-baseline references, then review the final diff.
|
||||
Reference in New Issue
Block a user