Robys01 commited on
Commit
e425192
·
1 Parent(s): de0170b

Added Align and Order Options

Browse files
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+
3
+ *.mp4
4
+
5
+ aligned_images/
6
+
7
+ flagged/
app.py CHANGED
@@ -2,8 +2,10 @@ import gradio as gr
2
  from datetime import datetime
3
 
4
  from src.face_morp import morph
 
 
5
 
6
- def transition(image_files, duration, fps, method, guideline):
7
  time = datetime.now().strftime("%d.%m.%Y_%H.%M.%S")
8
  output_name = f"output_{time}_{fps}fps.mp4"
9
 
@@ -12,31 +14,42 @@ def transition(image_files, duration, fps, method, guideline):
12
  debug_messages = []
13
 
14
  try:
15
- # Apelează funcția morph și prinde mesajele de debug
 
 
 
 
 
 
 
 
16
  morph(image_files, duration, fps, output_name, guideline, is_dlib)
17
  debug_messages.append("Video generation successful")
18
  return output_name, "\n".join(debug_messages)
19
 
20
  except Exception as e:
21
  error_message = f"Error: {str(e)}"
 
22
  debug_messages.append(error_message)
23
  return None, "\n".join(debug_messages)
24
 
25
  if __name__ == "__main__":
26
-
27
  gr.Interface(
28
  fn=transition,
29
  inputs=[
30
  gr.File(file_count="multiple", type="filepath"),
31
  gr.Slider(label="Duration (seconds) between images", minimum=1, maximum=10, step=1, value=3),
32
- gr.Slider(label="Frames per second (fps)", minimum=1, maximum=60, step=1, value=30),
33
  gr.Dropdown(label="Landmarks detection method", choices=["Dlib", "MediaPipe"], value="Dlib"),
 
 
34
  gr.Checkbox(label="Guideline")
 
35
  ],
36
- outputs=[gr.Video(), gr.Textbox(label="Debug Messages")],
37
  examples=[
38
- [["examples/1.png", "examples/2.png", "examples/3.png"], 3, 30, "Dlib", False]
39
  ],
40
  title="Face Morphing",
41
  description="Upload multiple images containing faces to create a transition video between them."
42
- ).launch()
 
2
  from datetime import datetime
3
 
4
  from src.face_morp import morph
5
+ from src.utils.align_images import align_images
6
+ from src.utils.sort_images import sort_images
7
 
8
+ def transition(image_files, duration, fps, method, align_resize, order_images, guideline):
9
  time = datetime.now().strftime("%d.%m.%Y_%H.%M.%S")
10
  output_name = f"output_{time}_{fps}fps.mp4"
11
 
 
14
  debug_messages = []
15
 
16
  try:
17
+ # Align and resize images
18
+ if align_resize:
19
+ aligned_dir = "aligned_images"
20
+ image_files = align_images(image_files, aligned_dir)
21
+
22
+ # Sort images by age
23
+ if order_images:
24
+ image_files = sort_images(image_files)
25
+
26
  morph(image_files, duration, fps, output_name, guideline, is_dlib)
27
  debug_messages.append("Video generation successful")
28
  return output_name, "\n".join(debug_messages)
29
 
30
  except Exception as e:
31
  error_message = f"Error: {str(e)}"
32
+ print(error_message)
33
  debug_messages.append(error_message)
34
  return None, "\n".join(debug_messages)
35
 
36
  if __name__ == "__main__":
 
37
  gr.Interface(
38
  fn=transition,
39
  inputs=[
40
  gr.File(file_count="multiple", type="filepath"),
41
  gr.Slider(label="Duration (seconds) between images", minimum=1, maximum=10, step=1, value=3),
42
+ gr.Slider(label="Frames per second (fps)", minimum=2, maximum=60, step=1, value=30),
43
  gr.Dropdown(label="Landmarks detection method", choices=["Dlib", "MediaPipe"], value="Dlib"),
44
+ gr.Checkbox(label="Align and Resize Images", value=True),
45
+ gr.Checkbox(label="Order Images by Age"),
46
  gr.Checkbox(label="Guideline")
47
+
48
  ],
49
+ outputs=[gr.Video(), gr.Textbox(label="Output Message")],
50
  examples=[
51
+ [["examples/1.png", "examples/2.png", "examples/3.png"], 3, 30, "Dlib", False, False, False]
52
  ],
53
  title="Face Morphing",
54
  description="Upload multiple images containing faces to create a transition video between them."
55
+ ).launch(share=False)
requirements.txt CHANGED
@@ -4,4 +4,5 @@ numpy==1.26.4
4
  scipy==1.13.0
5
  mediapipe==0.10.11
6
  dlib==19.24.4
7
- tqdm==4.66.4
 
 
4
  scipy==1.13.0
5
  mediapipe==0.10.11
6
  dlib==19.24.4
7
+ tqdm==4.66.4
8
+ transformers==4.40.2
src/landmark_detector.py CHANGED
@@ -13,6 +13,26 @@ def read_image(image_path):
13
 
14
  return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  class DlibLandmarkDetector:
18
  def __init__(self, predictor_model_path=f'{os.path.dirname(os.path.abspath(__file__))}/utils/shape_predictor_68_face_landmarks.dat'):
 
13
 
14
  return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
15
 
16
+ class LandmarksDetector:
17
+ def __init__(self, predictor_model_path=f'{os.path.dirname(os.path.abspath(__file__))}/utils/shape_predictor_68_face_landmarks.dat'):
18
+ """
19
+ :param predictor_model_path: path to shape_predictor_68_face_landmarks.dat file
20
+ """
21
+ self.detector = dlib.get_frontal_face_detector() # cnn_face_detection_model_v1 also can be used
22
+ self.shape_predictor = dlib.shape_predictor(predictor_model_path)
23
+
24
+ def get_landmarks(self, image):
25
+ img = dlib.load_rgb_image(image)
26
+ dets = self.detector(img, 1)
27
+ dets = [dets[0]]
28
+
29
+ for detection in dets:
30
+ try:
31
+ face_landmarks = [(item.x, item.y) for item in self.shape_predictor(img, detection).parts()]
32
+ yield face_landmarks
33
+ except:
34
+ print("Exception in get_landmarks()!")
35
+
36
 
37
  class DlibLandmarkDetector:
38
  def __init__(self, predictor_model_path=f'{os.path.dirname(os.path.abspath(__file__))}/utils/shape_predictor_68_face_landmarks.dat'):
src/process_images.py CHANGED
@@ -18,6 +18,7 @@ def get_images_and_landmarks(image_list, is_dlib):
18
  raise ValueError("At least two images are required for morphing.")
19
  # exit()
20
 
 
21
  landmarks_list = [] # List of landmarks for each image
22
  images_list = [] # List of images
23
 
@@ -40,4 +41,9 @@ def get_images_and_landmarks(image_list, is_dlib):
40
  raise ValueError("At least two faces are required for morphing.")
41
  # exit()
42
 
 
 
 
 
 
43
  return images_list, landmarks_list
 
18
  raise ValueError("At least two images are required for morphing.")
19
  # exit()
20
 
21
+
22
  landmarks_list = [] # List of landmarks for each image
23
  images_list = [] # List of images
24
 
 
41
  raise ValueError("At least two faces are required for morphing.")
42
  # exit()
43
 
44
+ # if images dont have the same dimensions raise an error
45
+ if len(set([image.shape for image in images_list])) > 1:
46
+ raise ValueError("Images must have the same dimensions for morphing.")
47
+ # exit()
48
+
49
  return images_list, landmarks_list
src/utils/align_images.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import argparse
4
+ from src.utils.face_alignment import image_align
5
+ from src.landmark_detector import LandmarksDetector
6
+
7
+
8
+ def align_images(img_files, aligned_dir, output_size=1024, x_scale=1, y_scale=1, em_scale=0.1, use_alpha=False):
9
+ """
10
+ Extracts and aligns all faces from images using DLib and a function from original FFHQ dataset preparation step
11
+ python align_images.py /raw_images /aligned_images
12
+ """
13
+
14
+ ALIGNED_IMAGES_DIR = aligned_dir
15
+
16
+ # Create the directory if it doesn't exist
17
+ if not os.path.exists(ALIGNED_IMAGES_DIR):
18
+ os.makedirs(ALIGNED_IMAGES_DIR)
19
+ else: # Remove existing files in the directory
20
+ for file in os.listdir(ALIGNED_IMAGES_DIR):
21
+ os.remove(os.path.join(ALIGNED_IMAGES_DIR, file))
22
+
23
+ landmarks_detector = LandmarksDetector()
24
+ for img_path in img_files:
25
+ img_name = os.path.basename(img_path)
26
+ print('Aligning %s ...' % img_name)
27
+ try:
28
+ raw_img_path = img_path
29
+ fn = face_img_name = '%s_%02d.png' % (os.path.splitext(img_name)[0], 1)
30
+ if os.path.isfile(fn):
31
+ continue
32
+ print('Getting landmarks...')
33
+
34
+ for i, face_landmarks in enumerate(landmarks_detector.get_landmarks(raw_img_path), start=1):
35
+ try:
36
+ print('Starting face alignment...')
37
+ face_img_name = '%s_%02d.png' % (os.path.splitext(img_name)[0], i)
38
+ aligned_face_path = os.path.join(ALIGNED_IMAGES_DIR, face_img_name)
39
+ image_align(raw_img_path, aligned_face_path, face_landmarks, output_size=output_size, x_scale=x_scale, y_scale=y_scale, em_scale=em_scale, alpha=use_alpha)
40
+ print('Wrote result %s\n' % aligned_face_path)
41
+ except Exception as e:
42
+ raise Exception("Exception in face alignment!", e)
43
+ except:
44
+ raise Exception("Exception in landmark detection!")
45
+
46
+ # return absolute paths of aligned images
47
+ return [os.path.join(ALIGNED_IMAGES_DIR, img) for img in os.listdir(ALIGNED_IMAGES_DIR)]
src/utils/face_alignment.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import scipy.ndimage
3
+ import os
4
+ import PIL.Image
5
+ from PIL import Image
6
+
7
+
8
+ def image_align(src_file, dst_file, face_landmarks, output_size=1024, transform_size=4096, enable_padding=True, x_scale=1, y_scale=1, em_scale=0.1, alpha=False):
9
+ # Align function from FFHQ dataset pre-processing step
10
+ # https://github.com/NVlabs/ffhq-dataset/blob/master/download_ffhq.py
11
+
12
+ lm = np.array(face_landmarks)
13
+ lm_chin = lm[0 : 17] # left-right
14
+ lm_eyebrow_left = lm[17 : 22] # left-right
15
+ lm_eyebrow_right = lm[22 : 27] # left-right
16
+ lm_nose = lm[27 : 31] # top-down
17
+ lm_nostrils = lm[31 : 36] # top-down
18
+ lm_eye_left = lm[36 : 42] # left-clockwise
19
+ lm_eye_right = lm[42 : 48] # left-clockwise
20
+ lm_mouth_outer = lm[48 : 60] # left-clockwise
21
+ lm_mouth_inner = lm[60 : 68] # left-clockwise
22
+
23
+ # Calculate auxiliary vectors.
24
+ eye_left = np.mean(lm_eye_left, axis=0)
25
+ eye_right = np.mean(lm_eye_right, axis=0)
26
+ eye_avg = (eye_left + eye_right) * 0.5
27
+ eye_to_eye = eye_right - eye_left
28
+ mouth_left = lm_mouth_outer[0]
29
+ mouth_right = lm_mouth_outer[6]
30
+ mouth_avg = (mouth_left + mouth_right) * 0.5
31
+ eye_to_mouth = mouth_avg - eye_avg
32
+
33
+ # Choose oriented crop rectangle.
34
+ x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1]
35
+ x /= np.hypot(*x)
36
+ x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8)
37
+ x *= x_scale
38
+ y = np.flipud(x) * [-y_scale, y_scale]
39
+ c = eye_avg + eye_to_mouth * em_scale
40
+ quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
41
+ qsize = np.hypot(*x) * 2
42
+
43
+ # Load in-the-wild image.
44
+ if not os.path.isfile(src_file):
45
+ print('\nCannot find source image. Please run "--wilds" before "--align".')
46
+ return
47
+ img = PIL.Image.open(src_file).convert('RGBA').convert('RGB')
48
+
49
+ # Shrink.
50
+ shrink = int(np.floor(qsize / output_size * 0.5))
51
+ if shrink > 1:
52
+ rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink)))
53
+ img = img.resize(rsize, Image.Resampling.LANCZOS)
54
+ quad /= shrink
55
+ qsize /= shrink
56
+
57
+ # Crop.
58
+ border = max(int(np.rint(qsize * 0.1)), 3)
59
+ 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]))))
60
+ 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]))
61
+ if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]:
62
+ img = img.crop(crop)
63
+ quad -= crop[0:2]
64
+
65
+ # Pad.
66
+ 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]))))
67
+ 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))
68
+ if enable_padding and max(pad) > border - 4:
69
+ pad = np.maximum(pad, int(np.rint(qsize * 0.3)))
70
+ img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
71
+ h, w, _ = img.shape
72
+ y, x, _ = np.ogrid[:h, :w, :1]
73
+ 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]))
74
+ blur = qsize * 0.02
75
+ img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
76
+ img += (np.median(img, axis=(0,1)) - img) * np.clip(mask, 0.0, 1.0)
77
+ img = np.uint8(np.clip(np.rint(img), 0, 255))
78
+ if alpha:
79
+ mask = 1-np.clip(3.0 * mask, 0.0, 1.0)
80
+ mask = np.uint8(np.clip(np.rint(mask*255), 0, 255))
81
+ img = np.concatenate((img, mask), axis=2)
82
+ img = PIL.Image.fromarray(img, 'RGBA')
83
+ else:
84
+ img = PIL.Image.fromarray(img, 'RGB')
85
+ quad += pad[:2]
86
+
87
+ # Transform.
88
+ img = img.transform((transform_size, transform_size), PIL.Image.QUAD, (quad + 0.5).flatten(), PIL.Image.BILINEAR)
89
+ if output_size < transform_size:
90
+ img = img.resize((output_size, output_size), Image.Resampling.LANCZOS)
91
+
92
+ # Save aligned image.
93
+ img.save(dst_file, 'PNG')
src/utils/sort_images.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import pipeline
2
+
3
+
4
+ def sort_images(image_files):
5
+ pipe = pipeline("image-classification", model="Robys01/facial_age_estimator")
6
+
7
+ def get_age(image):
8
+ result = pipe(image)
9
+ print(image, "age:", result[0]["label"])
10
+ return result[0]["label"]
11
+
12
+
13
+ image_files.sort(key=get_age)
14
+
15
+ return image_files
16
+
17
+ if __name__ == "__main__":
18
+ image_files = ["examples/3.png", "examples/1.png", "examples/2.png"]
19
+ sorted_images = sort_images(image_files)
20
+
21
+
22
+ print(sorted_images)