|
from __future__ import print_function
|
|
import os
|
|
import sys
|
|
import time
|
|
import torch
|
|
import math
|
|
import numpy as np
|
|
import cv2
|
|
|
|
|
|
def _gaussian(
|
|
size=3, sigma=0.25, amplitude=1, normalize=False, width=None,
|
|
height=None, sigma_horz=None, sigma_vert=None, mean_horz=0.5,
|
|
mean_vert=0.5):
|
|
|
|
if width is None:
|
|
width = size
|
|
if height is None:
|
|
height = size
|
|
if sigma_horz is None:
|
|
sigma_horz = sigma
|
|
if sigma_vert is None:
|
|
sigma_vert = sigma
|
|
center_x = mean_horz * width + 0.5
|
|
center_y = mean_vert * height + 0.5
|
|
gauss = np.empty((height, width), dtype=np.float32)
|
|
|
|
for i in range(height):
|
|
for j in range(width):
|
|
gauss[i][j] = amplitude * math.exp(-(math.pow((j + 1 - center_x) / (
|
|
sigma_horz * width), 2) / 2.0 + math.pow((i + 1 - center_y) / (sigma_vert * height), 2) / 2.0))
|
|
if normalize:
|
|
gauss = gauss / np.sum(gauss)
|
|
return gauss
|
|
|
|
|
|
def draw_gaussian(image, point, sigma):
|
|
|
|
ul = [math.floor(point[0] - 3 * sigma), math.floor(point[1] - 3 * sigma)]
|
|
br = [math.floor(point[0] + 3 * sigma), math.floor(point[1] + 3 * sigma)]
|
|
if (ul[0] > image.shape[1] or ul[1] > image.shape[0] or br[0] < 1 or br[1] < 1):
|
|
return image
|
|
size = 6 * sigma + 1
|
|
g = _gaussian(size)
|
|
g_x = [int(max(1, -ul[0])), int(min(br[0], image.shape[1])) - int(max(1, ul[0])) + int(max(1, -ul[0]))]
|
|
g_y = [int(max(1, -ul[1])), int(min(br[1], image.shape[0])) - int(max(1, ul[1])) + int(max(1, -ul[1]))]
|
|
img_x = [int(max(1, ul[0])), int(min(br[0], image.shape[1]))]
|
|
img_y = [int(max(1, ul[1])), int(min(br[1], image.shape[0]))]
|
|
assert (g_x[0] > 0 and g_y[1] > 0)
|
|
image[img_y[0] - 1:img_y[1], img_x[0] - 1:img_x[1]
|
|
] = image[img_y[0] - 1:img_y[1], img_x[0] - 1:img_x[1]] + g[g_y[0] - 1:g_y[1], g_x[0] - 1:g_x[1]]
|
|
image[image > 1] = 1
|
|
return image
|
|
|
|
|
|
def transform(point, center, scale, resolution, invert=False):
|
|
"""Generate and affine transformation matrix.
|
|
|
|
Given a set of points, a center, a scale and a targer resolution, the
|
|
function generates and affine transformation matrix. If invert is ``True``
|
|
it will produce the inverse transformation.
|
|
|
|
Arguments:
|
|
point {torch.tensor} -- the input 2D point
|
|
center {torch.tensor or numpy.array} -- the center around which to perform the transformations
|
|
scale {float} -- the scale of the face/object
|
|
resolution {float} -- the output resolution
|
|
|
|
Keyword Arguments:
|
|
invert {bool} -- define wherever the function should produce the direct or the
|
|
inverse transformation matrix (default: {False})
|
|
"""
|
|
_pt = torch.ones(3)
|
|
_pt[0] = point[0]
|
|
_pt[1] = point[1]
|
|
|
|
h = 200.0 * scale
|
|
t = torch.eye(3)
|
|
t[0, 0] = resolution / h
|
|
t[1, 1] = resolution / h
|
|
t[0, 2] = resolution * (-center[0] / h + 0.5)
|
|
t[1, 2] = resolution * (-center[1] / h + 0.5)
|
|
|
|
if invert:
|
|
t = torch.inverse(t)
|
|
|
|
new_point = (torch.matmul(t, _pt))[0:2]
|
|
|
|
return new_point.int()
|
|
|
|
|
|
def crop(image, center, scale, resolution=256.0):
|
|
"""Center crops an image or set of heatmaps
|
|
|
|
Arguments:
|
|
image {numpy.array} -- an rgb image
|
|
center {numpy.array} -- the center of the object, usually the same as of the bounding box
|
|
scale {float} -- scale of the face
|
|
|
|
Keyword Arguments:
|
|
resolution {float} -- the size of the output cropped image (default: {256.0})
|
|
|
|
Returns:
|
|
[type] -- [description]
|
|
"""
|
|
""" Crops the image around the center. Input is expected to be an np.ndarray """
|
|
ul = transform([1, 1], center, scale, resolution, True)
|
|
br = transform([resolution, resolution], center, scale, resolution, True)
|
|
|
|
if image.ndim > 2:
|
|
newDim = np.array([br[1] - ul[1], br[0] - ul[0],
|
|
image.shape[2]], dtype=np.int32)
|
|
newImg = np.zeros(newDim, dtype=np.uint8)
|
|
else:
|
|
newDim = np.array([br[1] - ul[1], br[0] - ul[0]], dtype=np.int)
|
|
newImg = np.zeros(newDim, dtype=np.uint8)
|
|
ht = image.shape[0]
|
|
wd = image.shape[1]
|
|
newX = np.array(
|
|
[max(1, -ul[0] + 1), min(br[0], wd) - ul[0]], dtype=np.int32)
|
|
newY = np.array(
|
|
[max(1, -ul[1] + 1), min(br[1], ht) - ul[1]], dtype=np.int32)
|
|
oldX = np.array([max(1, ul[0] + 1), min(br[0], wd)], dtype=np.int32)
|
|
oldY = np.array([max(1, ul[1] + 1), min(br[1], ht)], dtype=np.int32)
|
|
newImg[newY[0] - 1:newY[1], newX[0] - 1:newX[1]
|
|
] = image[oldY[0] - 1:oldY[1], oldX[0] - 1:oldX[1], :]
|
|
newImg = cv2.resize(newImg, dsize=(int(resolution), int(resolution)),
|
|
interpolation=cv2.INTER_LINEAR)
|
|
return newImg
|
|
|
|
|
|
def get_preds_fromhm(hm, center=None, scale=None):
|
|
"""Obtain (x,y) coordinates given a set of N heatmaps. If the center
|
|
and the scale is provided the function will return the points also in
|
|
the original coordinate frame.
|
|
|
|
Arguments:
|
|
hm {torch.tensor} -- the predicted heatmaps, of shape [B, N, W, H]
|
|
|
|
Keyword Arguments:
|
|
center {torch.tensor} -- the center of the bounding box (default: {None})
|
|
scale {float} -- face scale (default: {None})
|
|
"""
|
|
max, idx = torch.max(
|
|
hm.view(hm.size(0), hm.size(1), hm.size(2) * hm.size(3)), 2)
|
|
idx += 1
|
|
preds = idx.view(idx.size(0), idx.size(1), 1).repeat(1, 1, 2).float()
|
|
preds[..., 0].apply_(lambda x: (x - 1) % hm.size(3) + 1)
|
|
preds[..., 1].add_(-1).div_(hm.size(2)).floor_().add_(1)
|
|
|
|
for i in range(preds.size(0)):
|
|
for j in range(preds.size(1)):
|
|
hm_ = hm[i, j, :]
|
|
pX, pY = int(preds[i, j, 0]) - 1, int(preds[i, j, 1]) - 1
|
|
if pX > 0 and pX < 63 and pY > 0 and pY < 63:
|
|
diff = torch.FloatTensor(
|
|
[hm_[pY, pX + 1] - hm_[pY, pX - 1],
|
|
hm_[pY + 1, pX] - hm_[pY - 1, pX]])
|
|
preds[i, j].add_(diff.sign_().mul_(.25))
|
|
|
|
preds.add_(-.5)
|
|
|
|
preds_orig = torch.zeros(preds.size())
|
|
if center is not None and scale is not None:
|
|
for i in range(hm.size(0)):
|
|
for j in range(hm.size(1)):
|
|
preds_orig[i, j] = transform(
|
|
preds[i, j], center, scale, hm.size(2), True)
|
|
|
|
return preds, preds_orig
|
|
|
|
def get_preds_fromhm_batch(hm, centers=None, scales=None):
|
|
"""Obtain (x,y) coordinates given a set of N heatmaps. If the centers
|
|
and the scales is provided the function will return the points also in
|
|
the original coordinate frame.
|
|
|
|
Arguments:
|
|
hm {torch.tensor} -- the predicted heatmaps, of shape [B, N, W, H]
|
|
|
|
Keyword Arguments:
|
|
centers {torch.tensor} -- the centers of the bounding box (default: {None})
|
|
scales {float} -- face scales (default: {None})
|
|
"""
|
|
max, idx = torch.max(
|
|
hm.view(hm.size(0), hm.size(1), hm.size(2) * hm.size(3)), 2)
|
|
idx += 1
|
|
preds = idx.view(idx.size(0), idx.size(1), 1).repeat(1, 1, 2).float()
|
|
preds[..., 0].apply_(lambda x: (x - 1) % hm.size(3) + 1)
|
|
preds[..., 1].add_(-1).div_(hm.size(2)).floor_().add_(1)
|
|
|
|
for i in range(preds.size(0)):
|
|
for j in range(preds.size(1)):
|
|
hm_ = hm[i, j, :]
|
|
pX, pY = int(preds[i, j, 0]) - 1, int(preds[i, j, 1]) - 1
|
|
if pX > 0 and pX < 63 and pY > 0 and pY < 63:
|
|
diff = torch.FloatTensor(
|
|
[hm_[pY, pX + 1] - hm_[pY, pX - 1],
|
|
hm_[pY + 1, pX] - hm_[pY - 1, pX]])
|
|
preds[i, j].add_(diff.sign_().mul_(.25))
|
|
|
|
preds.add_(-.5)
|
|
|
|
preds_orig = torch.zeros(preds.size())
|
|
if centers is not None and scales is not None:
|
|
for i in range(hm.size(0)):
|
|
for j in range(hm.size(1)):
|
|
preds_orig[i, j] = transform(
|
|
preds[i, j], centers[i], scales[i], hm.size(2), True)
|
|
|
|
return preds, preds_orig
|
|
|
|
def shuffle_lr(parts, pairs=None):
|
|
"""Shuffle the points left-right according to the axis of symmetry
|
|
of the object.
|
|
|
|
Arguments:
|
|
parts {torch.tensor} -- a 3D or 4D object containing the
|
|
heatmaps.
|
|
|
|
Keyword Arguments:
|
|
pairs {list of integers} -- [order of the flipped points] (default: {None})
|
|
"""
|
|
if pairs is None:
|
|
pairs = [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
|
|
26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 27, 28, 29, 30, 35,
|
|
34, 33, 32, 31, 45, 44, 43, 42, 47, 46, 39, 38, 37, 36, 41,
|
|
40, 54, 53, 52, 51, 50, 49, 48, 59, 58, 57, 56, 55, 64, 63,
|
|
62, 61, 60, 67, 66, 65]
|
|
if parts.ndimension() == 3:
|
|
parts = parts[pairs, ...]
|
|
else:
|
|
parts = parts[:, pairs, ...]
|
|
|
|
return parts
|
|
|
|
|
|
def flip(tensor, is_label=False):
|
|
"""Flip an image or a set of heatmaps left-right
|
|
|
|
Arguments:
|
|
tensor {numpy.array or torch.tensor} -- [the input image or heatmaps]
|
|
|
|
Keyword Arguments:
|
|
is_label {bool} -- [denote wherever the input is an image or a set of heatmaps ] (default: {False})
|
|
"""
|
|
if not torch.is_tensor(tensor):
|
|
tensor = torch.from_numpy(tensor)
|
|
|
|
if is_label:
|
|
tensor = shuffle_lr(tensor).flip(tensor.ndimension() - 1)
|
|
else:
|
|
tensor = tensor.flip(tensor.ndimension() - 1)
|
|
|
|
return tensor
|
|
|
|
|
|
|
|
|
|
def appdata_dir(appname=None, roaming=False):
|
|
""" appdata_dir(appname=None, roaming=False)
|
|
|
|
Get the path to the application directory, where applications are allowed
|
|
to write user specific files (e.g. configurations). For non-user specific
|
|
data, consider using common_appdata_dir().
|
|
If appname is given, a subdir is appended (and created if necessary).
|
|
If roaming is True, will prefer a roaming directory (Windows Vista/7).
|
|
"""
|
|
|
|
|
|
userDir = os.getenv('FACEALIGNMENT_USERDIR', None)
|
|
if userDir is None:
|
|
userDir = os.path.expanduser('~')
|
|
if not os.path.isdir(userDir):
|
|
userDir = '/var/tmp'
|
|
|
|
|
|
path = None
|
|
if sys.platform.startswith('win'):
|
|
path1, path2 = os.getenv('LOCALAPPDATA'), os.getenv('APPDATA')
|
|
path = (path2 or path1) if roaming else (path1 or path2)
|
|
elif sys.platform.startswith('darwin'):
|
|
path = os.path.join(userDir, 'Library', 'Application Support')
|
|
|
|
if not (path and os.path.isdir(path)):
|
|
path = userDir
|
|
|
|
|
|
|
|
prefix = sys.prefix
|
|
if getattr(sys, 'frozen', None):
|
|
prefix = os.path.abspath(os.path.dirname(sys.executable))
|
|
for reldir in ('settings', '../settings'):
|
|
localpath = os.path.abspath(os.path.join(prefix, reldir))
|
|
if os.path.isdir(localpath):
|
|
try:
|
|
open(os.path.join(localpath, 'test.write'), 'wb').close()
|
|
os.remove(os.path.join(localpath, 'test.write'))
|
|
except IOError:
|
|
pass
|
|
else:
|
|
path = localpath
|
|
break
|
|
|
|
|
|
if appname:
|
|
if path == userDir:
|
|
appname = '.' + appname.lstrip('.')
|
|
path = os.path.join(path, appname)
|
|
if not os.path.isdir(path):
|
|
os.mkdir(path)
|
|
|
|
|
|
return path
|
|
|