Spaces:
mucusz
/
Runtime error

Yuliang's picture
Support TEXTure
487ee6d
raw
history blame
22.7 kB
import os
import pickle
from collections import OrderedDict
import cv2
import numpy as np
import torch
import torch.nn.functional as F
# ---------------------------- process/generate vertices, normals, faces
def generate_triangles(h, w, mask=None):
"""
quad layout:
0 1 ... w-1
w w+1
.
w*h
"""
triangles = []
margin = 0
for x in range(margin, w - 1 - margin):
for y in range(margin, h - 1 - margin):
triangle0 = [y * w + x, y * w + x + 1, (y + 1) * w + x]
triangle1 = [y * w + x + 1, (y + 1) * w + x + 1, (y + 1) * w + x]
triangles.append(triangle0)
triangles.append(triangle1)
triangles = np.array(triangles)
triangles = triangles[:, [0, 2, 1]]
return triangles
def face_vertices(vertices, faces):
"""
borrowed from https://github.com/daniilidis-group/neural_renderer/blob/master/neural_renderer/vertices_to_faces.py
:param vertices: [batch size, number of vertices, 3]
:param faces: [batch size, number of faces, 3]
:return: [batch size, number of faces, 3, 3]
"""
assert vertices.ndimension() == 3
assert faces.ndimension() == 3
assert vertices.shape[0] == faces.shape[0]
assert vertices.shape[2] == 3
assert faces.shape[2] == 3
bs, nv = vertices.shape[:2]
bs, nf = faces.shape[:2]
device = vertices.device
faces = faces + (torch.arange(bs, dtype=torch.int32).to(device) * nv)[:, None, None]
vertices = vertices.reshape((bs * nv, 3))
# pytorch only supports long and byte tensors for indexing
return vertices[faces.long()]
def vertex_normals(vertices, faces):
"""
borrowed from https://github.com/daniilidis-group/neural_renderer/blob/master/neural_renderer/vertices_to_faces.py
:param vertices: [batch size, number of vertices, 3]
:param faces: [batch size, number of faces, 3]
:return: [batch size, number of vertices, 3]
"""
assert vertices.ndimension() == 3
assert faces.ndimension() == 3
assert vertices.shape[0] == faces.shape[0]
assert vertices.shape[2] == 3
assert faces.shape[2] == 3
bs, nv = vertices.shape[:2]
bs, nf = faces.shape[:2]
device = vertices.device
normals = torch.zeros(bs * nv, 3).to(device)
faces = (
faces + (torch.arange(bs, dtype=torch.int32).to(device) * nv)[:, None, None]
) # expanded faces
vertices_faces = vertices.reshape((bs * nv, 3))[faces.long()]
faces = faces.reshape(-1, 3)
vertices_faces = vertices_faces.reshape(-1, 3, 3)
normals.index_add_(
0,
faces[:, 1].long(),
torch.cross(
vertices_faces[:, 2] - vertices_faces[:, 1],
vertices_faces[:, 0] - vertices_faces[:, 1],
),
)
normals.index_add_(
0,
faces[:, 2].long(),
torch.cross(
vertices_faces[:, 0] - vertices_faces[:, 2],
vertices_faces[:, 1] - vertices_faces[:, 2],
),
)
normals.index_add_(
0,
faces[:, 0].long(),
torch.cross(
vertices_faces[:, 1] - vertices_faces[:, 0],
vertices_faces[:, 2] - vertices_faces[:, 0],
),
)
normals = F.normalize(normals, eps=1e-6, dim=1)
normals = normals.reshape((bs, nv, 3))
# pytorch only supports long and byte tensors for indexing
return normals
def batch_orth_proj(X, camera):
"""
X is N x num_verts x 3
"""
camera = camera.clone().view(-1, 1, 3)
X_trans = X[:, :, :2] + camera[:, :, 1:]
X_trans = torch.cat([X_trans, X[:, :, 2:]], 2)
Xn = camera[:, :, 0:1] * X_trans
return Xn
# borrowed from https://github.com/vchoutas/expose
DIM_FLIP = np.array([1, -1, -1], dtype=np.float32)
DIM_FLIP_TENSOR = torch.tensor([1, -1, -1], dtype=torch.float32)
def flip_pose(pose_vector, pose_format="rot-mat"):
if pose_format == "aa":
if torch.is_tensor(pose_vector):
dim_flip = DIM_FLIP_TENSOR
else:
dim_flip = DIM_FLIP
return (pose_vector.reshape(-1, 3) * dim_flip).reshape(-1)
elif pose_format == "rot-mat":
rot_mats = pose_vector.reshape(-1, 9).clone()
rot_mats[:, [1, 2, 3, 6]] *= -1
return rot_mats.view_as(pose_vector)
else:
raise ValueError(f"Unknown rotation format: {pose_format}")
# -------------------------------------- image processing
# ref: https://torchgeometry.readthedocs.io/en/latest/_modules/kornia/filters
def gaussian(window_size, sigma):
def gauss_fcn(x):
return -((x - window_size // 2)**2) / float(2 * sigma**2)
gauss = torch.stack([torch.exp(torch.tensor(gauss_fcn(x))) for x in range(window_size)])
return gauss / gauss.sum()
def get_gaussian_kernel(kernel_size: int, sigma: float):
r"""Function that returns Gaussian filter coefficients.
Args:
kernel_size (int): filter size. It should be odd and positive.
sigma (float): gaussian standard deviation.
Returns:
Tensor: 1D tensor with gaussian filter coefficients.
Shape:
- Output: :math:`(\text{kernel_size})`
Examples::
>>> kornia.image.get_gaussian_kernel(3, 2.5)
tensor([0.3243, 0.3513, 0.3243])
>>> kornia.image.get_gaussian_kernel(5, 1.5)
tensor([0.1201, 0.2339, 0.2921, 0.2339, 0.1201])
"""
if not isinstance(kernel_size, int) or kernel_size % 2 == 0 or kernel_size <= 0:
raise TypeError(
"kernel_size must be an odd positive integer. "
"Got {}".format(kernel_size)
)
window_1d = gaussian(kernel_size, sigma)
return window_1d
def get_gaussian_kernel2d(kernel_size, sigma):
r"""Function that returns Gaussian filter matrix coefficients.
Args:
kernel_size (Tuple[int, int]): filter sizes in the x and y direction.
Sizes should be odd and positive.
sigma (Tuple[int, int]): gaussian standard deviation in the x and y
direction.
Returns:
Tensor: 2D tensor with gaussian filter matrix coefficients.
Shape:
- Output: :math:`(\text{kernel_size}_x, \text{kernel_size}_y)`
Examples::
>>> kornia.image.get_gaussian_kernel2d((3, 3), (1.5, 1.5))
tensor([[0.0947, 0.1183, 0.0947],
[0.1183, 0.1478, 0.1183],
[0.0947, 0.1183, 0.0947]])
>>> kornia.image.get_gaussian_kernel2d((3, 5), (1.5, 1.5))
tensor([[0.0370, 0.0720, 0.0899, 0.0720, 0.0370],
[0.0462, 0.0899, 0.1123, 0.0899, 0.0462],
[0.0370, 0.0720, 0.0899, 0.0720, 0.0370]])
"""
if not isinstance(kernel_size, tuple) or len(kernel_size) != 2:
raise TypeError("kernel_size must be a tuple of length two. Got {}".format(kernel_size))
if not isinstance(sigma, tuple) or len(sigma) != 2:
raise TypeError("sigma must be a tuple of length two. Got {}".format(sigma))
ksize_x, ksize_y = kernel_size
sigma_x, sigma_y = sigma
kernel_x = get_gaussian_kernel(ksize_x, sigma_x)
kernel_y = get_gaussian_kernel(ksize_y, sigma_y)
kernel_2d = torch.matmul(kernel_x.unsqueeze(-1), kernel_y.unsqueeze(-1).t())
return kernel_2d
def gaussian_blur(x, kernel_size=(5, 5), sigma=(1.3, 1.3)):
b, c, h, w = x.shape
kernel = get_gaussian_kernel2d(kernel_size, sigma).to(x.device).to(x.dtype)
kernel = kernel.repeat(c, 1, 1, 1)
padding = [(k - 1) // 2 for k in kernel_size]
return F.conv2d(x, kernel, padding=padding, stride=1, groups=c)
def _compute_binary_kernel(window_size):
r"""Creates a binary kernel to extract the patches. If the window size
is HxW will create a (H*W)xHxW kernel.
"""
window_range = window_size[0] * window_size[1]
kernel: torch.Tensor = torch.zeros(window_range, window_range)
for i in range(window_range):
kernel[i, i] += 1.0
return kernel.view(window_range, 1, window_size[0], window_size[1])
def median_blur(x, kernel_size=(3, 3)):
b, c, h, w = x.shape
kernel = _compute_binary_kernel(kernel_size).to(x.device).to(x.dtype)
kernel = kernel.repeat(c, 1, 1, 1)
padding = [(k - 1) // 2 for k in kernel_size]
features = F.conv2d(x, kernel, padding=padding, stride=1, groups=c)
features = features.view(b, c, -1, h, w)
median = torch.median(features, dim=2)[0]
return median
def get_laplacian_kernel2d(kernel_size: int):
r"""Function that returns Gaussian filter matrix coefficients.
Args:
kernel_size (int): filter size should be odd.
Returns:
Tensor: 2D tensor with laplacian filter matrix coefficients.
Shape:
- Output: :math:`(\text{kernel_size}_x, \text{kernel_size}_y)`
Examples::
>>> kornia.image.get_laplacian_kernel2d(3)
tensor([[ 1., 1., 1.],
[ 1., -8., 1.],
[ 1., 1., 1.]])
>>> kornia.image.get_laplacian_kernel2d(5)
tensor([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., -24., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.]])
"""
if not isinstance(kernel_size, int) or kernel_size % 2 == 0 or kernel_size <= 0:
raise TypeError("ksize must be an odd positive integer. Got {}".format(kernel_size))
kernel = torch.ones((kernel_size, kernel_size))
mid = kernel_size // 2
kernel[mid, mid] = 1 - kernel_size**2
kernel_2d: torch.Tensor = kernel
return kernel_2d
def laplacian(x):
# https://torchgeometry.readthedocs.io/en/latest/_modules/kornia/filters/laplacian.html
b, c, h, w = x.shape
kernel_size = 3
kernel = get_laplacian_kernel2d(kernel_size).to(x.device).to(x.dtype)
kernel = kernel.repeat(c, 1, 1, 1)
padding = (kernel_size - 1) // 2
return F.conv2d(x, kernel, padding=padding, stride=1, groups=c)
# -------------------------------------- io
def copy_state_dict(cur_state_dict, pre_state_dict, prefix="", load_name=None):
def _get_params(key):
key = prefix + key
if key in pre_state_dict:
return pre_state_dict[key]
return None
for k in cur_state_dict.keys():
if load_name is not None:
if load_name not in k:
continue
v = _get_params(k)
try:
if v is None:
# print('parameter {} not found'.format(k))
continue
cur_state_dict[k].copy_(v)
except:
# print('copy param {} failed'.format(k))
continue
def dict2obj(d):
# if isinstance(d, list):
# d = [dict2obj(x) for x in d]
if not isinstance(d, dict):
return d
class C(object):
pass
o = C()
for k in d:
o.__dict__[k] = dict2obj(d[k])
return o
# original saved file with DataParallel
def remove_module(state_dict):
# create new OrderedDict that does not contain `module.`
new_state_dict = OrderedDict()
for k, v in state_dict.items():
name = k[7:] # remove `module.`
new_state_dict[name] = v
return new_state_dict
def tensor2image(tensor):
image = tensor.detach().cpu().numpy()
image = image * 255.0
image = np.maximum(np.minimum(image, 255), 0)
image = image.transpose(1, 2, 0)[:, :, [2, 1, 0]]
return image.astype(np.uint8).copy()
def dict_tensor2npy(tensor_dict):
npy_dict = {}
for key in tensor_dict:
npy_dict[key] = tensor_dict[key][0].cpu().numpy()
return npy_dict
def load_config(cfg_file):
import yaml
with open(cfg_file, "r") as f:
cfg = yaml.load(f, Loader=yaml.FullLoader)
return cfg
def move_dict_to_device(dict, device, tensor2float=False):
for k, v in dict.items():
if isinstance(v, torch.Tensor):
if tensor2float:
dict[k] = v.float().to(device)
else:
dict[k] = v.to(device)
def write_obj(
obj_name,
vertices,
faces,
colors=None,
texture=None,
uvcoords=None,
uvfaces=None,
inverse_face_order=False,
normal_map=None,
):
"""Save 3D face model with texture.
borrowed from https://github.com/YadiraF/PRNet/blob/master/utils/write.py
Args:
obj_name: str
vertices: shape = (nver, 3)
colors: shape = (nver, 3)
faces: shape = (ntri, 3)
texture: shape = (uv_size, uv_size, 3)
uvcoords: shape = (nver, 2) max value<=1
"""
if obj_name.split(".")[-1] != "obj":
obj_name = obj_name + ".obj"
mtl_name = obj_name.replace(".obj", ".mtl")
texture_name = obj_name.replace(".obj", ".png")
material_name = "FaceTexture"
faces = faces.copy()
# mesh lab start with 1, python/c++ start from 0
faces += 1
if inverse_face_order:
faces = faces[:, [2, 1, 0]]
if uvfaces is not None:
uvfaces = uvfaces[:, [2, 1, 0]]
# write obj
with open(obj_name, "w") as f:
if texture is not None:
f.write("mtllib %s\n\n" % os.path.basename(mtl_name))
# write vertices
if colors is None:
for i in range(vertices.shape[0]):
f.write("v {} {} {}\n".format(vertices[i, 0], vertices[i, 1], vertices[i, 2]))
else:
for i in range(vertices.shape[0]):
f.write(
"v {} {} {} {} {} {}\n".format(
vertices[i, 0],
vertices[i, 1],
vertices[i, 2],
colors[i, 0],
colors[i, 1],
colors[i, 2],
)
)
# write uv coords
if texture is None:
for i in range(faces.shape[0]):
f.write("f {} {} {}\n".format(faces[i, 0], faces[i, 1], faces[i, 2]))
else:
for i in range(uvcoords.shape[0]):
f.write("vt {} {}\n".format(uvcoords[i, 0], uvcoords[i, 1]))
f.write("usemtl %s\n" % material_name)
# write f: ver ind/ uv ind
uvfaces = uvfaces + 1
for i in range(faces.shape[0]):
f.write(
"f {}/{} {}/{} {}/{}\n".format(
faces[i, 0],
uvfaces[i, 0],
faces[i, 1],
uvfaces[i, 1],
faces[i, 2],
uvfaces[i, 2],
)
)
# write mtl
with open(mtl_name, "w") as f:
f.write("newmtl %s\n" % material_name)
s = "map_Kd {}\n".format(os.path.basename(texture_name)) # map to image
f.write(s)
if normal_map is not None:
if torch.is_tensor(normal_map):
normal_map = normal_map.detach().cpu().numpy().squeeze()
normal_map = np.transpose(normal_map, (1, 2, 0))
name, _ = os.path.splitext(obj_name)
normal_name = f"{name}_normals.png"
f.write(f"disp {normal_name}")
out_normal_map = normal_map / (
np.linalg.norm(normal_map, axis=-1, keepdims=True) + 1e-9
)
out_normal_map = (out_normal_map + 1) * 0.5
cv2.imwrite(normal_name, (out_normal_map * 255).astype(np.uint8)[:, :, ::-1])
cv2.imwrite(texture_name, texture)
def save_pkl(savepath, params, ind=0):
out_data = {}
for k, v in params.items():
if torch.is_tensor(v):
out_data[k] = v[ind].detach().cpu().numpy()
else:
out_data[k] = v
with open(savepath, "wb") as f:
pickle.dump(out_data, f, protocol=2)
# load obj, similar to load_obj from pytorch3d
def load_obj(obj_filename):
"""Ref: https://github.com/facebookresearch/pytorch3d/blob/25c065e9dafa90163e7cec873dbb324a637c68b7/pytorch3d/io/obj_io.py
Load a mesh from a file-like object.
"""
with open(obj_filename, "r") as f:
lines = [line.strip() for line in f]
verts, uvcoords = [], []
faces, uv_faces = [], []
# startswith expects each line to be a string. If the file is read in as
# bytes then first decode to strings.
if lines and isinstance(lines[0], bytes):
lines = [el.decode("utf-8") for el in lines]
for line in lines:
tokens = line.strip().split()
if line.startswith("v "): # Line is a vertex.
vert = [float(x) for x in tokens[1:4]]
if len(vert) != 3:
msg = "Vertex %s does not have 3 values. Line: %s"
raise ValueError(msg % (str(vert), str(line)))
verts.append(vert)
elif line.startswith("vt "): # Line is a texture.
tx = [float(x) for x in tokens[1:3]]
if len(tx) != 2:
raise ValueError(
"Texture %s does not have 2 values. Line: %s" % (str(tx), str(line))
)
uvcoords.append(tx)
elif line.startswith("f "): # Line is a face.
# Update face properties info.
face = tokens[1:]
face_list = [f.split("/") for f in face]
for vert_props in face_list:
# Vertex index.
faces.append(int(vert_props[0]))
if len(vert_props) > 1:
if vert_props[1] != "":
# Texture index is present e.g. f 4/1/1.
uv_faces.append(int(vert_props[1]))
verts = torch.tensor(verts, dtype=torch.float32)
uvcoords = torch.tensor(uvcoords, dtype=torch.float32)
faces = torch.tensor(faces, dtype=torch.long)
faces = faces.reshape(-1, 3) - 1
uv_faces = torch.tensor(uv_faces, dtype=torch.long)
uv_faces = uv_faces.reshape(-1, 3) - 1
return (verts, uvcoords, faces, uv_faces)
# ---------------------------------- visualization
def draw_rectangle(img, bbox, bbox_color=(255, 255, 255), thickness=3, is_opaque=False, alpha=0.5):
"""Draws the rectangle around the object
borrowed from: https://bbox-visualizer.readthedocs.io/en/latest/_modules/bbox_visualizer/bbox_visualizer.html
Parameters
----------
img : ndarray
the actual image
bbox : list
a list containing x_min, y_min, x_max and y_max of the rectangle positions
bbox_color : tuple, optional
the color of the box, by default (255,255,255)
thickness : int, optional
thickness of the outline of the box, by default 3
is_opaque : bool, optional
if False, draws a solid rectangular outline. Else, a filled rectangle which is semi transparent, by default False
alpha : float, optional
strength of the opacity, by default 0.5
Returns
-------
ndarray
the image with the bounding box drawn
"""
output = img.copy()
if not is_opaque:
cv2.rectangle(output, (bbox[0], bbox[1]), (bbox[2], bbox[3]), bbox_color, thickness)
else:
overlay = img.copy()
cv2.rectangle(overlay, (bbox[0], bbox[1]), (bbox[2], bbox[3]), bbox_color, -1)
# cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0, output)
return output
def plot_bbox(image, bbox):
"""Draw bbox
Args:
image: the input image
bbox: [left, top, right, bottom]
"""
image = cv2.rectangle(
image.copy(), (bbox[1], bbox[0]), (bbox[3], bbox[2]), [0, 255, 0], thickness=3
)
# image = draw_rectangle(image, bbox, bbox_color=[0,255,0])
return image
end_list = np.array([17, 22, 27, 42, 48, 31, 36, 68], dtype=np.int32) - 1
def plot_kpts(image, kpts, color="r"):
"""Draw 68 key points
Args:
image: the input image
kpt: (68, 3).
"""
kpts = kpts.copy().astype(np.int32)
if color == "r":
c = (255, 0, 0)
elif color == "g":
c = (0, 255, 0)
elif color == "b":
c = (255, 0, 0)
image = image.copy()
kpts = kpts.copy()
for i in range(kpts.shape[0]):
st = kpts[i, :2]
if kpts.shape[1] == 4:
if kpts[i, 3] > 0.5:
c = (0, 255, 0)
else:
c = (0, 0, 255)
image = cv2.circle(image, (st[0], st[1]), 1, c, 2)
if i in end_list:
continue
ed = kpts[i + 1, :2]
image = cv2.line(image, (st[0], st[1]), (ed[0], ed[1]), (255, 255, 255), 1)
return image
def plot_verts(image, kpts, color="r"):
"""Draw 68 key points
Args:
image: the input image
kpt: (68, 3).
"""
kpts = kpts.copy().astype(np.int32)
if color == "r":
c = (255, 0, 0)
elif color == "g":
c = (0, 255, 0)
elif color == "b":
c = (0, 0, 255)
elif color == "y":
c = (0, 255, 255)
image = image.copy()
for i in range(kpts.shape[0]):
st = kpts[i, :2]
image = cv2.circle(image, (st[0], st[1]), 1, c, 5)
return image
def tensor_vis_landmarks(images, landmarks, gt_landmarks=None, color="g", isScale=True):
# visualize landmarks
vis_landmarks = []
images = images.cpu().numpy()
predicted_landmarks = landmarks.detach().cpu().numpy()
if gt_landmarks is not None:
gt_landmarks_np = gt_landmarks.detach().cpu().numpy()
for i in range(images.shape[0]):
image = images[i]
image = image.transpose(1, 2, 0)[:, :, [2, 1, 0]].copy()
image = image * 255
if isScale:
predicted_landmark = (predicted_landmarks[i] * image.shape[0] / 2 + image.shape[0] / 2)
else:
predicted_landmark = predicted_landmarks[i]
if predicted_landmark.shape[0] == 68:
image_landmarks = plot_kpts(image, predicted_landmark, color)
if gt_landmarks is not None:
image_landmarks = plot_verts(
image_landmarks,
gt_landmarks_np[i] * image.shape[0] / 2 + image.shape[0] / 2,
"r",
)
else:
image_landmarks = plot_verts(image, predicted_landmark, color)
if gt_landmarks is not None:
image_landmarks = plot_verts(
image_landmarks,
gt_landmarks_np[i] * image.shape[0] / 2 + image.shape[0] / 2,
"r",
)
vis_landmarks.append(image_landmarks)
vis_landmarks = np.stack(vis_landmarks)
vis_landmarks = (
torch.from_numpy(vis_landmarks[:, :, :, [2, 1, 0]].transpose(0, 3, 1, 2)) / 255.0
) # , dtype=torch.float32)
return vis_landmarks