108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
"""Phase 2 验收脚本:独立验证 combined_video 抽帧 + 左右切分逻辑。
|
||
|
||
不需要再跑一次 Trellis(省钱),直接用 generated/ 下已有的
|
||
`trellis_combined_*.mp4` 做验证。
|
||
|
||
用法(在 backend 目录激活 venv 之后):
|
||
python scripts/test_frame_extract.py
|
||
python scripts/test_frame_extract.py generated/trellis_combined_211176d3940b.mp4
|
||
|
||
成功标准:
|
||
- 退出码 0
|
||
- 打印的 color_png / normal_png 均为 /generated/view_xxx.png
|
||
- 到 backend/generated/ 能看到 6 对 color + normal 文件(12 张)且尺寸接近正方形
|
||
- 目视检查:每组 color 图按 0/60/120/180/240/300° 依次环绕建筑一圈
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
_BACKEND_ROOT = Path(__file__).resolve().parent.parent
|
||
if str(_BACKEND_ROOT) not in sys.path:
|
||
sys.path.insert(0, str(_BACKEND_ROOT))
|
||
|
||
from dotenv import load_dotenv # noqa: E402
|
||
|
||
load_dotenv(_BACKEND_ROOT / ".env", override=True)
|
||
|
||
from app.services import video_frame_extractor # noqa: E402
|
||
from app.services.image_gen import GENERATED_DIR # noqa: E402
|
||
|
||
|
||
def _pick_default_video() -> str:
|
||
candidates = sorted(
|
||
GENERATED_DIR.glob("trellis_combined_*.mp4"),
|
||
key=lambda p: p.stat().st_mtime,
|
||
reverse=True,
|
||
)
|
||
if not candidates:
|
||
raise FileNotFoundError(
|
||
"generated/ 下找不到 trellis_combined_*.mp4;请先跑一次 Phase 1 "
|
||
"(scripts/test_trellis.py)或显式传入视频路径"
|
||
)
|
||
return f"/generated/{candidates[0].name}"
|
||
|
||
|
||
async def main() -> int:
|
||
video_arg = sys.argv[1] if len(sys.argv) > 1 else _pick_default_video()
|
||
# 允许传相对路径或纯文件名
|
||
if not video_arg.startswith("/generated/"):
|
||
if video_arg.startswith("generated/"):
|
||
video_arg = "/" + video_arg.replace("\\", "/")
|
||
elif not video_arg.startswith("/") and not Path(video_arg).is_absolute():
|
||
# 如果只给了个文件名,假定在 generated/ 下
|
||
if (GENERATED_DIR / video_arg).exists():
|
||
video_arg = f"/generated/{video_arg}"
|
||
|
||
print(f"[test_frame_extract] 输入视频:{video_arg}")
|
||
print("[test_frame_extract] 抽取 6 视角:az=[0,60,120,180,240,300],el 全 0")
|
||
|
||
azimuths = [0, 60, 120, 180, 240, 300]
|
||
elevations = [0] * len(azimuths)
|
||
|
||
frames = await video_frame_extractor.extract_by_azimuth(
|
||
combined_video_path=video_arg,
|
||
azimuths=azimuths,
|
||
elevations=elevations,
|
||
total_rotation_deg=360,
|
||
)
|
||
|
||
print("[test_frame_extract] 抽帧结果:")
|
||
print(json.dumps(frames, ensure_ascii=False, indent=2))
|
||
|
||
# 简单完整性校验
|
||
all_exist = True
|
||
for f in frames:
|
||
for key in ("color_png", "normal_png"):
|
||
rel = f.get(key)
|
||
if not rel:
|
||
print(f"[test_frame_extract] 警告:帧 az={f['az']} 缺少 {key}")
|
||
all_exist = False
|
||
continue
|
||
local = GENERATED_DIR / Path(rel).name
|
||
size = local.stat().st_size if local.exists() else -1
|
||
status = "OK" if size > 0 else "MISSING"
|
||
print(f" [{status}] az={f['az']:3d} {key}: {rel} ({size} bytes)")
|
||
if size <= 0:
|
||
all_exist = False
|
||
|
||
if not all_exist:
|
||
print("[test_frame_extract] 部分文件缺失,判定失败")
|
||
return 1
|
||
|
||
print(
|
||
f"[test_frame_extract] 成功:共 {len(frames)} 个视角,"
|
||
f"color+normal 合计 {len(frames) * 2} 张已落盘到 {GENERATED_DIR}"
|
||
)
|
||
print(
|
||
"[test_frame_extract] 请目视检查:打开 generated/ 下任意 view_*_color.png,"
|
||
"确认 az=0 正面、az=180 背面、其余按顺序环绕;normal 帧应为蓝紫调法线图。"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(asyncio.run(main()))
|