|
import copy
|
|
import os
|
|
import shutil
|
|
|
|
import cv2
|
|
import gradio as gr
|
|
import numpy as np
|
|
import modules.scripts as scripts
|
|
|
|
from modules import images, processing
|
|
from modules.processing import process_images, Processed
|
|
from modules.shared import opts
|
|
from PIL import Image, ImageFilter, ImageColor, ImageOps
|
|
from pathlib import Path
|
|
from typing import List, Tuple, Iterable
|
|
|
|
|
|
|
|
def get_all_frames_from_path(path):
|
|
if not os.path.isdir(path):
|
|
return None
|
|
frame_list = []
|
|
for filename in sorted(os.listdir(path)):
|
|
if filename.endswith(".jpg") or filename.endswith(".png"):
|
|
img_path = os.path.join(path, filename)
|
|
img = cv2.imread(img_path)
|
|
if img is not None:
|
|
frame_list.append(img)
|
|
frame_list.insert(0, frame_list[0])
|
|
return frame_list
|
|
|
|
|
|
|
|
def get_images_from_path(path):
|
|
if not os.path.isdir(path):
|
|
return None
|
|
images = []
|
|
for filename in os.listdir(path):
|
|
if filename.endswith('.jpg') or filename.endswith('.png'):
|
|
img_path = os.path.join(path, filename)
|
|
img = Image.open(img_path)
|
|
images.append(img)
|
|
images.append(images[-1])
|
|
images.insert(0, images[0])
|
|
return images
|
|
|
|
|
|
def get_min_frame_num(video_list):
|
|
min_frame_num = -1
|
|
for video in video_list:
|
|
if video is None:
|
|
continue
|
|
else:
|
|
frame_num = len(video)
|
|
print(frame_num)
|
|
if min_frame_num < 0:
|
|
min_frame_num = frame_num
|
|
elif frame_num < min_frame_num:
|
|
min_frame_num = frame_num
|
|
return min_frame_num
|
|
|
|
|
|
|
|
|
|
|
|
def basic(target, blend, opacity):
|
|
return target * opacity + blend * (1-opacity)
|
|
|
|
def blender(func):
|
|
def blend(target, blend, opacity=1, *args):
|
|
res = func(target, blend, *args)
|
|
res = basic(res, blend, opacity)
|
|
return np.clip(res, 0, 1)
|
|
return blend
|
|
|
|
|
|
class Blend:
|
|
@classmethod
|
|
def method(cls, name):
|
|
return getattr(cls, name)
|
|
|
|
normal = basic
|
|
|
|
@staticmethod
|
|
@blender
|
|
def darken(target, blend, *args):
|
|
return np.minimum(target, blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def multiply(target, blend, *args):
|
|
return target * blend
|
|
|
|
@staticmethod
|
|
@blender
|
|
def color_burn(target, blend, *args):
|
|
return 1 - (1-target)/blend
|
|
|
|
@staticmethod
|
|
@blender
|
|
def linear_burn(target, blend, *args):
|
|
return target+blend-1
|
|
|
|
@staticmethod
|
|
@blender
|
|
def lighten(target, blend, *args):
|
|
return np.maximum(target, blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def screen(target, blend, *args):
|
|
return 1 - (1-target) * (1-blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def color_dodge(target, blend, *args):
|
|
return target/(1-blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def linear_dodge(target, blend, *args):
|
|
return target+blend
|
|
|
|
@staticmethod
|
|
@blender
|
|
def overlay(target, blend, *args):
|
|
return (target>0.5) * (1-(2-2*target)*(1-blend)) +\
|
|
(target<=0.5) * (2*target*blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def soft_light(target, blend, *args):
|
|
return (blend>0.5) * (1 - (1-target)*(1-(blend-0.5))) +\
|
|
(blend<=0.5) * (target*(blend+0.5))
|
|
|
|
@staticmethod
|
|
@blender
|
|
def hard_light(target, blend, *args):
|
|
return (blend>0.5) * (1 - (1-target)*(2-2*blend)) +\
|
|
(blend<=0.5) * (2*target*blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def vivid_light(target, blend, *args):
|
|
return (blend>0.5) * (1 - (1-target)/(2*blend-1)) +\
|
|
(blend<=0.5) * (target/(1-2*blend))
|
|
|
|
@staticmethod
|
|
@blender
|
|
def linear_light(target, blend, *args):
|
|
return (blend>0.5) * (target + 2*(blend-0.5)) +\
|
|
(blend<=0.5) * (target + 2*blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def pin_light(target, blend, *args):
|
|
return (blend>0.5) * np.maximum(target,2*(blend-0.5)) +\
|
|
(blend<=0.5) * np.minimum(target,2*blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def difference(target, blend, *args):
|
|
return np.abs(target - blend)
|
|
|
|
@staticmethod
|
|
@blender
|
|
def exclusion(target, blend, *args):
|
|
return 0.5 - 2*(target-0.5)*(blend-0.5)
|
|
|
|
blend_methods = [i for i in Blend.__dict__.keys() if i[0]!='_' and i!='method']
|
|
|
|
|
|
|
|
def blend_images(base_img, blend_img, blend_method, blend_opacity, do_invert):
|
|
|
|
img_base = np.array(base_img.convert("RGB")).astype(np.float64)/255
|
|
|
|
if do_invert:
|
|
img_to_blend = ImageOps.invert(blend_img.convert('RGB'))
|
|
else:
|
|
img_to_blend = blend_img
|
|
|
|
img_to_blend = img_to_blend.resize((int(base_img.width), int(base_img.height)))
|
|
|
|
img_to_blend = np.array(img_to_blend.convert("RGB")).astype(np.float64)/255
|
|
|
|
img_blended = Blend.method(blend_method)(img_to_blend, img_base, blend_opacity)
|
|
|
|
img_blended *= 255
|
|
|
|
img_blended = Image.fromarray(img_blended.astype(np.uint8), mode='RGB')
|
|
|
|
return img_blended
|
|
|
|
|
|
|
|
class Script(scripts.Script):
|
|
|
|
def title(self):
|
|
return "controlnet I2I sequence_toyxyz_v2"
|
|
|
|
def show(self, is_img2img):
|
|
return is_img2img
|
|
|
|
def ui(self, is_img2img):
|
|
|
|
ctrls_group = ()
|
|
max_models = opts.data.get("control_net_max_models_num", 1)
|
|
|
|
input_list = []
|
|
|
|
with gr.Group():
|
|
with gr.Accordion("ControlNet-I2I-sequence-toyxyz", open = True):
|
|
with gr.Column():
|
|
|
|
feed_prev_frame = gr.Checkbox(value=False, label="Feed previous frame / Reduce flickering by feeding the previous frame image generated by Img2Img")
|
|
|
|
use_init_img = gr.Checkbox(value=False, label="Blend color image / Blend the color image sequence with the initial Img2Img image or previous frame")
|
|
|
|
use_TemporalNet = gr.Checkbox(value=False, label="Use TemporalNet / Using TemporalNet to reduce flicker between image sequences. Add TemporalNet in addition to the multi-controlnet you need. It should be placed at the end of the controlnet list.")
|
|
|
|
blendmode = gr.Dropdown(blend_methods, value='normal', label='Blend mode / Choose how to blend the color image with the Previous frame or Img2Img initial image')
|
|
|
|
opacityvalue = gr.Slider(0, 1, value=0, label="Opacity / Previous frame or Img2Img initial image + (color image * opacity)", info="Choose betwen 0 and 1")
|
|
|
|
|
|
for i in range(max_models):
|
|
input_path = gr.Textbox(label=f"ControlNet-{i}", placeholder="image sequence path")
|
|
input_list.append(input_path)
|
|
|
|
tone_image_path = gr.Textbox(label=f"Color_Image / Color images to be used for Img2Img in sequence", placeholder="image sequence path")
|
|
|
|
output_path = gr.Textbox(label=f"Output_path / Deletes the contents located in the path, and creates a new path if it does not exist", placeholder="Output path")
|
|
|
|
ctrls_group += tuple(input_list) + (use_TemporalNet, use_init_img, opacityvalue, blendmode, feed_prev_frame, tone_image_path, output_path)
|
|
|
|
return ctrls_group
|
|
|
|
|
|
|
|
|
|
def run(self, p, *args):
|
|
|
|
path = p.outpath_samples
|
|
|
|
output_path = args[-1]
|
|
|
|
feedprev = args[-3]
|
|
|
|
blendm = args[-4]
|
|
|
|
opacityval = args[-5]
|
|
|
|
useinit = args[-6]
|
|
|
|
usetempo = args[-7]
|
|
|
|
|
|
|
|
if os.path.isdir(output_path):
|
|
for file in os.scandir(output_path):
|
|
os.remove(file.path)
|
|
else :
|
|
os.mkdir(output_path)
|
|
|
|
|
|
video_num = opts.data.get("control_net_max_models_num", 1)
|
|
|
|
|
|
image_list = [get_all_frames_from_path(image) for image in args[:video_num]]
|
|
|
|
|
|
color_image_list = get_images_from_path(args[-2])
|
|
|
|
|
|
previmg = p.init_images
|
|
|
|
tempoimg = p.init_images[0]
|
|
|
|
|
|
initial_color_corrections = [processing.setup_color_correction(p.init_images[0])]
|
|
|
|
|
|
initial_image = p.init_images[0]
|
|
|
|
|
|
frame_num = get_min_frame_num(image_list)
|
|
|
|
|
|
if frame_num > 0:
|
|
output_image_list = []
|
|
|
|
for frame in range(frame_num):
|
|
copy_p = copy.copy(p)
|
|
copy_p.control_net_input_image = []
|
|
for video in image_list:
|
|
if video is None:
|
|
continue
|
|
copy_p.control_net_input_image.append(video[frame])
|
|
|
|
if usetempo == True :
|
|
copy_p.control_net_input_image.append(tempoimg)
|
|
|
|
|
|
if color_image_list and feedprev == False:
|
|
|
|
if frame<len(color_image_list):
|
|
tone_image = color_image_list[frame+1]
|
|
|
|
if useinit:
|
|
tone_image = blend_images(initial_image, tone_image, blendm, opacityval, False)
|
|
|
|
p.init_images = [tone_image.convert("RGB")]
|
|
|
|
proc = process_images(copy_p)
|
|
|
|
|
|
|
|
if feedprev == True and useinit == False:
|
|
if previmg is None:
|
|
continue
|
|
else:
|
|
previmg = proc.images[0]
|
|
|
|
if frame == 0:
|
|
previmg = initial_image
|
|
|
|
p.init_images = [previmg]
|
|
|
|
if opts.img2img_color_correction:
|
|
p.color_corrections = initial_color_corrections
|
|
|
|
|
|
if feedprev == True and color_image_list and useinit:
|
|
if previmg is None:
|
|
continue
|
|
else:
|
|
previmg = proc.images[0]
|
|
|
|
if frame == 0:
|
|
previmg = initial_image
|
|
|
|
previmg = blend_images(previmg, color_image_list[frame+1], blendm, opacityval, False)
|
|
|
|
|
|
p.init_images = [previmg]
|
|
|
|
if opts.img2img_color_correction:
|
|
p.color_corrections = initial_color_corrections
|
|
|
|
img = proc.images[0]
|
|
|
|
if usetempo == True :
|
|
if frame > 0 :
|
|
tempoimg = proc.images[0]
|
|
|
|
|
|
|
|
if(frame>0):
|
|
images.save_image(img, output_path, f"Frame_{frame}")
|
|
copy_p.close()
|
|
|
|
|
|
else:
|
|
proc = process_images(p)
|
|
|
|
return proc |