Follow-Your-Emoji / utils.py
boheng.xie
rePush
a6f6a7a
import os
import numpy as np
import imageio # requires imageio
import cv2
from tqdm import tqdm
from media_pipe.mp_utils import LMKExtractor
from media_pipe.draw_util import FaceMeshVisualizer
from media_pipe import FaceMeshAlign
def get_video_fps(video_path):
video = cv2.VideoCapture(video_path)
fps = video.get(cv2.CAP_PROP_FPS)
video.release()
return int(fps)
def process_video(video_path, save_dir, save_gif=True):
lmk_extractor = LMKExtractor()
vis = FaceMeshVisualizer(forehead_edge=False)
face_aligner = FaceMeshAlign()
frames = imageio.get_reader(video_path)
face_results = []
motions = []
# Process first frame to get reference
first_frame = next(iter(frames))
first_frame_bgr = cv2.cvtColor(np.array(first_frame), cv2.COLOR_RGB2BGR)
ref_result = lmk_extractor(first_frame_bgr)
if ref_result is None:
print("No face detected in the first frame. Exiting.")
return None, 0
ref_result['width'] = first_frame_bgr.shape[1]
ref_result['height'] = first_frame_bgr.shape[0]
face_results.append(ref_result)
# Process remaining frames
for frame in tqdm(frames):
frame_bgr = cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR)
face_result = lmk_extractor(frame_bgr)
if face_result is None:
continue
face_result['width'] = frame_bgr.shape[1]
face_result['height'] = frame_bgr.shape[0]
face_results.append(face_result)
lmks = face_result['lmks'].astype(np.float32)
motion = vis.draw_landmarks((frame_bgr.shape[1], frame_bgr.shape[0]), lmks, normed=True)
motions.append(motion)
# Perform alignment
aligned_motions = face_aligner(ref_result, face_results)
base_name = os.path.splitext(os.path.basename(video_path))[0]
npy_path = os.path.join(save_dir, f"{base_name}_mppose.npy")
np.save(npy_path, face_results)
if save_gif:
# Save regular GIF
gif_path = os.path.join(save_dir, f"{base_name}_mppose.gif")
imageio.mimsave(gif_path, motions, 'GIF', duration=0.2, loop=0)
# Save aligned GIF
aligned_gif_path = os.path.join(save_dir, f"{base_name}_mppose_aligned.gif")
imageio.mimsave(aligned_gif_path, aligned_motions, 'GIF', duration=0.2, loop=0)
return npy_path, len(face_results)
def get_npy_files(root_dir):
npy_files = []
for root, dirs, files in os.walk(root_dir):
for file in files:
if file.endswith('.npy'):
npy_files.append(os.path.join(root, file))
return npy_files
def get_frame_count(npy_path):
data = np.load(npy_path, allow_pickle=True)
return len(data) - 1
def show_gif(npy_path):
aligned_gif_path = npy_path.replace('.npy', '_aligned.gif')
if os.path.exists(aligned_gif_path):
return aligned_gif_path, "Aligned GIF found and displayed"
return None, "No aligned GIF found for this NPY file"
def process_image(image_path, npy_path, save_dir, expand_x=1.0, expand_y=1.0, offset_x=0.0, offset_y=0.0):
lmk_extractor = LMKExtractor()
vis = FaceMeshVisualizer(forehead_edge=False)
# Load data from npy file
face_results = np.load(npy_path, allow_pickle=True)
if len(face_results) == 0:
print("No face data in the NPY file. Exiting.")
return None
# Get dimensions from first frame in npy
target_width = face_results[0]['width']
target_height = face_results[0]['height']
# Process image
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
face_result = lmk_extractor(image)
if face_result is None:
print("No face detected in the image. Exiting.")
return None
# Crop image
landmarks = face_result['lmks']
min_x, min_y = np.min(landmarks, axis=0)[:2]
max_x, max_y = np.max(landmarks, axis=0)[:2]
center_x = (min_x + max_x) / 2 * image.shape[1]
center_y = (min_y + max_y) / 2 * image.shape[0]
# Apply expansion and offset
crop_width = target_width * expand_x
crop_height = target_height * expand_y
offset_x_pixels = offset_x * target_width
offset_y_pixels = offset_y * target_height
left = int(max(center_x - crop_width / 2 + offset_x_pixels, 0))
top = int(max(center_y - crop_height / 2 + offset_y_pixels, 0))
right = int(min(left + crop_width, image.shape[1]))
bottom = int(min(top + crop_height, image.shape[0]))
cropped_image = image_rgb[top:bottom, left:right]
# If cropped image is smaller than target size, add padding
if cropped_image.shape[0] < target_height or cropped_image.shape[1] < target_width:
pad_top = max(0, (target_height - cropped_image.shape[0]) // 2)
pad_bottom = max(0, target_height - cropped_image.shape[0] - pad_top)
pad_left = max(0, (target_width - cropped_image.shape[1]) // 2)
pad_right = max(0, target_width - cropped_image.shape[1] - pad_left)
cropped_image = cv2.copyMakeBorder(cropped_image, pad_top, pad_bottom, pad_left, pad_right, cv2.BORDER_CONSTANT)
cropped_image = cv2.resize(cropped_image, (target_width, target_height))
# Save cropped image
base_name = os.path.splitext(os.path.basename(image_path))[0]
cropped_image_path = os.path.join(save_dir, f"{base_name}_cropped.png")
cv2.imwrite(cropped_image_path, cv2.cvtColor(cropped_image, cv2.COLOR_RGB2BGR))
# Process cropped image
cropped_face_result = lmk_extractor(cv2.cvtColor(cropped_image, cv2.COLOR_RGB2BGR))
if cropped_face_result is None:
print("No face detected in the cropped image. Exiting.")
return None
cropped_face_result['width'] = target_width
cropped_face_result['height'] = target_height
# Visualize facial landmarks
lmks = cropped_face_result['lmks'].astype(np.float32)
motion = vis.draw_landmarks((target_width, target_height), lmks, normed=True)
# Save visualization
motion_path = os.path.join(save_dir, f"{base_name}_motion.png")
cv2.imwrite(motion_path, cv2.cvtColor(motion, cv2.COLOR_RGB2BGR))
return cropped_image_path, motion_path