Ashrafb commited on
Commit
19937b3
·
verified ·
1 Parent(s): f97e001

Update face_detection.py

Browse files
Files changed (1) hide show
  1. face_detection.py +97 -89
face_detection.py CHANGED
@@ -1,27 +1,24 @@
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
@@ -30,7 +27,6 @@ def align(image_in, face_index=0, output_size=256):
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")
@@ -43,98 +39,110 @@ def composite_images(quad, img, output):
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.Resampling.LANCZOS)
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.Resampling.LANCZOS)
139
-
140
- return img, quad_orig
 
 
 
1
  import dlib
2
  import numpy as np
3
+ from PIL import Image, ImageOps
 
 
4
  from scipy.ndimage import gaussian_filter
5
  import cv2
6
 
 
7
  MODEL_PATH = "shape_predictor_5_face_landmarks.dat"
8
  detector = dlib.get_frontal_face_detector()
9
 
 
10
  def align(image_in, face_index=0, output_size=256):
11
  try:
12
  image_in = ImageOps.exif_transpose(image_in)
13
  except:
14
  print("exif problem, not rotating")
15
 
16
+ # Convert image to RGB format
17
+ image_in = ensure_rgb(np.array(image_in))
18
+
19
  landmarks = list(get_landmarks(image_in))
20
  n_faces = len(landmarks)
21
+ face_index = min(n_faces - 1, face_index)
22
  if n_faces == 0:
23
  aligned_image = image_in
24
  quad = None
 
27
 
28
  return aligned_image, n_faces, quad
29
 
 
30
  def composite_images(quad, img, output):
31
  """Composite an image into and output canvas according to transformed co-ords"""
32
  output = output.convert("RGBA")
 
39
  output.alpha_composite(img)
40
 
41
  return output.convert("RGB")
 
42
 
43
  def get_landmarks(image):
44
+ """Get landmarks from numpy image"""
45
  shape_predictor = dlib.shape_predictor(MODEL_PATH)
46
 
47
+ max_size = max(image.shape[:2])
48
+ reduction_scale = int(max_size / 512)
49
  if reduction_scale == 0:
50
  reduction_scale = 1
51
+ downscaled = cv2.resize(image, (image.shape[1] // reduction_scale, image.shape[0] // reduction_scale))
52
+
53
+ print(f"Type of downscaled image: {downscaled.dtype}, Shape: {downscaled.shape}")
54
+ if downscaled.dtype != np.uint8:
55
+ raise ValueError("Image is not in 8-bit format")
56
+
57
+ detections = detector(downscaled, 0)
58
 
59
  for detection in detections:
60
  try:
61
+ face_landmarks = [(reduction_scale * item.x, reduction_scale * item.y) for item in shape_predictor(downscaled, detection).parts()]
62
  yield face_landmarks
63
  except Exception as e:
64
  print(e)
65
 
66
+ def ensure_rgb(image):
67
+ if len(image.shape) == 2: # If the image is grayscale
68
+ return cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
69
+ elif image.shape[2] == 4: # If the image has an alpha channel
70
+ return cv2.cvtColor(image, cv2.COLOR_BGRA2RGB)
71
+ elif image.shape[2] == 3: # If the image is already RGB
72
+ return image
73
+ else:
74
+ raise ValueError("Unsupported image format")
75
 
76
  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):
77
+ # Align function modified from ffhq-dataset
78
+ # See https://github.com/NVlabs/ffhq-dataset for license
79
+
80
+ lm = np.array(face_landmarks)
81
+ lm_eye_left = lm[2:3] # left-clockwise
82
+ lm_eye_right = lm[0:1] # left-clockwise
83
+
84
+ # Calculate auxiliary vectors.
85
+ eye_left = np.mean(lm_eye_left, axis=0)
86
+ eye_right = np.mean(lm_eye_right, axis=0)
87
+ eye_avg = (eye_left + eye_right) * 0.5
88
+ eye_to_eye = 0.71 * (eye_right - eye_left)
89
+ mouth_avg = lm[4]
90
+ eye_to_mouth = 1.35 * (mouth_avg - eye_avg)
91
+
92
+ # Choose oriented crop rectangle.
93
+ x = eye_to_eye.copy()
94
+ x /= np.hypot(*x)
95
+ x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8)
96
+ x *= x_scale
97
+ y = np.flipud(x) * [-y_scale, y_scale]
98
+ c = eye_avg + eye_to_mouth * em_scale
99
+ quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
100
+ quad_orig = quad.copy()
101
+ qsize = np.hypot(*x) * 2
102
+
103
+ img = Image.fromarray(src_img).convert('RGBA').convert('RGB')
104
+
105
+ # Shrink.
106
+ shrink = int(np.floor(qsize / output_size * 0.5))
107
+ if shrink > 1:
108
+ rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink)))
109
+ img = img.resize(rsize, Image.Resampling.LANCZOS)
110
+ quad /= shrink
111
+ qsize /= shrink
112
+
113
+ # Crop.
114
+ border = max(int(np.rint(qsize * 0.1)), 3)
115
+ 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]))))
116
+ 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]))
117
+ if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]:
118
+ img = img.crop(crop)
119
+ quad -= crop[0:2]
120
+
121
+ # Pad.
122
+ 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]))))
123
+ 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))
124
+ if enable_padding and max(pad) > border - 4:
125
+ pad = np.maximum(pad, int(np.rint(qsize * 0.3)))
126
+ img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
127
+ h, w, _ = img.shape
128
+ y, x, _ = np.ogrid[:h, :w, :1]
129
+ 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]))
130
+ blur = qsize * 0.02
131
+ img += (gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
132
+ img += (np.median(img, axis=(0, 1)) - img) * np.clip(mask, 0.0, 1.0)
133
+ img = np.uint8(np.clip(np.rint(img), 0, 255))
134
+ if alpha:
135
+ mask = 1 - np.clip(3.0 * mask, 0.0, 1.0)
136
+ mask = np.uint8(np.clip(np.rint(mask * 255), 0, 255))
137
+ img = np.concatenate((img, mask), axis=2)
138
+ img = Image.fromarray(img, 'RGBA')
139
+ else:
140
+ img = Image.fromarray(img, 'RGB')
141
+ quad += pad[:2]
142
+
143
+ # Transform.
144
+ img = img.transform((transform_size, transform_size), Image.QUAD, (quad + 0.5).flatten(), Image.BILINEAR)
145
+ if output_size < transform_size:
146
+ img = img.resize((output_size, output_size), Image.Resampling.LANCZOS)
147
+
148
+ return img, quad_orig