save code
This commit is contained in:
@@ -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, 每个顶点的 UV,V 已翻转,∈ [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)
|
||||
Reference in New Issue
Block a user