Spaces:
Running
Running
File size: 6,316 Bytes
cf27ba5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
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}")
|