Doron Adler
commited on
Commit
β’
212a881
1
Parent(s):
325ef1d
Face2Doll (U2Net)
Browse files- .gitattributes +2 -0
- Example00001.jpg +0 -0
- Example00002.jpg +0 -0
- Example00003.jpg +0 -0
- Example00004.jpg +0 -0
- Example00005.jpg +0 -0
- Example00006.jpg +0 -0
- README.md +4 -4
- Sample00001.jpg +0 -0
- Sample00002.jpg +0 -0
- Sample00003.jpg +0 -0
- Sample00004.jpg +0 -0
- Sample00005.jpg +0 -0
- Sample00006.jpg +0 -0
- app.py +112 -0
- face_detection.py +140 -0
- requirements.txt +8 -0
- shape_predictor_5_face_landmarks.dat +3 -0
- u2net_bce_itr_18000_train_3.891670_tar_0.553700_512x_460x.jit.pt +3 -0
.gitattributes
CHANGED
@@ -25,3 +25,5 @@ saved_model/**/* 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
|
|
|
|
|
|
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
|
28 |
+
u2net_bce_itr_18000_train_3.891670_tar_0.553700_512x_460x.jit.pt filter=lfs diff=lfs merge=lfs -text
|
29 |
+
shape_predictor_5_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
|
Example00001.jpg
ADDED
Example00002.jpg
ADDED
Example00003.jpg
ADDED
Example00004.jpg
ADDED
Example00005.jpg
ADDED
Example00006.jpg
ADDED
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
-
title: Face2Doll
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
app_file: app.py
|
8 |
pinned: false
|
|
|
1 |
---
|
2 |
+
title: Face2Doll (U2Net)
|
3 |
+
emoji: π
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: black
|
6 |
sdk: gradio
|
7 |
app_file: app.py
|
8 |
pinned: false
|
Sample00001.jpg
ADDED
Sample00002.jpg
ADDED
Sample00003.jpg
ADDED
Sample00004.jpg
ADDED
Sample00005.jpg
ADDED
Sample00006.jpg
ADDED
app.py
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
os.system("pip install dlib")
|
3 |
+
os.system("pip install gradio==2.5.3")
|
4 |
+
import sys
|
5 |
+
import face_detection
|
6 |
+
import PIL
|
7 |
+
from PIL import Image, ImageOps, ImageFile
|
8 |
+
import numpy as np
|
9 |
+
|
10 |
+
import torch
|
11 |
+
torch.set_grad_enabled(False)
|
12 |
+
model = torch.jit.load('u2net_bce_itr_18000_train_3.891670_tar_0.553700_512x_460x.jit.pt')
|
13 |
+
model.eval()
|
14 |
+
|
15 |
+
def normPRED(d):
|
16 |
+
ma = np.max(d)
|
17 |
+
mi = np.min(d)
|
18 |
+
|
19 |
+
dn = (d-mi)/(ma-mi)
|
20 |
+
|
21 |
+
return dn
|
22 |
+
|
23 |
+
def array_to_image(array_in):
|
24 |
+
array_in = normPRED(array_in)
|
25 |
+
array_in = np.squeeze(255.0*(array_in))
|
26 |
+
array_in = np.transpose(array_in, (1, 2, 0))
|
27 |
+
im = Image.fromarray(array_in.astype(np.uint8))
|
28 |
+
return im
|
29 |
+
|
30 |
+
|
31 |
+
def image_as_array(image_in):
|
32 |
+
image_in = np.array(image_in, np.float32)
|
33 |
+
tmpImg = np.zeros((image_in.shape[0],image_in.shape[1],3))
|
34 |
+
image_in = image_in/np.max(image_in)
|
35 |
+
if image_in.shape[2]==1:
|
36 |
+
tmpImg[:,:,0] = (image_in[:,:,0]-0.485)/0.229
|
37 |
+
tmpImg[:,:,1] = (image_in[:,:,0]-0.485)/0.229
|
38 |
+
tmpImg[:,:,2] = (image_in[:,:,0]-0.485)/0.229
|
39 |
+
else:
|
40 |
+
tmpImg[:,:,0] = (image_in[:,:,0]-0.485)/0.229
|
41 |
+
tmpImg[:,:,1] = (image_in[:,:,1]-0.456)/0.224
|
42 |
+
tmpImg[:,:,2] = (image_in[:,:,2]-0.406)/0.225
|
43 |
+
|
44 |
+
tmpImg = tmpImg.transpose((2, 0, 1))
|
45 |
+
image_out = np.expand_dims(tmpImg, 0)
|
46 |
+
return image_out
|
47 |
+
|
48 |
+
def find_aligned_face(image_in, size=512):
|
49 |
+
aligned_image, n_faces, quad = face_detection.align(image_in, face_index=0, output_size=size)
|
50 |
+
return aligned_image, n_faces, quad
|
51 |
+
|
52 |
+
def align_first_face(image_in, size=512):
|
53 |
+
aligned_image, n_faces, quad = find_aligned_face(image_in,size=size)
|
54 |
+
if n_faces == 0:
|
55 |
+
try:
|
56 |
+
image_in = ImageOps.exif_transpose(image_in)
|
57 |
+
except:
|
58 |
+
print("exif problem, not rotating")
|
59 |
+
image_in = image_in.resize((size, size))
|
60 |
+
im_array = image_as_array(image_in)
|
61 |
+
else:
|
62 |
+
im_array = image_as_array(aligned_image)
|
63 |
+
|
64 |
+
return im_array
|
65 |
+
|
66 |
+
def img_concat_h(im1, im2):
|
67 |
+
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
|
68 |
+
dst.paste(im1, (0, 0))
|
69 |
+
dst.paste(im2, (im1.width, 0))
|
70 |
+
return dst
|
71 |
+
|
72 |
+
import gradio as gr
|
73 |
+
|
74 |
+
def face2doll(
|
75 |
+
img: Image.Image,
|
76 |
+
size: int
|
77 |
+
) -> Image.Image:
|
78 |
+
|
79 |
+
aligned_img = align_first_face(img)
|
80 |
+
if aligned_img is None:
|
81 |
+
output=None
|
82 |
+
else:
|
83 |
+
input = torch.Tensor(aligned_img)
|
84 |
+
results = model(input)
|
85 |
+
d2 = array_to_image(results[1].detach().numpy())
|
86 |
+
output = img_concat_h(array_to_image(aligned_img), d2)
|
87 |
+
del results
|
88 |
+
|
89 |
+
return output
|
90 |
+
|
91 |
+
def inference(img):
|
92 |
+
out = face2doll(img, 512)
|
93 |
+
return out
|
94 |
+
|
95 |
+
|
96 |
+
title = "Face2Doll U2Net"
|
97 |
+
description = "Style transfer a face into one of a \"Doll\". Upload an image with a face, or click on one of the examples below. If a face could not be detected, an image will still be created. Faces with glasses on, seem not to yield good results."
|
98 |
+
article = "<hr><p style='text-align: center'>See the <a href='https://github.com/Norod/U-2-Net-StyleTransfer' target='_blank'>Github Repo</a></p><p style='text-align: center'>samples: <img src='https://hf.space/gradioiframe/Norod78/Face2Doll/file/Sample00001.jpg' alt='Sample00001'/><img src='https://hf.space/gradioiframe/Norod78/Face2Doll/file/Sample00002.jpg' alt='Sample00002'/><img src='https://hf.space/gradioiframe/Norod78/Face2Doll/file/Sample00003.jpg' alt='Sample00003'/><img src='https://hf.space/gradioiframe/Norod78/Face2Doll/file/Sample00004.jpg' alt='Sample00004'/><img src='https://hf.space/gradioiframe/Norod78/Face2Doll/file/Sample00005.jpg' alt='Sample00005'/></p><p>The \"Face2Doll (U2Net)\" model was trained by <a href='https://linktr.ee/Norod78' target='_blank'>Doron Adler</a></p>"
|
99 |
+
|
100 |
+
examples=[['Example00001.jpg'],['Example00002.jpg'],['Example00003.jpg'],['Example00004.jpg'],['Example00005.jpg'], ['Example00006.jpg']]
|
101 |
+
|
102 |
+
gr.Interface(
|
103 |
+
inference,
|
104 |
+
gr.inputs.Image(type="pil", label="Input"),
|
105 |
+
gr.outputs.Image(type="pil", label="Output"),
|
106 |
+
title=title,
|
107 |
+
description=description,
|
108 |
+
article=article,
|
109 |
+
examples=examples,
|
110 |
+
enable_queue=True,
|
111 |
+
allow_flagging=False
|
112 |
+
).launch()
|
face_detection.py
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) 2021 Justin Pinkney
|
2 |
+
|
3 |
+
import dlib
|
4 |
+
import numpy as np
|
5 |
+
import os
|
6 |
+
from PIL import Image
|
7 |
+
from PIL import ImageOps
|
8 |
+
from scipy.ndimage import gaussian_filter
|
9 |
+
import cv2
|
10 |
+
|
11 |
+
|
12 |
+
MODEL_PATH = "shape_predictor_5_face_landmarks.dat"
|
13 |
+
detector = dlib.get_frontal_face_detector()
|
14 |
+
|
15 |
+
|
16 |
+
def align(image_in, face_index=0, output_size=256):
|
17 |
+
try:
|
18 |
+
image_in = ImageOps.exif_transpose(image_in)
|
19 |
+
except:
|
20 |
+
print("exif problem, not rotating")
|
21 |
+
|
22 |
+
landmarks = list(get_landmarks(image_in))
|
23 |
+
n_faces = len(landmarks)
|
24 |
+
face_index = min(n_faces-1, face_index)
|
25 |
+
if n_faces == 0:
|
26 |
+
aligned_image = image_in
|
27 |
+
quad = None
|
28 |
+
else:
|
29 |
+
aligned_image, quad = image_align(image_in, landmarks[face_index], output_size=output_size)
|
30 |
+
|
31 |
+
return aligned_image, n_faces, quad
|
32 |
+
|
33 |
+
|
34 |
+
def composite_images(quad, img, output):
|
35 |
+
"""Composite an image into and output canvas according to transformed co-ords"""
|
36 |
+
output = output.convert("RGBA")
|
37 |
+
img = img.convert("RGBA")
|
38 |
+
input_size = img.size
|
39 |
+
src = np.array(((0, 0), (0, input_size[1]), input_size, (input_size[0], 0)), dtype=np.float32)
|
40 |
+
dst = np.float32(quad)
|
41 |
+
mtx = cv2.getPerspectiveTransform(dst, src)
|
42 |
+
img = img.transform(output.size, Image.PERSPECTIVE, mtx.flatten(), Image.BILINEAR)
|
43 |
+
output.alpha_composite(img)
|
44 |
+
|
45 |
+
return output.convert("RGB")
|
46 |
+
|
47 |
+
|
48 |
+
def get_landmarks(image):
|
49 |
+
"""Get landmarks from PIL image"""
|
50 |
+
shape_predictor = dlib.shape_predictor(MODEL_PATH)
|
51 |
+
|
52 |
+
max_size = max(image.size)
|
53 |
+
reduction_scale = int(max_size/512)
|
54 |
+
if reduction_scale == 0:
|
55 |
+
reduction_scale = 1
|
56 |
+
downscaled = image.reduce(reduction_scale)
|
57 |
+
img = np.array(downscaled)
|
58 |
+
detections = detector(img, 0)
|
59 |
+
|
60 |
+
for detection in detections:
|
61 |
+
try:
|
62 |
+
face_landmarks = [(reduction_scale*item.x, reduction_scale*item.y) for item in shape_predictor(img, detection).parts()]
|
63 |
+
yield face_landmarks
|
64 |
+
except Exception as e:
|
65 |
+
print(e)
|
66 |
+
|
67 |
+
|
68 |
+
def image_align(src_img, face_landmarks, output_size=512, transform_size=2048, enable_padding=True, x_scale=1, y_scale=1, em_scale=0.1, alpha=False):
|
69 |
+
# Align function modified from ffhq-dataset
|
70 |
+
# See https://github.com/NVlabs/ffhq-dataset for license
|
71 |
+
|
72 |
+
lm = np.array(face_landmarks)
|
73 |
+
lm_eye_left = lm[2:3] # left-clockwise
|
74 |
+
lm_eye_right = lm[0:1] # left-clockwise
|
75 |
+
|
76 |
+
# Calculate auxiliary vectors.
|
77 |
+
eye_left = np.mean(lm_eye_left, axis=0)
|
78 |
+
eye_right = np.mean(lm_eye_right, axis=0)
|
79 |
+
eye_avg = (eye_left + eye_right) * 0.5
|
80 |
+
eye_to_eye = 0.71*(eye_right - eye_left)
|
81 |
+
mouth_avg = lm[4]
|
82 |
+
eye_to_mouth = 1.35*(mouth_avg - eye_avg)
|
83 |
+
|
84 |
+
# Choose oriented crop rectangle.
|
85 |
+
x = eye_to_eye.copy()
|
86 |
+
x /= np.hypot(*x)
|
87 |
+
x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8)
|
88 |
+
x *= x_scale
|
89 |
+
y = np.flipud(x) * [-y_scale, y_scale]
|
90 |
+
c = eye_avg + eye_to_mouth * em_scale
|
91 |
+
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
|
92 |
+
quad_orig = quad.copy()
|
93 |
+
qsize = np.hypot(*x) * 2
|
94 |
+
|
95 |
+
img = src_img.convert('RGBA').convert('RGB')
|
96 |
+
|
97 |
+
# Shrink.
|
98 |
+
shrink = int(np.floor(qsize / output_size * 0.5))
|
99 |
+
if shrink > 1:
|
100 |
+
rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink)))
|
101 |
+
img = img.resize(rsize, Image.ANTIALIAS)
|
102 |
+
quad /= shrink
|
103 |
+
qsize /= shrink
|
104 |
+
|
105 |
+
# Crop.
|
106 |
+
border = max(int(np.rint(qsize * 0.1)), 3)
|
107 |
+
crop = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1]))))
|
108 |
+
crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), min(crop[3] + border, img.size[1]))
|
109 |
+
if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]:
|
110 |
+
img = img.crop(crop)
|
111 |
+
quad -= crop[0:2]
|
112 |
+
|
113 |
+
# Pad.
|
114 |
+
pad = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1]))))
|
115 |
+
pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), max(pad[3] - img.size[1] + border, 0))
|
116 |
+
if enable_padding and max(pad) > border - 4:
|
117 |
+
pad = np.maximum(pad, int(np.rint(qsize * 0.3)))
|
118 |
+
img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
|
119 |
+
h, w, _ = img.shape
|
120 |
+
y, x, _ = np.ogrid[:h, :w, :1]
|
121 |
+
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w-1-x) / pad[2]), 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h-1-y) / pad[3]))
|
122 |
+
blur = qsize * 0.02
|
123 |
+
img += (gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
|
124 |
+
img += (np.median(img, axis=(0,1)) - img) * np.clip(mask, 0.0, 1.0)
|
125 |
+
img = np.uint8(np.clip(np.rint(img), 0, 255))
|
126 |
+
if alpha:
|
127 |
+
mask = 1-np.clip(3.0 * mask, 0.0, 1.0)
|
128 |
+
mask = np.uint8(np.clip(np.rint(mask*255), 0, 255))
|
129 |
+
img = np.concatenate((img, mask), axis=2)
|
130 |
+
img = Image.fromarray(img, 'RGBA')
|
131 |
+
else:
|
132 |
+
img = Image.fromarray(img, 'RGB')
|
133 |
+
quad += pad[:2]
|
134 |
+
|
135 |
+
# Transform.
|
136 |
+
img = img.transform((transform_size, transform_size), Image.QUAD, (quad + 0.5).flatten(), Image.BILINEAR)
|
137 |
+
if output_size < transform_size:
|
138 |
+
img = img.resize((output_size, output_size), Image.ANTIALIAS)
|
139 |
+
|
140 |
+
return img, quad_orig
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
numpy
|
2 |
+
opencv-python-headless
|
3 |
+
Pillow
|
4 |
+
scikit-image
|
5 |
+
torch
|
6 |
+
torchvision
|
7 |
+
scipy
|
8 |
+
cmake
|
shape_predictor_5_face_landmarks.dat
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c4b1e9804792707d3a405c2c16a80a20269e6675021f64a41d30fffafbc41888
|
3 |
+
size 9150489
|
u2net_bce_itr_18000_train_3.891670_tar_0.553700_512x_460x.jit.pt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a4759db0de410078514769bdae185cf80b095e8a5b49aef31987ad5fa6e66a30
|
3 |
+
size 177290264
|