Face mesh 데이터셋 중 오래됐지만 여전히 많이 쓰이는 데이터 중 하나인 facescape 데이터셋. TU model이라는 이름으로 topology를 맞춰놓은 데이터가 특히 사용하기 좋다.
TU model은 근데 생각보다 해상도(mesh vertex, face 수)가 높지 않은데 데이터 자체의 용량을 줄이기 위해서 coarse와 fine을 분리해놨기 때문이다.
그냥 obj 파일에 담겨있는 mesh는 coarse 즉 vertex 수도 적고 face도 적은 기본 template mesh를 registration해둔 결과다. 그 이외 fine detail은 displacement map담겨 있다.
displacement map (이하 dpmap)은 texture와 같이 uv 좌표계로 표현되어 있는 하나의 이미지인데, 그 값이 대응되는 mesh vertex의 SDF 값이다. mesh vertex의 uv값을 읽고 해당 dp 값을 얻은 뒤, normal 방향으로 이동시키면 더 정확한 vertex 위치를 얻어낼 수 있다.
이 때 mesh vertex수가 적어서 여전히 아쉬운 점이 있는데, mesh를 subdivision해서 4배, 16배로 부풀려서 dp 값대로 움직이는 행위를 반복하면 엄청 좋은 mesh를 얻어낼 수 있다.
정리해서 말하면 mesh subdivision + dp map 값으로 이동시켜서 최종 mesh를 얻어낼 수 있는게 facescape 데이터셋이다.
위 설명한 방식대로 최종 mesh를 얻어내는 코드는 다음과 같다. 먼저 필요한 함수 및 클래스다.
def MTL(filename):
contents = {}
mtl = None
for line in open(filename, "r"):
if line.startswith('#'): continue
values = line.split()
if not values: continue
if values[0] == 'newmtl':
mtl = contents[values[1]] = {}
elif mtl is None:
raise ValueError('mtl file doesn\'t start with newmtl stmt')
elif values[0] == 'map_Kd':
# load the texture referred to by this declaration
mtl[values[0]] = values[1]
else:
mtl[values[0]] = map(float, values[1:])
return contents
class OBJ:
def __init__(self, filename, swapyz=False):
"""Loads a Wavefront OBJ file. """
self.vertices = []
self.normals = []
self.texcoords = []
self.faces = []
self.adjacent_list = []
material = None
for line in open(filename, "r"):
if line.startswith('#'): continue
values = line.split()
if not values: continue
if values[0] == 'v':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.vertices.append(v)
elif values[0] == 'vn':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.normals.append(v)
elif values[0] == 'vt':
self.texcoords.append(list(map(float, values[1:3])))
elif values[0] in ('usemtl', 'usemat'):
material = values[1]
# elif values[0] == 'mtllib':
# self.mtl = MTL(os.path.dirname(filename) + '/' + os.path.basename(values[1]))
elif values[0] == 'f':
face = []
texcoords = []
norms = []
for v in values[1:]:
w = v.split('/')
face.append(int(w[0]))
if len(w) >= 2 and len(w[1]) > 0:
texcoords.append(int(w[1]))
else:
texcoords.append(0)
if len(w) >= 3 and len(w[2]) > 0:
norms.append(int(w[2]))
else:
norms.append(0)
self.faces.append((face, norms, texcoords, material))
def get_adjacent(self, index):
if not self.adjacent_list:
adjacent_list = [[] for i in range(len(self.vertices))]
for face in self.faces:
face_vertices, face_normals, face_texture_coords, material = face
adjacent_list[face_vertices[0] - 1].append(face_vertices[1] - 1)
adjacent_list[face_vertices[0] - 1].append(face_vertices[2] - 1)
adjacent_list[face_vertices[1] - 1].append(face_vertices[0] - 1)
adjacent_list[face_vertices[1] - 1].append(face_vertices[2] - 1)
adjacent_list[face_vertices[2] - 1].append(face_vertices[0] - 1)
adjacent_list[face_vertices[2] - 1].append(face_vertices[1] - 1)
adjacent_list = list(map(set, adjacent_list))
self.adjacent_list = list(map(list, adjacent_list))
return self.adjacent_list[index]
def subdiv(verts, tris, texcoords=None, face_index=None):
if face_index is None:
face_index = np.arange(len(tris))
else:
face_index = np.asanyarray(face_index)
# the (c, 3) int array of vertex indices
tris_subset = tris[face_index]
# find the unique edges of our faces subset
edges = np.sort(trimesh.remesh.faces_to_edges(tris_subset), axis=1)
unique, inverse = trimesh.remesh.grouping.unique_rows(edges)
# then only produce one midpoint per unique edge
mid = verts[edges[unique]].mean(axis=1)
mid_idx = inverse.reshape((-1, 3)) + len(verts)
# the new faces_subset with correct winding
f = np.column_stack([tris_subset[:, 0],
mid_idx[:, 0],
mid_idx[:, 2],
mid_idx[:, 0],
tris_subset[:, 1],
mid_idx[:, 1],
mid_idx[:, 2],
mid_idx[:, 1],
tris_subset[:, 2],
mid_idx[:, 0],
mid_idx[:, 1],
mid_idx[:, 2]]).reshape((-1, 3))
# add the 3 new faces_subset per old face
new_faces = np.vstack((tris, f[len(face_index):]))
# replace the old face with a smaller face
new_faces[face_index] = f[:len(face_index)]
new_vertices = np.vstack((verts, mid))
if texcoords is not None:
texcoords_mid = texcoords[edges[unique]].mean(axis=1)
new_texcoords = np.vstack((texcoords, texcoords_mid))
return new_vertices, new_faces, new_texcoords
return new_vertices, new_faces
def dpmap2verts(verts, tris, texcoords, dpmap, scale=0.914):
dpmap = np.array(dpmap).astype(int)
normals = np.zeros(verts.shape)
test = np.zeros(verts.shape)
tri_verts = verts[tris]
n0 = np.cross(tri_verts[::, 1] - tri_verts[::, 0], tri_verts[::, 2] - tri_verts[::, 0])
n0 = n0 / np.linalg.norm(n0, axis=1)[:, np.newaxis]
for i in range(tris.shape[0]):
normals[tris[i, 0]] += n0[i]
normals[tris[i, 1]] += n0[i]
normals[tris[i, 2]] += n0[i]
normals = normals / np.linalg.norm(normals, axis=1)[:, np.newaxis]
pos_u = dpmap.shape[0] - (texcoords[:, 1] * dpmap.shape[0]).astype(int)
pos_v = (texcoords[:, 0] * dpmap.shape[1]).astype(int)
pos_u[pos_u >= dpmap.shape[0]] = dpmap.shape[0] - 1
pos_v[pos_v >= dpmap.shape[1]] = dpmap.shape[1] - 1
verts += normals * (dpmap[pos_u, pos_v] - 32768)[:, np.newaxis] / 32768 * scale
return verts
그 다음 아래처럼 움직여주면 된다.
mesh = OBJ(obj_path)
tris = []
vert_texcoords = np.zeros((len(mesh.vertices), 2))
for face in mesh.faces:
vertices, normals, texture_coords, material = face
tris.append([vertices[0] - 1, vertices[1] - 1, vertices[2] - 1])
for i in range(len(vertices)):
vert_texcoords[vertices[i] - 1] = mesh.texcoords[texture_coords[i] - 1]
tris = np.array(tris)
verts = np.array(mesh.vertices)
raw_verts = verts.copy()
verts = verts / np.linalg.norm(verts[12280] - verts[12320]) * 60
### CHANGE THIS NUMBER
num_subdiv = 1
for _ in range(num_subdiv):
verts, tris, vert_texcoords = subdiv(verts, tris, vert_texcoords)
if os.path.exists(dp_path):
dpmap = Image.open(dp_path)
dpmap = np.array(dpmap)
verts = dpmap2verts(verts, tris, vert_texcoords, dpmap)
verts = verts / 60 * np.linalg.norm(raw_verts[12280] - raw_verts[12320])
'Knowhow > Vision' 카테고리의 다른 글
Facescape 모델 68 keypoint/landmark index (0) | 2024.08.28 |
---|---|
obj 파일 v, vt, f 등 직접 저장하기, obj save (0) | 2024.08.28 |
Unit cube vertices, faces index (pytorch3d cube 만들기) (0) | 2024.08.05 |
BFM face model 파라미터로 변형하기 (cropped BFM 2009 버전 예시) (0) | 2024.07.30 |
Open3d mesh uv coordinate (0) | 2024.07.30 |