Add image perspective editing and related tools

- Implemented the Image Perspective tool for editing image corners in Blender.
- Created a new operator `EditImagePerspective` to handle the perspective transformation.
- Added a preview feature for the perspective adjustment.
- Introduced the `ImagePolylineTool` for editing image masks with polylines.
- Developed backend support for Real-ESRGAN model inference, including ONNX integration.
- Added functionality for image masking and background removal.
- Created unit tests for the Real-ESRGAN ONNX model to ensure correct preprocessing and inference.
This commit is contained in:
2026-08-22 22:17:10 +08:00
parent f42cba1932
commit 1613bd3194
45 changed files with 1802 additions and 1133 deletions

View File

@@ -1,77 +1,84 @@
# Image 工具技术实现
Image Box、Lasso 与 Perspective 位于 `operators/image_extract/`。三者共享源图选择、设置、图片写回、空间定位、Continuous 分层和可选背景清理。
Image 工具位于 `operators/image_edit/`。Box、Lasso、Line 与 Polyline 共享直接编辑管线;Perspective 在工具栏中独立显示并处理 Homography,但复用源图选择、绘制和 Empty 写回。
## 模块职责
```mermaid
flowchart LR
Box["box.py\nWorkspaceTool"] --> Region["region.py\nBox / Lasso Operator"]
Lasso["lasso.py\nWorkspaceTool"] --> Region
Perspective["perspective.py\n四点与透视矫正"]
Region --> Common["common.py\n共享选择、设置与结果写回"]
Tools["box / lasso / line / polyline\nWorkspaceTool"] --> Mask["mask.py\n手势、Refine、Invert"]
Perspective["perspective.py\n四点、比例、Homography"]
Mask --> Common["common.py\n选择、绘制、裁切、Empty 写回"]
Perspective --> Common
Common --> Job["extract-image-background\n可选 BEN2 Job"]
Mask --> Job["refine-image-mask\nBEN2 内容 mask"]
```
`box.py` 与 `lasso.py` 只声明 WorkspaceTool。两者通过 `selection_shape` 复用 `ExtractImageRegion`;Perspective 保留独立 Operator,因为它还有四点输入、比例预览和 Homography。
- `box.py`、`lasso.py`、`line.py`、`polyline.py` 只声明 WorkspaceTool。
- `mask.py` 用 `EditImageMask` 统一四种手势;`RefineImageMask` 负责异步 BEN2 响应。
- `perspective.py` 维护四点输入、比例预览和全分辨率重采样。
- `common.py` 只放跨两条管线实际共用的逻辑。
## 首次点击
`resolve_image_extract_click()` 将首次点击归为两种结果:
`resolve_image_edit_click()` 临时调用 Viewport Select 判断点击目标:
| 点击目标 | 结果 |
| 点击目标 | 行为 |
| --- | --- |
| 另一张 Image Empty | 只选择并设为 Active |
| 当前 Active 图片、空白或非图片对象 | 使用当前 Active 图片开始取区 |
| 另一张 Image Empty | 选择并设为 Active,本次不编辑 |
| 当前 Active 图片、空白或非图片对象 | 恢复原选择并开始编辑当前图片 |
这段逻辑由三个工具共用。点击非图片对象后会恢复原选择,避免触发对象移动或更换源图。
这避免工具在无有效源图时退化为对象移动,也让用户无需切回 Select Tool。
## 处理管线
## Mask 管线
```mermaid
flowchart TD
Input["屏幕选区"] --> Pixels["转换到源图片像素坐标"]
Pixels --> Background{"Remove Background?"}
Background -- 否 --> Local["Blender 内处理 RGBA"]
Background -- 是 --> Server["选区画布 → BEN2 → 局部图片"]
Local --> Tool{"工具类型"}
Server --> Tool
Tool -- Box / Lasso --> Crop["Alpha bounds + Padding"]
Tool -- Perspective --> Warp["预乘 Alpha Homography"]
Warp --> Crop
Crop --> Result["创建并 Pack Image\n定位 Empty"]
Gesture["屏幕手势"] --> Polygon["源图片像素 polygon / mask"]
Polygon --> Refine{"Refine Selection?"}
Refine -- 否 --> Content["几何 mask"]
Refine -- 是 --> Server["选区外 RGBA 清零 → BEN2"]
Server --> Content2["BEN2 Alpha × 几何 mask"]
Content --> Invert{"Invert?"}
Content2 --> Invert
Invert -- 否 --> Final["final mask"]
Invert -- 是 --> Reverse["1 - final mask"]
Final --> Apply["源 Alpha × mask"]
Reverse --> Apply
Apply --> Crop["按非零 Alpha 紧裁切\n保留源 RGB"]
Crop --> Write["替换 Image 并更新 Empty 显示"]
```
Box 与 Lasso 将选区外 RGBA 清为透明黑,再按非零 Alpha 的最小范围裁切。Perspective 先将四点规范为沿源图 X/Y 方向的凸四边形,再按确认的宽高比重采样;点击顺序不参与输出旋转。
Box / Lasso 的顺序固定为 **Refine → Invert**。BEN2 只提供内容 Alpha;最终 RGB 始终来自源图。Blender 侧只修改 Alpha,不清理透明区 RGB,以避免 Viewport 纹理过滤产生黑边。Line / Polyline 强制关闭 Refine,只执行几何 mask 与 Invert。
透视预览固定生成一次 512 × 512 临时 Image。鼠标移动只改变屏幕上的预览比例;确认后才执行全分辨率重采样。
Line 的屏幕坐标 Y 轴与图片坐标 Y 轴方向相反,因此像素阶段会反转线段端点,使半透明预览侧与实际保留侧一致;`Ctrl` 只影响屏幕端点的 5° 吸附。零长度点击在投影前直接取消,投影结果不足两个点时也会安全终止。Polyline 接近起点时绘制闭合圆环,并支持单击起点或 `Enter` 完成;完成时显式闭合,不把当前游标位置作为额外顶点。
## 背景清理
## Perspective 管线
`extract-image-background` 与标准 `remove-background` Job 共用 `remove_image_background()`。图片工具的 Job 只增加三项选区语义:
四点先按源图片 X/Y 方向规范为凸四边形,输入顺序不参与输出方向。第四点后只生成一次 512 × 512 临时纹理;移动鼠标改变 UI 比例,确认时才进行全分辨率 Homography。
- polygon mask;
- 局部图片在源图中的 bounds;
- Box / Lasso 使用的透明 Padding。
透视重采样使用预乘 Alpha 插值,随后恢复 RGB、清理透明像素并紧裁切。输出最长边限制为 8192 px,总像素限制约 16 MP。
Perspective 请求的 Job Padding 固定为 0。局部结果回到 Blender 后先换算为源图坐标,再执行透视矫正和最终 Padding。
Perspective 不调用 BEN2,也不支持 Invert。它是 deform 工具,不属于 mask 清理语义。
## Empty 定位与 Continuous
## Empty 写回
Box 与 Lasso 根据裁切前后的像素 bounds 换算 Empty 的中心和显示尺寸。Perspective 使用源四边形的轴对齐 bounds 近似放置结果。三者都沿用源 Empty 的矩阵,因此朝向与深度不变。
`replace_empty_image_pixels()` 创建并 Pack 新 Image datablock,然后替换当前 Empty 的 `data`。它不复制对象,也不修改 `matrix_world`。
Continuous 关闭时直接更新源 Empty;开启时复制对象,并沿操作开始时捕获的 View 方向偏移:
`align_empty_display_to_bounds()` 根据源画布、旧 `plane_bounds`、结果尺寸和放置 bounds 更新:
```text
offset = display_size × world_scale × 0.001 × layer
```
- `empty_display_size`;
- `empty_image_offset`。
`layer` 记录在源对象的 `_anyimage_extract_layer` Custom Property 中,使连续结果依次靠近当前 View。
Box、Lasso、Line 与 Polyline 的放置 bounds 等于最终裁切范围,因此像素与原图片平面严格对齐。Perspective 的结果比例可能不同于四边形轴对齐范围,因此只保持中心和覆盖尺度近似一致。
## 维护约束
## Server 边界
- WorkspaceTool 的显示设置放在 `box.py`、`lasso.py`、`perspective.py`。
- Box / Lasso 的共享交互放在 `region.py`,跨三个工具的行为放在 `common.py`。
- Blender 数据只在主线程 Operator 中读写;Server 参数保持为可序列化字典。
- 普通裁切不启动 Job Server;只有 Remove Background 需要 Environment 与 BEN2。
普通 Mask 和 Perspective 完全在 Blender 侧执行。只有 Box / Lasso 的 **Refine Selection** 提交 `refine-image-mask`:
1. 根据 polygon 创建局部透明画布;
2. 清空选区外 RGBA;BEN2 公共入口再以输入 Alpha 遮住透明 RGB;
3. 使用共享的 `remove_image_background()` 运行 BEN2;
4. 再乘几何 selection mask;
5. 返回紧裁切的 `image.png` 与源图坐标 `bounds.json`。
Blender 只读取返回图片的 Alpha,并把它应用到原图 RGB。相同的 BEN2 输入清理规则也用于 Remove Background、Depth Plane、Surface 和 Balloon;正式输出使用源 RGB 与 BEN2 Alpha。Blender 数据只在主线程 Operator 中读写,Job 参数保持为可序列化字典。