Face-Morphing / src /face_morp.py
Robys01's picture
Upload code
cf27ba5 verified
raw
history blame
6.32 kB
import cv2
import time
import numpy as np
from tqdm import tqdm
from scipy.spatial import Delaunay
from concurrent.futures import ProcessPoolExecutor
from src.process_images import get_images_and_landmarks
def morph(image_files, duration, frame_rate, output, guideline, is_dlib):
# Get the list of images and landmarks
images_list, landmarks_list = get_images_and_landmarks(image_files, is_dlib)
video_frames = [] # List of frames for the video
sequence_time = time.time()
print("Generating morph sequence...", end="\n\n")
# Use ProcessPoolExecutor to parallelize the generation of morph sequences
with ProcessPoolExecutor() as executor:
futures = []
for i in range(1, len(images_list)):
src_image, src_landmarks = images_list[i-1], landmarks_list[i-1]
dst_image, dst_landmarks = images_list[i], landmarks_list[i]
# Generate Delaunay Triangulation
tri = Delaunay(dst_landmarks).simplices
# Submit the task to the executor
futures.append((i, executor.submit(generate_morph_sequence, duration, frame_rate, src_image, dst_image, src_landmarks, dst_landmarks, tri, guideline)))
# Retrieve and store the results in the correct order
results = [None] * (len(images_list) - 1)
for idx, future in futures:
results[idx - 1] = future.result()
for sequence_frames in results:
video_frames.extend(sequence_frames)
print(f"Total time taken to generate morph sequence: {time.time() - sequence_time:.2f} seconds", end="\n\n")
# Write the frames to a video file
write_frames_to_video(video_frames, frame_rate, output)
def generate_morph_sequence(duration, frame_rate, image1, image2, landmarks1, landmarks2, tri, guideline):
num_frames = int(duration * frame_rate)
morphed_frames = []
for frame in range(num_frames):
alpha = frame / (num_frames - 1)
# Working with floats for better precision
image1_float = np.float32(image1)
image2_float = np.float32(image2)
# Compute the intermediate landmarks at time alpha
landmarks = []
for i in range(len(landmarks1)):
x = (1 - alpha) * landmarks1[i][0] + alpha * landmarks2[i][0]
y = (1 - alpha) * landmarks1[i][1] + alpha * landmarks2[i][1]
landmarks.append((x, y))
# Allocate space for final output
morphed_frame = np.zeros_like(image1_float)
for i in range(len(tri)):
x = tri[i][0]
y = tri[i][1]
z = tri[i][2]
t1 = [landmarks1[x], landmarks1[y], landmarks1[z]]
t2 = [landmarks2[x], landmarks2[y], landmarks2[z]]
t = [landmarks[x], landmarks[y], landmarks[z]]
# Morph one triangle at a time.
morph_triangle(image1_float, image2_float, morphed_frame, t1, t2, t, alpha)
if guideline:
# Draw lines for the face landmarks
points = [(int(t[i][0]), int(t[i][1])) for i in range(3)]
for i in range(3):
# image, (x1, y1), (x2, y2), color, thickness, lineType, shift
cv2.line(morphed_frame, points[i], points[(i + 1) % 3], (255, 255, 255), 1, 8, 0)
# Convert the morphed image to RGB color space (from BGR)
morphed_frame = cv2.cvtColor(np.uint8(morphed_frame), cv2.COLOR_BGR2RGB)
morphed_frames.append(morphed_frame)
return morphed_frames
def morph_triangle(image1, image2, morphed_image, t1, t2, t, alpha):
# Calculate bounding rectangles and offset points together
r, r1, r2 = [cv2.boundingRect(np.float32([tri])) for tri in [t, t1, t2]]
# Offset the triangle points by the top-left corner of the corresponding bounding rectangle
t_rect, t1_rect, t2_rect = [[(tri[i][0] - rect[0], tri[i][1] - rect[1]) for i in range(3)]
for tri, rect in zip([t, t1, t2], [r, r1, r2])]
# Create a mask to keep only the pixels inside the triangle
mask = np.zeros((r[3], r[2], 3), dtype=np.float32)
# Fill the mask with white pixels inside the triangle
cv2.fillConvexPoly(mask, np.int32(t_rect), (1.0, 1.0, 1.0), 16, 0)
# Extract the triangle from the first and second image
image1_rect = image1[r1[1]:r1[1]+r1[3], r1[0]:r1[0]+r1[2]]
image2_rect = image2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]]
size = (r[2], r[3]) # Size of the bounding rectangle
# Apply affine transformation to warp the triangles from the source image to the destination image
warpImage1 = apply_affine_transform(image1_rect, t1_rect, t_rect, size)
warpImage2 = apply_affine_transform(image2_rect, t2_rect, t_rect, size)
# Perform alpha blending between the warped triangles and copy the result to the destination image
morphed_image_rect = warpImage1 * (1 - alpha) + warpImage2 * alpha
morphed_image[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] = morphed_image[r[1]:r[1]+r[3], r[0]:r[0]+r[2]] * (1 - mask) + morphed_image_rect * mask
return morphed_image
def apply_affine_transform(img, src, dst, size):
"""
Apply an affine transformation to the image.
"""
warp_matrix = cv2.getAffineTransform(np.float32(src), np.float32(dst))
return cv2.warpAffine(img, warp_matrix, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)
def write_frames_to_video(frames, frame_rate, output):
# Get the height and width of the frames
height, width, _ = frames[0].shape
# Cut the outside pixels to remove the black border
pad = 2
new_height = height - pad * 2
new_width = width - pad * 2
# Initialize the video writer
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output, fourcc, frame_rate, (new_width, new_height))
# Write the frames to the video
print("Writing frames to video...")
for frame in tqdm(frames):
# Cut the outside pixels
cut_frame = frame[pad:new_height+pad, pad:new_width+pad]
out.write(cut_frame)
out.release()
print(f"Video saved at: {output}")