English
PyTorch
computer vision
GAN
lcve opetrova commited on
Commit
1359db3
·
0 Parent(s):

Duplicate from opetrova/face-frontalization

Browse files

Co-authored-by: Olga Petrova <opetrova@users.noreply.huggingface.co>

Files changed (4) hide show
  1. .gitattributes +27 -0
  2. README.md +97 -0
  3. generator_v0.pt +3 -0
  4. network.py +101 -0
.gitattributes ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bin.* filter=lfs diff=lfs merge=lfs -text
5
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.model filter=lfs diff=lfs merge=lfs -text
12
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
13
+ *.onnx filter=lfs diff=lfs merge=lfs -text
14
+ *.ot filter=lfs diff=lfs merge=lfs -text
15
+ *.parquet filter=lfs diff=lfs merge=lfs -text
16
+ *.pb filter=lfs diff=lfs merge=lfs -text
17
+ *.pt filter=lfs diff=lfs merge=lfs -text
18
+ *.pth filter=lfs diff=lfs merge=lfs -text
19
+ *.rar filter=lfs diff=lfs merge=lfs -text
20
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
21
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
22
+ *.tflite filter=lfs diff=lfs merge=lfs -text
23
+ *.tgz filter=lfs diff=lfs merge=lfs -text
24
+ *.xz filter=lfs diff=lfs merge=lfs -text
25
+ *.zip filter=lfs diff=lfs merge=lfs -text
26
+ *.zstandard filter=lfs diff=lfs merge=lfs -text
27
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: en
3
+ license: mit
4
+ library_name: PyTorch
5
+ tags:
6
+ - computer vision
7
+ - GAN
8
+ datasets:
9
+ - multi-pie
10
+ ---
11
+
12
+ Face Frontalization is a generative computer vision task in which the model takes a photo of a person's head taken at an angle between -90 and 90 degrees, and produces an image of what that person's frontal (i.e. 0 degree) view of the face might look like. The present model was first released in [this repository](https://github.com/scaleway/frontalization) by [Scaleway](https://www.scaleway.com/), a European cloud provider originating from France. It has been previously discussed in a [Scaleway blog post](https://blog.scaleway.com/gpu-instances-using-deep-learning-to-obtain-frontal-rendering-of-facial-images/) and presented at [the DataXDay conference in Paris](https://www.youtube.com/watch?v=aL7rhJz8mAI). The model's GAN architecture was inspired by [the work of R. Huang et al](https://arxiv.org/abs/1704.04086).
13
+
14
+ # Model description
15
+
16
+ The Face Frontalization model is the Generator part of a [GAN](https://proceedings.neurips.cc/paper/2014/file/5ca3e9b122f61f8f06494c97b1afccf3-Paper.pdf) that was trained in a supervised fashion on profile-frontal image pairs. The Discriminator was based on a fairly standard [DCGAN](https://arxiv.org/abs/1511.06434) architecture, where the input is a 128x128x3 image that is processed through multiple convolutional layers, to be classified as either Real or Fake. The Generator had to be modified in order to fit the supervised learning scenario. It consists of convolutional layers (the Encoder of the input image), followed by a 512-dimensional hidden representation that is then fed into the Decoder made up of deconvolutional layers, which produces the output image. For more details on the model's architecture, see [this blog post](https://blog.scaleway.com/gpu-instances-using-deep-learning-to-obtain-frontal-rendering-of-facial-images/).
17
+
18
+ # Intended uses & limitations
19
+
20
+ The present Face Frontalization model was not intended to represent the state of the art for this machine learning task. Instead, the goals were:
21
+
22
+ (a) to demonstrate the benefits of using a GAN for supervised machine learning tasks (whereas the original GAN is an unsupervised generative algorithm; see [this conference talk](https://www.youtube.com/watch?v=aL7rhJz8mAI) for more details);
23
+
24
+ (b) to show how a complex generative computer vision project can be accomplished on a [Scaleway cloud RENDER-S instance](https://www.scaleway.com/en/gpu-instances/) within ~ a day.
25
+
26
+ # How to use
27
+
28
+ The Face Frontalization model is a saved Pytorch model that can be loaded provided the included *network* package is present in the directory. It takes in 3-channel color images resized to 128x128 pixels in the form of [N, 3, 128, 128] tensors (where N is the size of the batch). Ideally, the input images should be closely-cropped photos of faces, taken in good lighting conditions. Here is how the model can be used for inference with a *gradio* image widget, e.g. in a Jupyter notebook:
29
+
30
+ ```
31
+ import gradio as gr
32
+ import numpy as np
33
+
34
+ import torch
35
+ from torchvision import transforms
36
+ from torch.autograd import Variable
37
+
38
+ from PIL import Image
39
+ import matplotlib.pyplot as plt
40
+
41
+ import warnings
42
+ warnings.filterwarnings('ignore')
43
+
44
+ # Load the saved Frontalization generator model
45
+ saved_model = torch.load("./generator_v0.pt", map_location=torch.device('cpu'))
46
+
47
+ def frontalize(image):
48
+
49
+ # Convert the test image to a [1, 3, 128, 128]-shaped torch tensor
50
+ # (as required by the frontalization model)
51
+ preprocess = transforms.Compose((transforms.ToPILImage(),
52
+ transforms.Resize(size = (128, 128)),
53
+ transforms.ToTensor()))
54
+ input_tensor = torch.unsqueeze(preprocess(image), 0)
55
+
56
+ # Use the saved model to generate an output (whose values go between -1 and 1,
57
+ # and this will need to get fixed before the output is displayed)
58
+ generated_image = saved_model(Variable(input_tensor.type('torch.FloatTensor')))
59
+ generated_image = generated_image.detach().squeeze().permute(1, 2, 0).numpy()
60
+ generated_image = (generated_image + 1.0) / 2.0
61
+
62
+ return generated_image
63
+
64
+ iface = gr.Interface(frontalize, gr.inputs.Image(type="numpy"), "image")
65
+ iface.launch()
66
+ ```
67
+
68
+ # Limitations and bias
69
+
70
+ As mentioned in the **Intended uses** section, the present model's performance is not intended to compete with the state of the art. Additionally, as the training data had a disproportionately high number of images of caucasian and asian males in their 20s, the model does not perform as well when supplied with images of people not belonging to this limited demographic.
71
+
72
+ # Training data
73
+
74
+ The present model was trained on [the CMU Multi-PIE Face Database that is available commercially](https://www.cs.cmu.edu/afs/cs/project/PIE/MultiPie/Multi-Pie/Home.html). The input images were closely cropped to include the face of a person photographed at an angle between -90 and 90 degrees. The target frontal images were cropped and aligned so that the center of the person's left eye was at the same relative position in all of them. Having a precise alignment for the target images turned out to play a key role in the training of the model.
75
+
76
+ # Training procedure
77
+
78
+ The training of the model was performed in a similar manner to that of a regular unsupervised [GAN](https://proceedings.neurips.cc/paper/2014/file/5ca3e9b122f61f8f06494c97b1afccf3-Paper.pdf), except that in addition to the binary cross entropy loss for the Discriminator, a pixelwise loss function was introduced for the Generator (see [the blog post](https://blog.scaleway.com/gpu-instances-using-deep-learning-to-obtain-frontal-rendering-of-facial-images/) for details). The exact weights given to the L1 and L2 pixelwise losses, as well as the BCE (GAN) loss were as follows:
79
+
80
+ ```
81
+ L1_factor = 1
82
+ L2_factor = 1
83
+ GAN_factor = 0.001
84
+ ```
85
+ The model was trained for 18 epochs, with the training batch size equal to 30. The following optimizers were used for the Discriminator and the Generator:
86
+ ```
87
+ optimizerD = optim.Adam(netD.parameters(), lr = 0.0002, betas = (0.5, 0.999))
88
+ optimizerG = optim.Adam(netG.parameters(), lr = 0.0002, betas = (0.5, 0.999), eps = 1e-8)
89
+ ```
90
+
91
+ # Evaluation results
92
+
93
+ GANs are notoriously difficult to train, with the losses for the Discriminator and the Generator often failing to converge even when producing what looks to be a highly realistic result to a human eye. The pixelwise loss for the test images also serves as a poor indicator of the model's performance because any variation in the lighting between the real target photo and the generated image could result in a deceptively high discrepancy between the two. The best evaluation method that remains is the manual inspection of the generated results. We have found that the present model performs reasonably well on the test data from the CMU Multi-PIE Face Database (naturally, all of the photos of the individuals included in the test set were removed from training):
94
+
95
+ ![test examples](https://github.com/scaleway/frontalization/raw/master/pretrained/test-Pie.jpg)
96
+
97
+ (Top row: inputs; middle row: model outputs; bottom row: ground truth images)
generator_v0.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c73243c112d21d6f7a02ebedb054ef014d60e152263a60858e5a6751a7c1f6e4
3
+ size 22416631
network.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.parallel
4
+ import torch.optim as optim
5
+ from torch.autograd import Variable
6
+
7
+
8
+ def weights_init(m):
9
+ classname = m.__class__.__name__
10
+
11
+ if classname.find('Conv') != -1:
12
+ m.weight.data.normal_(0.0, 0.02)
13
+
14
+ elif classname.find('BatchNorm') != -1:
15
+ m.weight.data.normal_(1.0, 0.02)
16
+ m.bias.data.fill_(0)
17
+
18
+
19
+ ''' Generator network for 128x128 RGB images '''
20
+ class G(nn.Module):
21
+
22
+ def __init__(self):
23
+ super(G, self).__init__()
24
+
25
+ self.main = nn.Sequential(
26
+ # Input HxW = 128x128
27
+ nn.Conv2d(3, 16, 4, 2, 1), # Output HxW = 64x64
28
+ nn.BatchNorm2d(16),
29
+ nn.ReLU(True),
30
+ nn.Conv2d(16, 32, 4, 2, 1), # Output HxW = 32x32
31
+ nn.BatchNorm2d(32),
32
+ nn.ReLU(True),
33
+ nn.Conv2d(32, 64, 4, 2, 1), # Output HxW = 16x16
34
+ nn.BatchNorm2d(64),
35
+ nn.ReLU(True),
36
+ nn.Conv2d(64, 128, 4, 2, 1), # Output HxW = 8x8
37
+ nn.BatchNorm2d(128),
38
+ nn.ReLU(True),
39
+ nn.Conv2d(128, 256, 4, 2, 1), # Output HxW = 4x4
40
+ nn.BatchNorm2d(256),
41
+ nn.ReLU(True),
42
+ nn.Conv2d(256, 512, 4, 2, 1), # Output HxW = 2x2
43
+ nn.MaxPool2d((2,2)),
44
+ # At this point, we arrive at our low D representation vector, which is 512 dimensional.
45
+
46
+ nn.ConvTranspose2d(512, 256, 4, 1, 0, bias = False), # Output HxW = 4x4
47
+ nn.BatchNorm2d(256),
48
+ nn.ReLU(True),
49
+ nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False), # Output HxW = 8x8
50
+ nn.BatchNorm2d(128),
51
+ nn.ReLU(True),
52
+ nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False), # Output HxW = 16x16
53
+ nn.BatchNorm2d(64),
54
+ nn.ReLU(True),
55
+ nn.ConvTranspose2d(64, 32, 4, 2, 1, bias = False), # Output HxW = 32x32
56
+ nn.BatchNorm2d(32),
57
+ nn.ReLU(True),
58
+ nn.ConvTranspose2d(32, 16, 4, 2, 1, bias = False), # Output HxW = 64x64
59
+ nn.BatchNorm2d(16),
60
+ nn.ReLU(True),
61
+ nn.ConvTranspose2d(16, 3, 4, 2, 1, bias = False), # Output HxW = 128x128
62
+ nn.Tanh()
63
+ )
64
+
65
+
66
+ def forward(self, input):
67
+ output = self.main(input)
68
+ return output
69
+
70
+
71
+ ''' Discriminator network for 128x128 RGB images '''
72
+ class D(nn.Module):
73
+
74
+ def __init__(self):
75
+ super(D, self).__init__()
76
+ self.main = nn.Sequential(
77
+ nn.Conv2d(3, 16, 4, 2, 1),
78
+ nn.LeakyReLU(0.2, inplace = True),
79
+ nn.Conv2d(16, 32, 4, 2, 1),
80
+ nn.BatchNorm2d(32),
81
+ nn.LeakyReLU(0.2, inplace = True),
82
+ nn.Conv2d(32, 64, 4, 2, 1),
83
+ nn.BatchNorm2d(64),
84
+ nn.LeakyReLU(0.2, inplace = True),
85
+ nn.Conv2d(64, 128, 4, 2, 1),
86
+ nn.BatchNorm2d(128),
87
+ nn.LeakyReLU(0.2, inplace = True),
88
+ nn.Conv2d(128, 256, 4, 2, 1),
89
+ nn.BatchNorm2d(256),
90
+ nn.LeakyReLU(0.2, inplace = True),
91
+ nn.Conv2d(256, 512, 4, 2, 1),
92
+ nn.BatchNorm2d(512),
93
+ nn.LeakyReLU(0.2, inplace = True),
94
+ nn.Conv2d(512, 1, 4, 2, 1, bias = False),
95
+ nn.Sigmoid()
96
+ )
97
+
98
+
99
+ def forward(self, input):
100
+ output = self.main(input)
101
+ return output.view(-1)