Created
April 18, 2020 14:06
-
-
Save fepegar/572ba08ae43ee2ae60fc0d1fcaf574e0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tempfile | |
from pathlib import Path | |
from subprocess import call | |
# pip install moviepy opencv-python scikit-image face_recognition | |
import cv2 | |
import numpy as np | |
from PIL import Image | |
from tqdm import tqdm | |
import face_recognition | |
from moviepy.editor import VideoFileClip, ImageSequenceClip | |
from skimage.morphology import convex_hull_image | |
def get_face_landmarks(image): | |
face_landmarks_list = face_recognition.face_landmarks(image) | |
if not face_landmarks_list: | |
return | |
face_landmarks_dict = face_landmarks_list[0] | |
return get_landmarks_from_dict(image, face_landmarks_dict) | |
def get_landmarks_from_dict(image, face_landmarks_dict, clip=True): | |
face_landmarks = np.vstack(list(face_landmarks_dict.values())) | |
si, sj, _ = image.shape | |
np.clip(face_landmarks, (0, 0), (sj - 1, si - 1), out=face_landmarks) | |
landmarks_j, landmarks_i = face_landmarks.T | |
return landmarks_i, landmarks_j | |
def move_face(image, translation=(0, 0), show_landmarks=False): | |
# Get face landmarks | |
face_landmarks = get_face_landmarks(image) | |
if face_landmarks is None: | |
return None | |
# Make image with landmarks only | |
si, sj, _ = image.shape | |
mask = np.zeros((si, sj), dtype=np.uint8) | |
mask[face_landmarks] = 1 | |
# Compute convex hull | |
chull = convex_hull_image(mask) | |
face_indices = np.where(chull) | |
# Extract face pixels | |
values = image[face_indices] | |
# Remove face | |
image[chull] = 0 | |
# Show landmarks | |
if show_landmarks: | |
image[face_landmarks] = 255 | |
# Move face | |
ti, tj = translation | |
face_indices = np.array(face_indices) | |
face_indices[0] += int(ti) | |
face_indices[1] += int(tj) | |
face_indices = np.clip(face_indices.T, (0, 0), (si - 1, sj - 1)).T | |
image[tuple(face_indices)] = values | |
def drop_face(input_path, start_time, pixels_per_second, output_path): | |
"""Make face fall. | |
Args: | |
input_path: | |
start_time: Starting time in seconds. | |
pixels_per_second: Falling velocity. | |
output_path: | |
""" | |
input_path = Path(input_path) | |
start_time = float(start_time) | |
pixels_per_second = float(pixels_per_second) | |
output_path = Path(output_path) | |
clip = VideoFileClip(str(input_path)) | |
pixels_per_frame = pixels_per_second / clip.fps | |
num_frames = int(clip.fps * clip.duration) | |
offset = 0 | |
with tempfile.NamedTemporaryFile(suffix='.mp4') as f: | |
with tempfile.TemporaryDirectory() as tmpdirname: | |
paths = [] | |
for index, frame in tqdm(enumerate(clip.iter_frames()), total=num_frames): | |
frame = frame.copy() | |
time = 1 / clip.fps * index | |
if time > start_time: | |
offset += pixels_per_frame | |
move_face(frame, translation=(offset, 0)) | |
output_frame_name = f'{input_path.stem}_{index:08d}.jpg' | |
output_frame_path = Path(tmpdirname, output_frame_name) | |
Image.fromarray(frame).save(output_frame_path) | |
paths.append(str(output_frame_path)) | |
new_clip = ImageSequenceClip(paths, fps=clip.fps) | |
new_clip.write_videofile(f.name) | |
command = [ | |
'ffmpeg', | |
'-i', f.name, | |
'-i', input_path, | |
'-c', 'copy', | |
'-map', '0:v:0', | |
'-map', '1:a:0', | |
'-shortest', | |
output_path, | |
'-y', | |
] | |
command = [str(arg) for arg in command] | |
call(command) | |
def capture(): | |
# pylint: disable=no-member | |
cap = cv2.VideoCapture(0) | |
while(True): | |
# Capture frame-by-frame | |
_, frame = cap.read() | |
# Our operations on the frame come here | |
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
move_face(rgb, (65, -120)) | |
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) | |
bgr = bgr[:, ::-1] | |
# Display the resulting frame | |
cv2.imshow('frame', bgr) | |
if cv2.waitKey(1) & 0xFF == ord('q'): | |
break | |
# When everything done, release the capture | |
cap.release() | |
cv2.destroyAllWindows() | |
if __name__ == "__main__": | |
import sys | |
print(sys.argv[1:]) | |
input_path, start_time, pixels_per_second, output_path = sys.argv[1:] | |
drop_face(input_path, start_time, pixels_per_second, output_path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment