callum-canavan's picture
Remove comment
09b83c7
import cv2
from tqdm import tqdm
import numpy as np
from PIL import Image, ImageDraw, ImageFont, ImageChops
import imageio
from pygifsicle import optimize
import torchvision.transforms.functional as TF
from visual_anagrams.views import get_views
from visual_anagrams.utils import get_courier_font_path
def draw_text(image, text, fill=(0,0,0), frame_size=384, im_size=256):
image = image.copy()
# Font info
font_path = get_courier_font_path()
font_size = 16
# Make PIL objects
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font_path, font_size)
# Center text horizontally, and vertically between
# illusion bottom and frame bottom
text_position = (0, 0)
bbox = draw.textbbox(text_position, text, font=font, align='center')
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
text_left = (frame_size - text_width) // 2
text_top = int(3/4 * frame_size + 1/4 * im_size - 1/2 * text_height)
text_position = (text_left, text_top)
# Draw text on image
draw.text(text_position, text, font=font, fill=fill, align='center')
return image
def easeInOutQuint(x):
# From Matthew Tancik:
# https://github.com/tancik/Illusion-Diffusion/blob/main/IllusionDiffusion.ipynb
if x < 0.5:
return 4 * x**3
else:
return 1 - (-2 * x + 2)**3 / 2
def animate_two_view(
im_path,
view,
prompt_1,
prompt_2,
save_video_path='tmp.mp4',
hold_duration=60,
text_fade_duration=10,
transition_duration=80,
im_size=256,
frame_size=384,
):
'''
TODO: Assuming two views, first one is identity
'''
im = Image.open(im_path)
# Make list of frames
frames = []
# Make frames for two views
frame_1 = view.make_frame(im, 0.0)
frame_2 = view.make_frame(im, 1.0)
# Display frame 1 with text
frame_1_text = draw_text(frame_1,
prompt_1,
frame_size=frame_size,
im_size=im_size)
frames += [frame_1_text] * (hold_duration // 2)
# Fade out text 1
for t in np.linspace(0,1,text_fade_duration):
c = int(t * 255)
fill = (c,c,c)
frame = draw_text(frame_1,
prompt_1,
fill=fill,
frame_size=frame_size,
im_size=im_size)
frames.append(frame)
# Transition view 1 -> view 2
for t in tqdm(np.linspace(0,1,transition_duration)):
t_ease = easeInOutQuint(t)
frames.append(view.make_frame(im, t_ease))
# Fade in text 2
for t in np.linspace(1,0,text_fade_duration):
c = int(t * 255)
fill = (c,c,c)
frame = draw_text(frame_2,
prompt_2,
fill=fill,
frame_size=frame_size,
im_size=im_size)
frames.append(frame)
# Display frame 2 with text
frame_2_text = draw_text(frame_2,
prompt_2,
frame_size=frame_size,
im_size=im_size)
frames += [frame_2_text] * (hold_duration // 2)
# "Boomerang" the clip, so we get back to view 1
frames = frames + frames[::-1]
# Move last bit of clip to front
frames = frames[-hold_duration//2:] + frames[:-hold_duration//2]
images = frames
# Save the frames as a GIF
image_array = [imageio.core.asarray(frame) for frame in frames]
# Save as video
print('Making video...')
imageio.mimsave(save_video_path, image_array, fps=30)
if __name__ == '__main__':
import argparse
import pickle
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("--im_path", required=True, type=str, help='Path to the illusion to animate')
parser.add_argument("--save_video_path", default=None, type=str,
help='Path to save video to. If None, defaults to `im_path`, with extension `.mp4`')
parser.add_argument("--metadata_path", default=None, type=str, help='Path to metadata. If specified, overrides `view` and `prompt` args')
parser.add_argument("--view", default=None, type=str, help='Name of view to use')
parser.add_argument("--prompt_1", default='', nargs='+', type=str,
help='Prompt for first view. Passing multiple will join them with newlines.')
parser.add_argument("--prompt_2", default='', nargs='+', type=str,
help='Prompt for first view. Passing multiple will join them with newlines.')
args = parser.parse_args()
# Load image
im_path = Path(args.im_path)
# Get save dir
if args.save_video_path is None:
save_video_path = im_path.with_suffix('.mp4')
if args.metadata_path is None:
# Join prompts with newlines
prompt_1 = '\n'.join(args.prompt_1)
prompt_2 = '\n'.join(args.prompt_2)
# Get paths and views
view = get_views([args.view])[0]
else:
with open(args.metadata_path, 'rb') as f:
metadata = pickle.load(f)
view = metadata['views'][1]
m_args = metadata['args']
prompt_1 = f'{m_args.style} {m_args.prompts[0]}'.strip()
prompt_2 = f'{m_args.style} {m_args.prompts[1]}'.strip()
# Animate
animate_two_view(
im_path,
view,
prompt_1,
prompt_2,
save_video_path=save_video_path,
hold_duration=120,
text_fade_duration=10,
transition_duration=45,
im_size=256,
frame_size=384,
)