|
|
import copy |
|
|
import glob |
|
|
import math |
|
|
import os |
|
|
import random |
|
|
import sys |
|
|
|
|
|
import cv2 |
|
|
import matplotlib.pyplot as plt |
|
|
import numpy as np |
|
|
import scipy.io as sio |
|
|
import torch |
|
|
from imgaug import augmenters as iaa |
|
|
from PIL import Image |
|
|
from scipy import interpolate |
|
|
from skimage import io |
|
|
from skimage import transform as ski_transform |
|
|
from skimage.color import rgb2gray |
|
|
from torch.utils.data import DataLoader |
|
|
from torch.utils.data import Dataset |
|
|
from torchvision import transforms |
|
|
from torchvision import utils |
|
|
from torchvision.transforms import Compose |
|
|
from torchvision.transforms import Lambda |
|
|
from torchvision.transforms.functional import adjust_brightness |
|
|
from torchvision.transforms.functional import adjust_contrast |
|
|
from torchvision.transforms.functional import adjust_hue |
|
|
from torchvision.transforms.functional import adjust_saturation |
|
|
|
|
|
from utils.utils import cv_crop |
|
|
from utils.utils import cv_rotate |
|
|
from utils.utils import draw_gaussian |
|
|
from utils.utils import fig2data |
|
|
from utils.utils import generate_weight_map |
|
|
from utils.utils import power_transform |
|
|
from utils.utils import shuffle_lr |
|
|
from utils.utils import transform |
|
|
|
|
|
|
|
|
class AddBoundary(object): |
|
|
def __init__(self, num_landmarks=68): |
|
|
self.num_landmarks = num_landmarks |
|
|
|
|
|
def __call__(self, sample): |
|
|
landmarks_64 = np.floor(sample["landmarks"] / 4.0) |
|
|
if self.num_landmarks == 68: |
|
|
boundaries = {} |
|
|
boundaries["cheek"] = landmarks_64[0:17] |
|
|
boundaries["left_eyebrow"] = landmarks_64[17:22] |
|
|
boundaries["right_eyebrow"] = landmarks_64[22:27] |
|
|
boundaries["uper_left_eyelid"] = landmarks_64[36:40] |
|
|
boundaries["lower_left_eyelid"] = np.array([landmarks_64[i] for i in [36, 41, 40, 39]]) |
|
|
boundaries["upper_right_eyelid"] = landmarks_64[42:46] |
|
|
boundaries["lower_right_eyelid"] = np.array([landmarks_64[i] for i in [42, 47, 46, 45]]) |
|
|
boundaries["noise"] = landmarks_64[27:31] |
|
|
boundaries["noise_bot"] = landmarks_64[31:36] |
|
|
boundaries["upper_outer_lip"] = landmarks_64[48:55] |
|
|
boundaries["upper_inner_lip"] = np.array([landmarks_64[i] for i in [60, 61, 62, 63, 64]]) |
|
|
boundaries["lower_outer_lip"] = np.array([landmarks_64[i] for i in [48, 59, 58, 57, 56, 55, 54]]) |
|
|
boundaries["lower_inner_lip"] = np.array([landmarks_64[i] for i in [60, 67, 66, 65, 64]]) |
|
|
elif self.num_landmarks == 98: |
|
|
boundaries = {} |
|
|
boundaries["cheek"] = landmarks_64[0:33] |
|
|
boundaries["left_eyebrow"] = landmarks_64[33:38] |
|
|
boundaries["right_eyebrow"] = landmarks_64[42:47] |
|
|
boundaries["uper_left_eyelid"] = landmarks_64[60:65] |
|
|
boundaries["lower_left_eyelid"] = np.array([landmarks_64[i] for i in [60, 67, 66, 65, 64]]) |
|
|
boundaries["upper_right_eyelid"] = landmarks_64[68:73] |
|
|
boundaries["lower_right_eyelid"] = np.array([landmarks_64[i] for i in [68, 75, 74, 73, 72]]) |
|
|
boundaries["noise"] = landmarks_64[51:55] |
|
|
boundaries["noise_bot"] = landmarks_64[55:60] |
|
|
boundaries["upper_outer_lip"] = landmarks_64[76:83] |
|
|
boundaries["upper_inner_lip"] = np.array([landmarks_64[i] for i in [88, 89, 90, 91, 92]]) |
|
|
boundaries["lower_outer_lip"] = np.array([landmarks_64[i] for i in [76, 87, 86, 85, 84, 83, 82]]) |
|
|
boundaries["lower_inner_lip"] = np.array([landmarks_64[i] for i in [88, 95, 94, 93, 92]]) |
|
|
elif self.num_landmarks == 19: |
|
|
boundaries = {} |
|
|
boundaries["left_eyebrow"] = landmarks_64[0:3] |
|
|
boundaries["right_eyebrow"] = landmarks_64[3:5] |
|
|
boundaries["left_eye"] = landmarks_64[6:9] |
|
|
boundaries["right_eye"] = landmarks_64[9:12] |
|
|
boundaries["noise"] = landmarks_64[12:15] |
|
|
|
|
|
elif self.num_landmarks == 29: |
|
|
boundaries = {} |
|
|
boundaries["upper_left_eyebrow"] = np.stack([landmarks_64[0], landmarks_64[4], landmarks_64[2]], axis=0) |
|
|
boundaries["lower_left_eyebrow"] = np.stack([landmarks_64[0], landmarks_64[5], landmarks_64[2]], axis=0) |
|
|
boundaries["upper_right_eyebrow"] = np.stack([landmarks_64[1], landmarks_64[6], landmarks_64[3]], axis=0) |
|
|
boundaries["lower_right_eyebrow"] = np.stack([landmarks_64[1], landmarks_64[7], landmarks_64[3]], axis=0) |
|
|
boundaries["upper_left_eye"] = np.stack([landmarks_64[8], landmarks_64[12], landmarks_64[10]], axis=0) |
|
|
boundaries["lower_left_eye"] = np.stack([landmarks_64[8], landmarks_64[13], landmarks_64[10]], axis=0) |
|
|
boundaries["upper_right_eye"] = np.stack([landmarks_64[9], landmarks_64[14], landmarks_64[11]], axis=0) |
|
|
boundaries["lower_right_eye"] = np.stack([landmarks_64[9], landmarks_64[15], landmarks_64[11]], axis=0) |
|
|
boundaries["noise"] = np.stack([landmarks_64[18], landmarks_64[21], landmarks_64[19]], axis=0) |
|
|
boundaries["outer_upper_lip"] = np.stack([landmarks_64[22], landmarks_64[24], landmarks_64[23]], axis=0) |
|
|
boundaries["inner_upper_lip"] = np.stack([landmarks_64[22], landmarks_64[25], landmarks_64[23]], axis=0) |
|
|
boundaries["outer_lower_lip"] = np.stack([landmarks_64[22], landmarks_64[26], landmarks_64[23]], axis=0) |
|
|
boundaries["inner_lower_lip"] = np.stack([landmarks_64[22], landmarks_64[27], landmarks_64[23]], axis=0) |
|
|
functions = {} |
|
|
|
|
|
for key, points in boundaries.items(): |
|
|
temp = points[0] |
|
|
new_points = points[0:1, :] |
|
|
for point in points[1:]: |
|
|
if point[0] == temp[0] and point[1] == temp[1]: |
|
|
continue |
|
|
else: |
|
|
new_points = np.concatenate((new_points, np.expand_dims(point, 0)), axis=0) |
|
|
temp = point |
|
|
points = new_points |
|
|
if points.shape[0] == 1: |
|
|
points = np.concatenate((points, points + 0.001), axis=0) |
|
|
k = min(4, points.shape[0]) |
|
|
functions[key] = interpolate.splprep([points[:, 0], points[:, 1]], k=k - 1, s=0) |
|
|
|
|
|
boundary_map = np.zeros((64, 64)) |
|
|
|
|
|
fig = plt.figure(figsize=[64 / 96.0, 64 / 96.0], dpi=96) |
|
|
|
|
|
ax = fig.add_axes([0, 0, 1, 1]) |
|
|
|
|
|
ax.axis("off") |
|
|
|
|
|
ax.imshow(boundary_map, interpolation="nearest", cmap="gray") |
|
|
|
|
|
|
|
|
for key in functions.keys(): |
|
|
xnew = np.arange(0, 1, 0.01) |
|
|
out = interpolate.splev(xnew, functions[key][0], der=0) |
|
|
plt.plot(out[0], out[1], ",", linewidth=1, color="w") |
|
|
|
|
|
img = fig2data(fig) |
|
|
|
|
|
plt.close() |
|
|
|
|
|
sigma = 1 |
|
|
temp = 255 - img[:, :, 1] |
|
|
temp = cv2.distanceTransform(temp, cv2.DIST_L2, cv2.DIST_MASK_PRECISE) |
|
|
temp = temp.astype(np.float32) |
|
|
temp = np.where(temp < 3 * sigma, np.exp(-(temp * temp) / (2 * sigma * sigma)), 0) |
|
|
|
|
|
fig = plt.figure(figsize=[64 / 96.0, 64 / 96.0], dpi=96) |
|
|
|
|
|
ax = fig.add_axes([0, 0, 1, 1]) |
|
|
|
|
|
ax.axis("off") |
|
|
ax.imshow(temp, cmap="gray") |
|
|
plt.close() |
|
|
|
|
|
boundary_map = fig2data(fig) |
|
|
|
|
|
sample["boundary"] = boundary_map[:, :, 0] |
|
|
|
|
|
return sample |
|
|
|
|
|
|
|
|
class AddWeightMap(object): |
|
|
def __call__(self, sample): |
|
|
heatmap = sample["heatmap"] |
|
|
boundary = sample["boundary"] |
|
|
heatmap = np.concatenate((heatmap, np.expand_dims(boundary, axis=0)), 0) |
|
|
weight_map = np.zeros_like(heatmap) |
|
|
for i in range(heatmap.shape[0]): |
|
|
weight_map[i] = generate_weight_map(weight_map[i], heatmap[i]) |
|
|
sample["weight_map"] = weight_map |
|
|
return sample |
|
|
|
|
|
|
|
|
class ToTensor(object): |
|
|
"""Convert ndarrays in sample to Tensors.""" |
|
|
|
|
|
def __call__(self, sample): |
|
|
image, heatmap, landmarks, boundary, weight_map = ( |
|
|
sample["image"], |
|
|
sample["heatmap"], |
|
|
sample["landmarks"], |
|
|
sample["boundary"], |
|
|
sample["weight_map"], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(image.shape) == 2: |
|
|
image = np.expand_dims(image, axis=2) |
|
|
image_small = np.expand_dims(image_small, axis=2) |
|
|
image = image.transpose((2, 0, 1)) |
|
|
boundary = np.expand_dims(boundary, axis=2) |
|
|
boundary = boundary.transpose((2, 0, 1)) |
|
|
return { |
|
|
"image": torch.from_numpy(image).float().div(255.0), |
|
|
"heatmap": torch.from_numpy(heatmap).float(), |
|
|
"landmarks": torch.from_numpy(landmarks).float(), |
|
|
"boundary": torch.from_numpy(boundary).float().div(255.0), |
|
|
"weight_map": torch.from_numpy(weight_map).float(), |
|
|
} |
|
|
|
|
|
|
|
|
class FaceLandmarksDataset(Dataset): |
|
|
"""Face Landmarks dataset.""" |
|
|
|
|
|
def __init__( |
|
|
self, |
|
|
img_dir, |
|
|
landmarks_dir, |
|
|
num_landmarks=68, |
|
|
gray_scale=False, |
|
|
detect_face=False, |
|
|
enhance=False, |
|
|
center_shift=0, |
|
|
transform=None, |
|
|
): |
|
|
""" |
|
|
Args: |
|
|
landmark_dir (string): Path to the mat file with landmarks saved. |
|
|
img_dir (string): Directory with all the images. |
|
|
transform (callable, optional): Optional transform to be applied |
|
|
on a sample. |
|
|
""" |
|
|
self.img_dir = img_dir |
|
|
self.landmarks_dir = landmarks_dir |
|
|
self.num_lanmdkars = num_landmarks |
|
|
self.transform = transform |
|
|
self.img_names = glob.glob(self.img_dir + "*.jpg") + glob.glob(self.img_dir + "*.png") |
|
|
self.gray_scale = gray_scale |
|
|
self.detect_face = detect_face |
|
|
self.enhance = enhance |
|
|
self.center_shift = center_shift |
|
|
if self.detect_face: |
|
|
self.face_detector = MTCNN(thresh=[0.5, 0.6, 0.7]) |
|
|
|
|
|
def __len__(self): |
|
|
return len(self.img_names) |
|
|
|
|
|
def __getitem__(self, idx): |
|
|
img_name = self.img_names[idx] |
|
|
pil_image = Image.open(img_name) |
|
|
if pil_image.mode != "RGB": |
|
|
|
|
|
if self.enhance: |
|
|
pil_image = power_transform(pil_image, 0.5) |
|
|
temp_image = Image.new("RGB", pil_image.size) |
|
|
temp_image.paste(pil_image) |
|
|
pil_image = temp_image |
|
|
image = np.array(pil_image) |
|
|
if self.gray_scale: |
|
|
image = rgb2gray(image) |
|
|
image = np.expand_dims(image, axis=2) |
|
|
image = np.concatenate((image, image, image), axis=2) |
|
|
image = image * 255.0 |
|
|
image = image.astype(np.uint8) |
|
|
if not self.detect_face: |
|
|
center = [450 // 2, 450 // 2 + 0] |
|
|
if self.center_shift != 0: |
|
|
center[0] += int(np.random.uniform(-self.center_shift, self.center_shift)) |
|
|
center[1] += int(np.random.uniform(-self.center_shift, self.center_shift)) |
|
|
scale = 1.8 |
|
|
else: |
|
|
detected_faces = self.face_detector.detect_image(image) |
|
|
if len(detected_faces) > 0: |
|
|
box = detected_faces[0] |
|
|
left, top, right, bottom, _ = box |
|
|
center = [right - (right - left) / 2.0, bottom - (bottom - top) / 2.0] |
|
|
center[1] = center[1] - (bottom - top) * 0.12 |
|
|
scale = (right - left + bottom - top) / 195.0 |
|
|
else: |
|
|
center = [450 // 2, 450 // 2 + 0] |
|
|
scale = 1.8 |
|
|
if self.center_shift != 0: |
|
|
shift = self.center * self.center_shift / 450 |
|
|
center[0] += int(np.random.uniform(-shift, shift)) |
|
|
center[1] += int(np.random.uniform(-shift, shift)) |
|
|
base_name = os.path.basename(img_name) |
|
|
landmarks_base_name = base_name[:-4] + "_pts.mat" |
|
|
landmarks_name = os.path.join(self.landmarks_dir, landmarks_base_name) |
|
|
if os.path.isfile(landmarks_name): |
|
|
mat_data = sio.loadmat(landmarks_name) |
|
|
landmarks = mat_data["pts_2d"] |
|
|
elif os.path.isfile(landmarks_name[:-8] + ".pts.npy"): |
|
|
landmarks = np.load(landmarks_name[:-8] + ".pts.npy") |
|
|
else: |
|
|
landmarks = [] |
|
|
heatmap = [] |
|
|
|
|
|
if landmarks != []: |
|
|
new_image, new_landmarks = cv_crop(image, landmarks, center, scale, 256, self.center_shift) |
|
|
tries = 0 |
|
|
while self.center_shift != 0 and tries < 5 and (np.max(new_landmarks) > 240 or np.min(new_landmarks) < 15): |
|
|
center = [450 // 2, 450 // 2 + 0] |
|
|
scale += 0.05 |
|
|
center[0] += int(np.random.uniform(-self.center_shift, self.center_shift)) |
|
|
center[1] += int(np.random.uniform(-self.center_shift, self.center_shift)) |
|
|
|
|
|
new_image, new_landmarks = cv_crop(image, landmarks, center, scale, 256, self.center_shift) |
|
|
tries += 1 |
|
|
if np.max(new_landmarks) > 250 or np.min(new_landmarks) < 5: |
|
|
center = [450 // 2, 450 // 2 + 0] |
|
|
scale = 2.25 |
|
|
new_image, new_landmarks = cv_crop(image, landmarks, center, scale, 256, 100) |
|
|
assert np.min(new_landmarks) > 0 and np.max(new_landmarks) < 256, "Landmarks out of boundary!" |
|
|
image = new_image |
|
|
landmarks = new_landmarks |
|
|
heatmap = np.zeros((self.num_lanmdkars, 64, 64)) |
|
|
for i in range(self.num_lanmdkars): |
|
|
if landmarks[i][0] > 0: |
|
|
heatmap[i] = draw_gaussian(heatmap[i], landmarks[i] / 4.0 + 1, 1) |
|
|
sample = {"image": image, "heatmap": heatmap, "landmarks": landmarks} |
|
|
if self.transform: |
|
|
sample = self.transform(sample) |
|
|
|
|
|
return sample |
|
|
|
|
|
|
|
|
def get_dataset( |
|
|
val_img_dir, |
|
|
val_landmarks_dir, |
|
|
batch_size, |
|
|
num_landmarks=68, |
|
|
rotation=0, |
|
|
scale=0, |
|
|
center_shift=0, |
|
|
random_flip=False, |
|
|
brightness=0, |
|
|
contrast=0, |
|
|
saturation=0, |
|
|
blur=False, |
|
|
noise=False, |
|
|
jpeg_effect=False, |
|
|
random_occlusion=False, |
|
|
gray_scale=False, |
|
|
detect_face=False, |
|
|
enhance=False, |
|
|
): |
|
|
val_transforms = transforms.Compose([AddBoundary(num_landmarks), AddWeightMap(), ToTensor()]) |
|
|
|
|
|
val_dataset = FaceLandmarksDataset( |
|
|
val_img_dir, |
|
|
val_landmarks_dir, |
|
|
num_landmarks=num_landmarks, |
|
|
gray_scale=gray_scale, |
|
|
detect_face=detect_face, |
|
|
enhance=enhance, |
|
|
transform=val_transforms, |
|
|
) |
|
|
|
|
|
val_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=6) |
|
|
data_loaders = {"val": val_dataloader} |
|
|
dataset_sizes = {} |
|
|
dataset_sizes["val"] = len(val_dataset) |
|
|
return data_loaders, dataset_sizes |
|
|
|