|
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 |