save code

This commit is contained in:
xsl
2026-05-05 21:44:42 +08:00
parent f8b1e567c1
commit bd1b7e66c8
45 changed files with 2953 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
"""
解析 face_picture_3dmax.obj,得到顶点 UV 和三角形索引。
OBJ 约定:v 顶点 / vt UV / vn 法线,f 三元组 v/vt/vn 索引(1-based)。
本服务只需要每个顶点的 UV 和三角形顶点索引——3D 顶点位置在 SDK 里也只是
占位(运行时被 MediaPipe landmark 覆盖),这里同样无需读 v。
★ 与 vulkan/FaceApp.cpp::LoadOBJ 一致,UV 的 V 分量取 (1 - v),把 OBJ 的
bottom-up 约定翻成 top-down,与图像(OpenCV/Pillow)一致。
"""
from dataclasses import dataclass
from pathlib import Path
import numpy as np
@dataclass
class FaceMesh:
# (468, 2) float32, 每个顶点的 UVV 已翻转,∈ [0,1]²
uvs: np.ndarray
# (852, 3) int32, 每个三角形的 3 个顶点索引(0-based,指向 uvs 第一维)
triangles: np.ndarray
def load_face_mesh(obj_path: str | Path) -> FaceMesh:
obj_path = Path(obj_path)
text = obj_path.read_text(encoding="utf-8", errors="ignore")
pos_count = 0
uv_list: list[tuple[float, float]] = []
# OBJ 的 face 用 v/vt/vn,三个索引可能不同;但本工程的 obj 三者一一对齐
# pos_index == uv_index == normal_index),所以我们只看 v 索引来索引 UV
# 列表也成立。这里仍然显式校验以防换 obj 时出错。
triangles: list[tuple[int, int, int]] = []
for raw in text.splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
tag = parts[0]
if tag == "v":
pos_count += 1
elif tag == "vt":
u = float(parts[1])
v = float(parts[2])
uv_list.append((u, 1.0 - v)) # V 翻转
elif tag == "f":
if len(parts) != 4:
raise ValueError(f"only triangle faces supported, got: {line}")
tri: list[int] = []
for token in parts[1:]:
# 形如 "v/vt/vn" 或 "v//vn" 或 "v"
seg = token.split("/")
v_idx = int(seg[0]) - 1 # OBJ 1-based → 0-based
vt_idx = int(seg[1]) - 1 if len(seg) > 1 and seg[1] else v_idx
if v_idx != vt_idx:
raise ValueError(
f"this loader assumes pos_idx == uv_idx, "
f"got v={v_idx + 1} vt={vt_idx + 1} in line: {line}"
)
tri.append(v_idx)
triangles.append((tri[0], tri[1], tri[2]))
if pos_count != len(uv_list):
raise ValueError(
f"vertex count {pos_count} != uv count {len(uv_list)}; "
"obj loader assumes 1:1 correspondence"
)
uvs = np.asarray(uv_list, dtype=np.float32)
tris = np.asarray(triangles, dtype=np.int32)
return FaceMesh(uvs=uvs, triangles=tris)