Files
EPEEAIKit/docs/art-agent/VIEW-TRANSFORM-MESH-PIPELINE-PLAN.md
2026-04-20 21:52:35 +08:00

464 lines
23 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.
# 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_videoHunyuan 贵 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
- **不返回预渲染视频** → 必须额外实现阶段 2pyrender / 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 # [新增] 阶段 3ControlNet + 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 1Trellis 接入(预计 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 2ffmpeg 抽帧(预计 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 5Hunyuan3D-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 1P1-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 全部 itemP2-1 ~ P2-4
- 放弃系统 ffmpeg改用 `imageio-ffmpeg` 自带二进制22.6MB wheelWin 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