464 lines
23 KiB
Markdown
464 lines
23 KiB
Markdown
# Mesh-Pipeline 视角变换 · 跨会话实施计划
|
||
|
||
> **状态**:SPEC / 尚未开工
|
||
> **最后更新**:2026-04-20 15:30
|
||
> **负责人**:Agent + 用户 @shine
|
||
> **适用对象**:任意新会话的 Agent,本文档自包含,无需额外上下文即可开工
|
||
|
||
---
|
||
|
||
## 0 · 上下文(Why)
|
||
|
||
### 0.1 起因
|
||
|
||
当前项目 `art-agent` 已经集成了 Zero123++(`jd7h/zero123plusplus`)做单图→多视角变换,但存在固有缺陷:
|
||
|
||
1. **只输出 6 个固定视角**,`azimuth/elevation` 写死在 `config.py`,用户无法指定任意相机位姿
|
||
2. **大角度一致性差**,Zero123++ 主要训练于小物体,对建筑类规整几何 + 大角度偏转常出现立面错位
|
||
3. **无几何中间产物**,纯图对图黑盒映射,无法二次利用
|
||
|
||
### 0.2 目标
|
||
|
||
新增一条"Mesh Pipeline"视角变换路径,与现有 `zero123plus` 并列保留,让用户可以:
|
||
|
||
- 输入 1 张 2D 图(典型场景:带描边卡通风的建筑,如教堂/房屋/塔楼)
|
||
- 输出 N 张**保留原画风**、**几何一致**的新视角 2D 图
|
||
- 可指定任意 `azimuth/elevation`(至少阶段性支持 Trellis 360° 中的任意角度)
|
||
|
||
**非目标**:输出 3D mesh 给用户下载(mesh 只是中间产物);替代 Zero123++(它作为"轻量路径"继续保留)。
|
||
|
||
### 0.3 相关历史上下文
|
||
|
||
| 记录 | 说明 |
|
||
|---|---|
|
||
| `CL-20260416-1000` | Zero123++ 首次集成 |
|
||
| `CL-20260420-1212` | Zero123++ 切图布局修复(cols=2/rows=3) |
|
||
| `CL-20260420-1501` | 后端 watchfiles 依赖修复,uvicorn --reload 生效 |
|
||
| `PF-20260420-1212` | Grid 切图布局坑("3×2" 措辞歧义) |
|
||
| `PF-20260420-1501` | Windows uvicorn --reload 无 watchfiles 退化坑 |
|
||
|
||
---
|
||
|
||
## 1 · 核心决策
|
||
|
||
### 1.1 三段式流水线
|
||
|
||
```
|
||
[输入: 1 张 2D 图]
|
||
↓
|
||
【阶段 1】单图 3D 重建 → 生成 textured .glb + 预渲染 360° 视频
|
||
↓
|
||
【阶段 2】从视频抽帧 / 自定义相机渲染 → 多视角"几何参考图"
|
||
↓
|
||
【阶段 3】SDXL + ControlNet + IP-Adapter 二次重绘 → 还原原画风
|
||
↓
|
||
[输出: N 张保留风格、几何一致的 2D 新视角图]
|
||
```
|
||
|
||
### 1.2 模型选型
|
||
|
||
| 阶段 | 主选 | 备选 / 高级选项 | 理由 |
|
||
|---|---|---|---|
|
||
| 阶段 1 | **Trellis** (`firtoz/trellis`) | Hunyuan3D-2 (`tencent/hunyuan3d-2`) | Trellis $0.041/30s 且自带 color_video,Hunyuan 贵 3×慢 4×但 mesh 更扎实 |
|
||
| 阶段 2 | **ffmpeg 从 Trellis `color_video` 抽帧** | pyrender 加载 .glb 任意角度渲染 | Trellis 已送 360° 环绕视频,首版完全省事 |
|
||
| 阶段 3 | **`chigozienri/ip_adapter-sdxl-controlnet-depth`** | 自建 SDXL + 多 ControlNet 组合 | 一个 Replicate 模型打包了 SDXL+ControlNet-Depth+IP-Adapter |
|
||
|
||
### 1.3 为什么不用 Hunyuan3D-2 作默认
|
||
|
||
- 贵 3 倍($0.12 vs $0.041)
|
||
- 慢 4 倍(127s vs 30s)
|
||
- **不返回预渲染视频** → 必须额外实现阶段 2(pyrender / blender 渲染器)
|
||
- 唯一优势:mesh 几何质量更高
|
||
|
||
→ 留作"高质量模式"供用户显式选择,一期不默认启用。
|
||
|
||
### 1.4 为什么不用 ControlNet-Depth 必须先估深度
|
||
|
||
`chigozienri/ip_adapter-sdxl-controlnet-depth` 的 `controlnet_input` 要求已经是 depth map。两条路:
|
||
|
||
- **路 A**:从 Trellis `normal_video` 抽帧,把 normal 当"结构约束"直接喂(近似方案,不完美但可用)
|
||
- **路 B**:从 color_video 抽帧后,额外调用 `chenxwh/depth-anything-v2` 生成 depth map
|
||
|
||
一期走路 A 简化;若效果不足再升级路 B(多一次 API 调用 + $0.003/张)。
|
||
|
||
---
|
||
|
||
## 2 · 技术数据清单(Schema)
|
||
|
||
### 2.1 Trellis — `firtoz/trellis`
|
||
|
||
- **版本 ID**:`e8f6c45206993f297372f5436b90350817bd9b4a0d52d2a76df50c1c8afa2b3c`
|
||
- **成本**:$0.041/次(~24 次/$1)
|
||
- **耗时**:~30 秒
|
||
- **硬件**:Nvidia A100 80GB
|
||
- **输入关键字段**:
|
||
|
||
| 字段 | 类型 | 默认 | 说明 |
|
||
|---|---|---|---|
|
||
| `images` | `array[uri]` | 必填 | 输入图(支持多图,一期只传 1 张) |
|
||
| `seed` | int | 0 | 种子 |
|
||
| `randomize_seed` | bool | true | 一期设为 false 便于复现 |
|
||
| `texture_size` | int (512-2048) | 1024 | GLB 纹理分辨率 |
|
||
| `mesh_simplify` | float (0.9-0.98) | 0.95 | 网格简化,建议保持默认 |
|
||
| `generate_color` | bool | **true** | **必开**,用于得到 color_video |
|
||
| `generate_normal` | bool | **true** | **一期开启**,便于阶段 3 做结构约束 |
|
||
| `generate_model` | bool | **true** | **必开**,否则不输出 .glb |
|
||
| `save_gaussian_ply` | bool | false | 不需要 |
|
||
| `ss_sampling_steps` | int (1-50) | 12 | 默认即可 |
|
||
| `slat_sampling_steps` | int (1-50) | 12 | 默认即可 |
|
||
| `ss_guidance_strength` | float (0-10) | 7.5 | 默认即可 |
|
||
| `slat_guidance_strength` | float (0-10) | 3 | 默认即可 |
|
||
|
||
- **输出字段**(⚠️ 实测修订 2026-04-20 16:15,版本 `e8f6c452...`):
|
||
|
||
| 字段 | 类型 | 实测行为 |
|
||
|---|---|---|
|
||
| `model_file` | uri | ✅ .glb 文件(`generate_model=true` 时)|
|
||
| `color_video` | uri | ❌ **恒为 null** — 该部署版不单独输出 |
|
||
| `normal_video` | uri | ❌ **恒为 null** — 该部署版不单独输出 |
|
||
| `combined_video` | uri | ✅ **唯一可用的视频** — color+normal **左右并排** mp4 |
|
||
| `gaussian_ply` | uri | ✅ 高斯点云(本项目不用) |
|
||
| `no_background_images` | array[uri] | ✅ 去背景的预处理图 |
|
||
|
||
**实测 schema 踩坑**(已记为 PF-20260420-1600):
|
||
|
||
1. 输入字段必须用 `images`(数组)—— 传 `image`(单数字符串)会 422 `input: images is required`,即使 Replicate README example 里写的是 `image`。
|
||
2. 尽管 `generate_color=True` 和 `generate_normal=True` 都开,`color_video` 和 `normal_video` 字段也**恒为 null**,只有 `combined_video` 可用。
|
||
3. 因此 Phase 2 抽帧的数据源是 `combined_video`,抽帧后需要**切左半 = color 帧、切右半 = normal 帧**。
|
||
|
||
### 2.2 Hunyuan3D-2 — `tencent/hunyuan3d-2`(备选)
|
||
|
||
- **版本 ID**:`b1b9449a1277e10402781c5d41eb30c0a0683504fb23fab591ca9dfc2aabe1cb`
|
||
- **成本**:$0.12/次
|
||
- **耗时**:~127 秒
|
||
- **硬件**:Nvidia L40S
|
||
- **输入**:`image`, `steps` (20-50, default 50), `guidance_scale` (1-20, default 5.5), `seed`, `octree_resolution` (default 256), `remove_background` (default true)
|
||
- **输出**:`mesh` (uri, .glb) —— **无视频无多视角预览**,必须配套阶段 2 自定义渲染
|
||
|
||
### 2.3 ControlNet + IP-Adapter — `chigozienri/ip_adapter-sdxl-controlnet-depth`
|
||
|
||
- **版本 ID**:`0436c8702ef52616be5c30948551b3af6a86c821cca9b01f11ac297624fff14c`
|
||
- **成本**:$0.070/次
|
||
- **耗时**:~72 秒
|
||
- **硬件**:Nvidia L40S
|
||
- **输入关键字段**:
|
||
|
||
| 字段 | 类型 | 默认 | 说明 |
|
||
|---|---|---|---|
|
||
| `image` | uri | 必填 | **IP-Adapter 参考图**(此处传**用户原图**保持风格) |
|
||
| `controlnet_input` | uri | 必填 | **Depth / Normal / Lineart 参考图**(此处传**阶段 2 抽出来的 normal 帧**) |
|
||
| `scale` | float (0-1) | 0.6 | IP-Adapter 权重,建议调到 **0.7-0.8** 强化风格 |
|
||
| `controlnet_conditioning_scale` | float (0-1) | 0.6 | 结构约束权重,建议 **0.7-0.9** |
|
||
| `prompt` | str | "" | 留空或用 "same building, same art style" |
|
||
| `negative_prompt` | str | "" | "blurry, distorted, different style" |
|
||
| `num_outputs` | int (1-4) | 1 | 一期 1 |
|
||
| `num_inference_steps` | int (1-500) | 30 | 默认即可 |
|
||
| `seed` | int | 随机 | 调参阶段固定 |
|
||
|
||
### 2.4 依赖清单
|
||
|
||
**一期必装**(首版走 Trellis + ffmpeg 抽帧,最小化依赖):
|
||
|
||
```
|
||
# backend requirements.txt
|
||
ffmpeg-python==0.2.0 # 或者直接在后端宿主机装 ffmpeg 可执行文件
|
||
```
|
||
|
||
**二期可选**(若要上 Hunyuan3D-2 + 自定义相机):
|
||
|
||
```
|
||
trimesh==4.5.0
|
||
pyrender==0.1.45
|
||
PyOpenGL==3.1.7
|
||
# Windows 额外需要 OSMesa 或改用 Blender headless(二选一)
|
||
```
|
||
|
||
**系统依赖**:
|
||
|
||
- `ffmpeg` 可执行文件(Windows:`choco install ffmpeg` 或手动放 PATH)
|
||
- 若二期启用:Windows 上 OSMesa 编译困难,推荐用 Blender 4.x headless 替代
|
||
|
||
---
|
||
|
||
## 3 · 数据流与接口设计
|
||
|
||
### 3.1 新增后端服务模块
|
||
|
||
```
|
||
art-agent/backend/app/services/
|
||
├── view_transform.py # [改造] 管道调度器
|
||
├── mesh_generator.py # [新增] 阶段 1:调用 Trellis / Hunyuan3D-2
|
||
├── video_frame_extractor.py # [新增] 阶段 2(轻量):ffmpeg 抽帧
|
||
├── mesh_renderer.py # [新增, 二期] 阶段 2(自由相机):pyrender 加载 .glb 渲染任意角度
|
||
└── style_restorer.py # [新增] 阶段 3:ControlNet + IP-Adapter 重绘
|
||
```
|
||
|
||
### 3.2 管道调度伪代码
|
||
|
||
```python
|
||
# services/view_transform.py
|
||
async def transform_view_mesh_pipeline(
|
||
image_path: str,
|
||
model_id: str = "trellis", # or "hunyuan3d"
|
||
azimuths: list[int] = [0, 60, 120, 180, 240, 300],
|
||
elevations: list[int] = [0, 0, 0, 0, 0, 0],
|
||
preserve_style: bool = True, # 是否走阶段 3
|
||
) -> dict:
|
||
# === 阶段 1 ===
|
||
mesh_result = await mesh_generator.generate(image_path, model_id)
|
||
# 返回 {glb_url, color_video_url?, normal_video_url?}
|
||
|
||
# === 阶段 2 ===
|
||
if model_id == "trellis":
|
||
# 从视频抽帧(Trellis 的 360° 视频约 5s,对应 az 0→360°)
|
||
frames = await video_frame_extractor.extract_by_azimuth(
|
||
color_video_url=mesh_result["color_video_url"],
|
||
normal_video_url=mesh_result["normal_video_url"],
|
||
azimuths=azimuths,
|
||
total_rotation_deg=360,
|
||
)
|
||
else: # hunyuan3d
|
||
# 二期:自定义相机渲染
|
||
frames = await mesh_renderer.render_views(
|
||
glb_path=mesh_result["glb_path"],
|
||
azimuths=azimuths,
|
||
elevations=elevations,
|
||
)
|
||
# frames = [{az, el, color_png, normal_png}, ...]
|
||
|
||
# === 阶段 3 ===
|
||
if preserve_style:
|
||
final_images = await asyncio.gather(*[
|
||
style_restorer.restore(
|
||
original_image=image_path,
|
||
structure_image=f["normal_png"],
|
||
azimuth=f["az"],
|
||
elevation=f["el"],
|
||
)
|
||
for f in frames
|
||
])
|
||
else:
|
||
final_images = [f["color_png"] for f in frames]
|
||
|
||
return {
|
||
"success": True,
|
||
"images": [
|
||
{"url": img, "azimuth": az, "elevation": el}
|
||
for img, (az, el) in zip(final_images, zip(azimuths, elevations))
|
||
],
|
||
"mesh_url": mesh_result.get("glb_url"),
|
||
"model_name": f"Mesh Pipeline ({model_id})",
|
||
}
|
||
```
|
||
|
||
### 3.3 config.py 扩展
|
||
|
||
```python
|
||
VIEW_TRANSFORM_MODELS = {
|
||
"zero123plus": {...}, # 保留不动
|
||
"trellis": {
|
||
"id": "trellis",
|
||
"name": "Trellis (Mesh Pipeline)",
|
||
"provider": "replicate",
|
||
"model_id": "firtoz/trellis:e8f6c45206993f297372f5436b90350817bd9b4a0d52d2a76df50c1c8afa2b3c",
|
||
"description": "3D 重建 + 任意视角 + 风格还原,建筑友好(~30s + 风格化 ~72s/张)",
|
||
"pipeline": "mesh", # 新增字段区分管道类型
|
||
"default_params": {
|
||
"texture_size": 1024,
|
||
"mesh_simplify": 0.95,
|
||
"generate_color": True,
|
||
"generate_normal": True,
|
||
"generate_model": True,
|
||
},
|
||
},
|
||
"hunyuan3d": {
|
||
"id": "hunyuan3d",
|
||
"name": "Hunyuan3D-2 (高质量)",
|
||
"provider": "replicate",
|
||
"model_id": "tencent/hunyuan3d-2:b1b9449a1277e10402781c5d41eb30c0a0683504fb23fab591ca9dfc2aabe1cb",
|
||
"description": "几何质量最佳(~127s),需自定义渲染(二期启用)",
|
||
"pipeline": "mesh",
|
||
"enabled": False, # 一期先禁用
|
||
},
|
||
}
|
||
|
||
IMAGE_MODELS["ip-adapter-controlnet-depth"] = {
|
||
"id": "ip-adapter-controlnet-depth",
|
||
"name": "IP-Adapter + ControlNet Depth (内部)",
|
||
"provider": "replicate",
|
||
"model_id": "chigozienri/ip_adapter-sdxl-controlnet-depth:0436c8702ef52616be5c30948551b3af6a86c821cca9b01f11ac297624fff14c",
|
||
"internal": True, # 不在前端展示,仅用于阶段 3
|
||
...
|
||
}
|
||
```
|
||
|
||
### 3.4 工具层 `tools.py`
|
||
|
||
`transform_view` 工具参数扩展(向后兼容):
|
||
|
||
```python
|
||
{
|
||
"name": "transform_view",
|
||
"parameters": {
|
||
"image_path": {...},
|
||
"model_id": {
|
||
"enum": ["zero123plus", "trellis", "hunyuan3d"],
|
||
"default": "zero123plus",
|
||
},
|
||
# 以下为 mesh pipeline 专属参数
|
||
"azimuths": {"type": "array", "default": [0, 60, 120, 180, 240, 300]},
|
||
"elevations": {"type": "array", "default": [0, 0, 0, 0, 0, 0]},
|
||
"preserve_style": {"type": "boolean", "default": True},
|
||
},
|
||
}
|
||
```
|
||
|
||
### 3.5 前端改造
|
||
|
||
- 模型下拉增加 "Trellis (Mesh Pipeline)" 和 "Hunyuan3D-2 (高质量)" 选项
|
||
- 选中 mesh pipeline 时,额外显示:
|
||
- "保留原画风格" 开关(默认开)
|
||
- 角度输入:简化为"快速预设(6/8/12 视角)" + "自定义 azimuth 数组"(高级)
|
||
- 结果区域:除了 N 张图,还展示 `color_video` 可预览
|
||
|
||
---
|
||
|
||
## 4 · 施工任务清单(跨会话可独立执行)
|
||
|
||
### Phase 1:Trellis 接入(预计 1.5h)
|
||
|
||
**验收标准**:用 curl / Replicate SDK 能跑通一次 Trellis,拿到 .glb + color_video URL,下载到本地。
|
||
|
||
- [x] P1-1 `services/mesh_generator.py` 新建,封装 `generate_with_trellis(image_path)` 函数(调用 Replicate + 下载结果到 `GENERATED_DIR`)
|
||
- [x] P1-2 `config.py` 新增 `VIEW_TRANSFORM_MODELS["trellis"]` 条目
|
||
- [x] P1-3 `services/view_transform.py` 根据 `pipeline == "mesh"` 分流到 `mesh_generator`
|
||
- [x] P1-4 `tools.py` 的 `transform_view` 增加 `model_id` 参数默认值兼容
|
||
- [x] P1-5 本地手动测试:`scripts/test_trellis.py` 可独立运行验证 Trellis → .glb + color_video + normal_video 本地落地(**等用户执行**)
|
||
|
||
### Phase 2:ffmpeg 抽帧(预计 1h)
|
||
|
||
**验收标准**:从 color_video.mp4 抽出指定 azimuth 对应的帧,保存为 PNG,帧数正确、角度映射正确。
|
||
|
||
- [x] P2-1 宿主机 ffmpeg 未装,改用 `imageio-ffmpeg==0.5.1`(静态二进制随包,免系统依赖),已写入 `requirements.txt`
|
||
- [x] P2-2 `services/video_frame_extractor.py` 新建,实现 `extract_by_azimuth(combined_video_path, azimuths, elevations=None, total_rotation_deg=360)`
|
||
- 策略改为"一次抽全帧到 temp dir + 按帧索引挑" —— 比 `-ss` 逐帧抽更稳(不受 keyframe 对齐影响),且 5s 视频成本可忽略
|
||
- 抽出后用 PIL 按中线切:左半 → color_png、右半 → normal_png(实测 1024×512 → 512×512 方形)
|
||
- `ffmpeg + PIL` 是阻塞的,用 `run_in_executor` 丢到线程池避免堵住 async 事件循环
|
||
- 临时目录抽完即清理,只保留切好的最终帧
|
||
- [x] P2-3 `view_transform._transform_view_mesh` 串接 Stage 1 → Stage 2:默认 `azimuths=[0,60,120,180,240,300]`;Stage 2 失败不会把整体置为失败(Stage 1 的 .glb / video 仍有交付价值,`error` 字段单独带回 Stage 2 错误)
|
||
- [x] P2-4 本地测试脚本 `scripts/test_frame_extract.py`(**等用户执行**验收)
|
||
- Agent 已自测:120 帧视频 → az=0/60/120/180/240/300 映射到 frame_idx=0/20/40/60/80/100,完全均匀 ✓
|
||
- 12 个 color/normal PNG 全部落盘,尺寸 512×512 方形 ✓
|
||
|
||
### Phase 3:风格还原重绘(预计 1.5h)
|
||
|
||
**验收标准**:6 张视角图通过 IP-Adapter+ControlNet 重绘后,风格与原图一致、几何不变形。
|
||
|
||
- [x] P3-1 `config.py` 注册 `IMAGE_MODELS["ip-adapter-controlnet-depth"]`,标 `internal=True`,同时改 `get_image_models_list()` 过滤 internal,不污染前端下拉
|
||
- [x] P3-2 `services/style_restorer.py` 新建,实现 `restore(original_image, structure_image, ...)` + `restore_batch()`(带并发 Semaphore 防 Replicate rate limit)
|
||
- [x] P3-3 `view_transform.py` 增加阶段 3;`preserve_style=True` 时对 Stage 2 的每一帧做重绘;单帧失败自动回退为 Stage 2 color 帧,不阻塞整体
|
||
- [ ] P3-4 **等待 Checkpoint 3**:调参轮次需要用户执行 `scripts/test_style_restore.py <原图>`,分别尝试 `scale=0.6/0.7/0.8` × `controlnet_conditioning_scale=0.6/0.7/0.9`,选出最佳组合写入默认参数
|
||
|
||
### Phase 4:前端 + 端到端联调(预计 1h)
|
||
|
||
- [ ] P4-1 `types.ts` 扩展 `ViewTransformModel` 类型
|
||
- [ ] P4-2 前端模型下拉新增两个选项
|
||
- [ ] P4-3 工作台选中 mesh 管道时显示"保留原画风"开关
|
||
- [ ] P4-4 结果面板支持展示视频(color_video)+ N 张风格化后的视角图
|
||
- [ ] P4-5 端到端联调:前端上传一张卡通建筑图 → 选 Trellis → 确认 N 张结果正确显示
|
||
|
||
### Phase 5:Hunyuan3D-2 + 自由相机(**可选,二期**,预计 3-4h)
|
||
|
||
**仅在 Phase 1-4 完成且用户明确要求自由 elevation/远近相机时启动。**
|
||
|
||
- [ ] P5-1 venv 安装 `trimesh + pyrender + PyOpenGL`;Windows 上验证 headless 可用性
|
||
- [ ] P5-2 若 pyrender 在 Windows 不可行 → 改用 Blender 4.x headless(需 `blender.exe -b --python render.py`)
|
||
- [ ] P5-3 `services/mesh_renderer.py` 新建,实现 `render_views(glb_path, azimuths, elevations, distance=2.0, fov=45)`
|
||
- [ ] P5-4 启用 `config.py` 的 `hunyuan3d` 条目(`enabled=True`)
|
||
- [ ] P5-5 前端"高级模式"解锁 elevation 滑块
|
||
|
||
### Phase 6:收尾
|
||
|
||
- [ ] P6-1 `changelog-full.md` / `-recent.md` / `-headlines.md` 三层同步记录
|
||
- [ ] P6-2 发现的坑 → `pitfalls.md`
|
||
- [ ] P6-3 更新 `art-agent/README.md` 里视角变换段落
|
||
|
||
---
|
||
|
||
## 5 · 风险与兜底
|
||
|
||
| 风险 | 概率 | 影响 | 兜底 |
|
||
|---|---|---|---|
|
||
| Trellis 对 2D 卡通描边图重建几何偏糊 | 中 | 阶段 3 结构约束失效 | 先用 SDXL+InstantStyle 把原图"半写实化"一版给 Trellis 吃,最后阶段 3 用原 2D 图做 IP-Adapter 还原风格 |
|
||
| 从 color_video 抽帧只有水平环绕(elevation=0),不能仰视/俯视 | 高 | 无法满足"仰视大教堂"等需求 | 二期启用 Phase 5 的 pyrender 自由相机,或改用 Hunyuan3D-2 + 自定义渲染 |
|
||
| IP-Adapter+ControlNet 6 次调用并发打满 Replicate rate limit | 低 | 部分帧失败 | 改成串行 + 指数退避 + 失败单帧重试 |
|
||
| 总链路太慢(>3 分钟)用户失去耐心 | 中 | 产品体验差 | 前端用 SSE 流式展示中间产物:阶段 1 完成显示视频 → 阶段 3 每张完成立即插入 |
|
||
| ffmpeg 未装 | 高(Windows) | Phase 2 不能跑 | 开工前检查 `ffmpeg -version`,未装先 `choco install ffmpeg` 或用 Python 库 `imageio-ffmpeg` 绑定 |
|
||
| Trellis 某次输出 video 是 4s 某次 6s,固定 total_rotation_deg=360 可能不准 | 低 | 抽帧角度偏差 | 用 `ffprobe` 动态读取总帧数;如有疑问就固定 24 帧均匀抽样全部保留 |
|
||
|
||
---
|
||
|
||
## 6 · 成本与性能预估
|
||
|
||
**一次完整请求(6 视角,保留风格)**:
|
||
|
||
| 阶段 | 耗时 | 成本 |
|
||
|---|---|---|
|
||
| Trellis 重建 | ~30s | $0.041 |
|
||
| ffmpeg 抽 6 帧 | ~2s(本地) | 0 |
|
||
| ControlNet+IP-Adapter × 6(并发) | ~72s | $0.42 |
|
||
| **合计** | **~105s** | **~$0.46** |
|
||
|
||
参考:当前 Zero123++ 一次 ~$0.02,~15s。新管道贵约 23×、慢约 7×,但产出质量对建筑是跨代提升。
|
||
|
||
---
|
||
|
||
## 7 · 跨会话恢复指引
|
||
|
||
**新会话的 Agent 在开工前务必**:
|
||
|
||
1. 读本文件 **§0-§3**,建立完整技术背景
|
||
2. 读 `art-agent/backend/app/services/view_transform.py` 当前实现(Zero123++ 的做法作参照)
|
||
3. 读 `art-agent/backend/app/config.py` 的 `VIEW_TRANSFORM_MODELS` / `IMAGE_MODELS` 当前结构
|
||
4. 读 `art-agent/backend/app/agent/tools.py` 看 `transform_view` 工具定义
|
||
5. 按 **§4 的 Phase 顺序**推进,Phase 之间互相独立,完成一个交付一个
|
||
6. 每个 Phase 完成后在本文件对应 checkbox 打勾(改 `[ ]` 为 `[x]`),并在本节底部追加简短进度 note
|
||
|
||
**不要一次 Phase 1-6 全做完再交付**——每个 Phase 完成后停下来让用户测试/审阅。
|
||
|
||
### 进度日志
|
||
|
||
<!-- 格式:YYYY-MM-DD 会话简述 → 完成了 Px-y 的哪些 item -->
|
||
|
||
- 2026-04-20 · 会话"Zero123++ 切图诊断 + 方案调研" → 完成 §2 全部 schema 查证,写入本 SPEC。尚未开工 Phase 1。
|
||
- 2026-04-20 · 会话"按计划实施 Mesh Pipeline" → 完成 Phase 1(P1-1 ~ P1-5):新增 `mesh_generator.py`、`config.py` 注册 trellis/hunyuan3d(后者 enabled=False)、`view_transform.py` 按 `pipeline` 字段分流、`tools.py` 扩 `model_id/azimuths/preserve_style` 参数,额外提供 `scripts/test_trellis.py` 供用户手动验收。
|
||
- 2026-04-20 16:15 · **Checkpoint 1 通过** → Agent 运行 `scripts/test_trellis.py` 实测发现:
|
||
- ✅ Trellis 调用链路完全打通:Replicate token / 代理 / httpx 下载 / GENERATED_DIR 落盘均正常
|
||
- ✅ `.glb` (1.89MB) 下载正常
|
||
- ⚠️ 意外发现:该部署版 `color_video` / `normal_video` 字段**恒为 null**,只有 `combined_video` 可用(color+normal 左右并排 mp4 ~450KB)
|
||
- 修订 §2.1 输出字段表为实测版本,P2-2 任务改为"从 combined_video 抽帧 + 切左右半",已记 PF-20260420-1600 踩坑
|
||
- Phase 1 verdict:**functionally green**,可以开 Phase 2
|
||
- 2026-04-20 · 会话"继续执行 Phase 2" → 完成 Phase 2 全部 item(P2-1 ~ P2-4):
|
||
- 放弃系统 ffmpeg,改用 `imageio-ffmpeg` 自带二进制(22.6MB wheel,Win amd64 下已测 v4.2.2 ok)
|
||
- 新增 `services/video_frame_extractor.py` — 一次全帧抽取 + 按索引挑 + PIL 切左右半的极简实现
|
||
- `view_transform.py` 的 mesh 管道调度器现已走到 Stage 2,返回的 `images[]` 含 `{url, azimuth, elevation, normal_url, frame_idx}`
|
||
- 新增 `scripts/test_frame_extract.py` 供用户复用已有 combined_video 做 Checkpoint 2 验收(无需再花钱跑 Trellis)
|
||
- Agent 自测:120 帧 → 6 视角 frame_idx 均匀分布 (0/20/40/60/80/100),所有切图 512×512 方形,逻辑正确
|
||
- **等待 Checkpoint 2 用户验收**:跑 `python scripts/test_frame_extract.py` 并目视检查 `generated/view_*_color.png` 6 张图的角度顺序
|
||
- 2026-04-20 · 会话"继续执行 Phase 3(风格还原)" → 完成 P3-1 ~ P3-3:
|
||
- `config.py` 注册内部模型 `ip-adapter-controlnet-depth`(`internal=True`),并让 `get_image_models_list()` 自动过滤,不会污染前端下拉;新增 helper `get_style_restore_model_id()`
|
||
- 新增 `services/style_restorer.py`:`restore()` 单帧调用 + `restore_batch()` 带 Semaphore 并发 3 的批量接口;统一处理 FileOutput/list/str 的 Replicate 返回
|
||
- `view_transform._transform_view_mesh` 接入 Stage 3:当 `preserve_style=True` 对每一帧做 IP-Adapter+ControlNet 重绘;**单帧失败会自动回退为 Stage 2 color 帧**,不阻塞整体;`images[i]` 新增 `restyled` 布尔和 `color_url`(保留原 color 便于对比)
|
||
- 最终返回的 `_stage` 字段从 `stage2` 升级为 `stage3 (N/M restyled)`,便于前端 / 日志快速判断链路完整度
|
||
- 新增 `scripts/test_style_restore.py` 供 Checkpoint 3 用户验收:支持 `--single` 冒烟、`--prefix` 指定 normal 帧组、`--scale/--cn-scale` 覆盖参数,复用 Phase 2 已有帧无需再跑 Trellis
|
||
- **等待 Checkpoint 3 用户验收**:跑 `python scripts/test_style_restore.py <原图> --single` 做冒烟,通过后再全量 6 帧并调参(P3-4)
|
||
|
||
---
|
||
|
||
## 8 · 开放问题(留给未来决策)
|
||
|
||
- 前端角度输入 UX:要不要做一个"3D 预览转盘"让用户拖动选角度?(Phase 4+ 再说)
|
||
- 是否把 mesh `.glb` 作为资产保存到用户的 gallery?(涉及存储与用户系统对接)
|
||
- Hunyuan3D-2 路线的 Windows 渲染器:pyrender vs Blender 的选型需要一次实测才能决定
|
||
- 是否对同一原图的重复请求加缓存?(Trellis 结果相对稳定,可按 image_hash 缓存 .glb)
|