265 lines
13 KiB
Python
265 lines
13 KiB
Python
import cv2
|
|
import torch
|
|
import numpy as np
|
|
from .AnyText2_utils import check_channels, resize_image, separate_pos_imgs, find_polygon, arr2tensor
|
|
from .AnyText2_t3_dataset import draw_glyph, draw_glyph2, draw_font_hint
|
|
from AnyText2ControlDiffusion.cldm.recognizer import crop_image
|
|
from ....UL_common.common import tensor2pil
|
|
import os
|
|
from .AnyText_Infer import generate_rectangles
|
|
|
|
mimic_list = font_hint_image = font_hint_mask =[]
|
|
|
|
def encode(
|
|
model, font, fonts, mask, prompt, texts, latent, mode, sort_radio, a_prompt, n_prompt, revise_pos, random_mask, Random_Gen_Mask_path, image, font_apply, show_glyph, **extra_kwargs
|
|
):
|
|
max_chars = 50
|
|
batch_size, height, width = latent["samples"].shape[0], latent["samples"].shape[2] * 8, latent["samples"].shape[3] * 8 # B, C, H, W
|
|
|
|
sort_radio = '↔' if sort_radio else '↕'
|
|
|
|
img_count = batch_size
|
|
h, w = height, width
|
|
glyline_font_path, text_colors = fonts.glyline_font_path, fonts.text_colors
|
|
n_lines = len(texts)
|
|
|
|
if not random_mask:
|
|
pos_image = np.array(tensor2pil(mask))
|
|
pos_image = 255 - resize_image(pos_image, max(height, width))#[..., :3]
|
|
else:
|
|
generate_rectangles(width, height, n_lines, 500, Random_Gen_Mask_path)
|
|
pos_image = cv2.imread(Random_Gen_Mask_path)[..., ::-1]
|
|
if random_mask:
|
|
pos_image = pos_image[..., 0:1]
|
|
draw_pos = pos_image
|
|
|
|
h, w = draw_pos.shape[:2]
|
|
|
|
if mode:
|
|
mode = 'text-generation'
|
|
revise_pos = revise_pos
|
|
else:
|
|
if image == None:
|
|
raise ValueError('Edit mode need a image.')
|
|
ori_image = np.array(tensor2pil(image))
|
|
mode = 'text-editing'
|
|
revise_pos = False
|
|
|
|
if mode in ['text-generation', 'gen']:
|
|
edit_image = np.zeros((h, w, 3))
|
|
elif mode in ['text-editing', 'edit']:
|
|
if draw_pos is None or ori_image is None:
|
|
return None, -1, "Reference image and position image are needed for text editing!", ""
|
|
if isinstance(ori_image, str):
|
|
ori_image = cv2.imread(ori_image)[..., ::-1]
|
|
assert ori_image is not None, f"Can't read ori_image image from{ori_image}!"
|
|
elif isinstance(ori_image, torch.Tensor):
|
|
ori_image = ori_image.cpu().numpy()
|
|
else:
|
|
assert isinstance(ori_image, np.ndarray), f'Unknown format of ori_image: {type(ori_image)}'
|
|
# edit_image = ori_image[..., ::-1] #bgr2rgb
|
|
edit_image = ori_image.clip(1, 255) # for mask reason
|
|
edit_image = check_channels(edit_image)
|
|
edit_image = resize_image(edit_image, max_length=max(height, width)) # make w h multiple of 64, resize if w or h > max_length
|
|
h, w = edit_image.shape[:2] # change h, w by input ref_img
|
|
|
|
# preprocess pos_imgs(if numpy, make sure it's white pos in black bg)
|
|
if draw_pos is None:
|
|
pos_imgs = np.zeros((w, h, 1))
|
|
if isinstance(draw_pos, str):
|
|
draw_pos = cv2.imread(draw_pos)[..., ::-1]
|
|
assert draw_pos is not None, f"Can't read draw_pos image from{draw_pos}!"
|
|
pos_imgs = 255-draw_pos
|
|
elif isinstance(draw_pos, torch.Tensor):
|
|
pos_imgs = draw_pos.cpu().numpy()
|
|
else:
|
|
# assert isinstance(draw_pos, np.ndarray), f'Unknown format of draw_pos: {type(draw_pos)}'
|
|
pos_imgs = cv2.resize(draw_pos, (w, h))
|
|
if mode in ['text-editing', 'edit']:
|
|
pos_imgs = cv2.resize(pos_imgs, (w, h))
|
|
|
|
pos_imgs = 255 - pos_imgs#[..., 0:1]
|
|
pos_imgs = cv2.convertScaleAbs(pos_imgs)
|
|
_, pos_imgs = cv2.threshold(pos_imgs, 254, 255, cv2.THRESH_BINARY)
|
|
# seprate pos_imgs
|
|
pos_imgs = separate_pos_imgs(pos_imgs, sort_radio)
|
|
|
|
if len(pos_imgs) == 0:
|
|
pos_imgs = [np.zeros((h, w, 1))]
|
|
if len(pos_imgs) < n_lines:
|
|
if n_lines == 1 and texts[0] == ' ':
|
|
# pass # text-to-image without text
|
|
print('\033[93m', f'Warning: text-to-image without text.', '\033[0m')
|
|
else:
|
|
raise ValueError(f'Found {len(pos_imgs)} positions that < needed {n_lines} from prompt, check and try again(手绘遮罩数少于要绘制的文本数,检查修改再重试)!')
|
|
elif len(pos_imgs) > n_lines:
|
|
print('\033[93m', f'Warning: found {len(pos_imgs)} positions that > needed {n_lines} from prompt.', '\033[0m')
|
|
|
|
# get pre_pos, poly_list, hint that needed for anytext
|
|
pre_pos = []
|
|
poly_list = []
|
|
for input_pos in pos_imgs:
|
|
if input_pos.mean() != 0:
|
|
input_pos = input_pos[..., np.newaxis] if len(input_pos.shape) == 2 else input_pos
|
|
poly, pos_img = find_polygon(input_pos)
|
|
pre_pos += [pos_img/255.]
|
|
poly_list += [poly]
|
|
else:
|
|
pre_pos += [np.zeros((h, w, 1))]
|
|
poly_list += [None]
|
|
np_hint = np.sum(pre_pos, axis=0).clip(0, 1)
|
|
# prepare info dict
|
|
info = {}
|
|
info['glyphs'] = []
|
|
info['gly_line'] = []
|
|
info['positions'] = []
|
|
info['n_lines'] = [len(texts)]*img_count
|
|
font_hint = []
|
|
font_paths = ['None' for i in range(len(texts))]
|
|
if glyline_font_path:
|
|
glyline_font_path = glyline_font_path[:len(texts)]
|
|
font_paths[:len(glyline_font_path)] = glyline_font_path
|
|
info['colors'] = [np.array([500, 500, 500]) for i in range(len(texts))]
|
|
if text_colors:
|
|
text_colors = text_colors.strip().split()[:len(texts)]
|
|
info['colors'][:len(text_colors)] = [np.array([int(p) for p in s.split(',')]) for s in text_colors]
|
|
|
|
gly_pos_imgs = []
|
|
font_hint_mimic_imgs = []
|
|
|
|
gly_line_list = []
|
|
glyphs_list = []
|
|
|
|
for i in range(len(texts)):
|
|
text = texts[i]
|
|
if len(text) > max_chars:
|
|
# str_warning = f'"{text}" length > max_chars: {max_chars}, will be cut off...'
|
|
print(f'"{text}" length > max_chars: {max_chars}, will be cut off...')
|
|
text = text[:max_chars]
|
|
|
|
gly_scale = model.model.control_model.glyph_scale
|
|
|
|
if pre_pos[i].mean() != 0:
|
|
if font_apply:
|
|
gly_line = draw_glyph(font, text, info['colors'][i], show_glyph)
|
|
glyphs = draw_glyph2(font, text, poly_list[i], info['colors'][i], scale=gly_scale, width=w, height=h, add_space=True, show_glyph=show_glyph)
|
|
else:
|
|
gly_line = draw_glyph(font_paths[i], text, info['colors'][i], show_glyph)
|
|
glyphs = draw_glyph2(font_paths[i], text, poly_list[i], info['colors'][i], scale=gly_scale, width=w, height=h, add_space=True, show_glyph=show_glyph)
|
|
|
|
gly_line_list+=[gly_line]
|
|
glyphs_list+=[glyphs]
|
|
|
|
if i < len(font_hint_image) and font_hint_image[i] is not None:
|
|
hint_poly = font_hint_mask[i]
|
|
poly, _ = find_polygon(hint_poly)
|
|
font_hint_mimic_img, _ = draw_font_hint((font_hint_image[i]/127.5-1), poly)
|
|
font_hint_mimic_img = torch.from_numpy(font_hint_mimic_img*255).permute(2, 0, 1).repeat(3, 1, 1)
|
|
font_hint_mimic_imgs += [crop_image(font_hint_mimic_img, hint_poly)] # chw, tensor, 0-255
|
|
font_paths[i] = 'None' # not render
|
|
else:
|
|
font_hint_mimic_imgs += [None]
|
|
if font_apply:
|
|
font_hint_line = draw_glyph2(font_paths[i], text, poly_list[i], np.array([255, 255, 255]), scale=1, width=w, height=h, add_space=True, show_glyph=show_glyph)
|
|
else:
|
|
font_hint_line = draw_glyph2(font_paths[i], text, poly_list[i], info['colors'][i], scale=1, width=w, height=h, add_space=True, show_glyph=show_glyph)
|
|
gly_pos_img = cv2.drawContours(glyphs*255, [poly_list[i]*gly_scale], 0, (255, 255, 255), 1)
|
|
|
|
if revise_pos:
|
|
resize_gly = cv2.resize(glyphs, (pre_pos[i].shape[1], pre_pos[i].shape[0]))
|
|
new_pos = cv2.morphologyEx((resize_gly*255).astype(np.uint8), cv2.MORPH_CLOSE, kernel=np.ones((resize_gly.shape[0]//10, resize_gly.shape[1]//10), dtype=np.uint8), iterations=1)
|
|
new_pos = new_pos[..., np.newaxis] if len(new_pos.shape) == 2 else new_pos
|
|
contours, _ = cv2.findContours(new_pos[..., 0:1], cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
if len(contours) != 1:
|
|
str_warning = f'Fail to revise position {i} to bounding rect, remain position unchanged...'
|
|
else:
|
|
rect = cv2.minAreaRect(contours[0])
|
|
poly = np.int0(cv2.boxPoints(rect))
|
|
pre_pos[i] = cv2.drawContours(new_pos, [poly], -1, 255, -1) / 255.
|
|
gly_pos_img = cv2.drawContours(glyphs*255, [poly*gly_scale], 0, (255, 255, 255), 1)
|
|
|
|
gly_pos_imgs += [gly_pos_img] # for show
|
|
else:
|
|
glyphs = np.zeros((h*gly_scale, w*gly_scale, 3))
|
|
gly_line = np.zeros((80, 512, 1))
|
|
gly_pos_imgs += [np.zeros((h*gly_scale, w*gly_scale, 1))] # for show
|
|
font_hint_line = np.zeros((h, w, 3))
|
|
pos = pre_pos[i][..., 0:1]
|
|
info['glyphs'] += [arr2tensor(glyphs, img_count, model.load_device, model.dtype)]
|
|
info['gly_line'] += [arr2tensor(gly_line, img_count, model.load_device, model.dtype)]
|
|
info['positions'] += [arr2tensor(pos, img_count, model.load_device, model.dtype)]
|
|
info['colors'][i] = arr2tensor(info['colors'][i], img_count, model.load_device, model.dtype)/255.
|
|
font_hint += [font_hint_line]
|
|
font_hint_mimic_imgs = [font_hint_mimic_imgs] * img_count
|
|
model.model.embedding_manager.font_hint_mimic_imgs = font_hint_mimic_imgs
|
|
# get masked_x
|
|
masked_img = ((edit_image.astype(np.float32) / 127.5) - 1.0 - np_hint*10).clip(-1, 1)
|
|
masked_img = np.transpose(masked_img, (2, 0, 1))
|
|
masked_img = torch.from_numpy(masked_img.copy()).float().cuda(0)
|
|
|
|
# if self.use_fp16:
|
|
# masked_img = masked_img.half()
|
|
masked_img = masked_img.to(model.dtype)
|
|
|
|
encoder_posterior = model.model.encode_first_stage(masked_img[None, ...])
|
|
masked_x = model.model.get_first_stage_encoding(encoder_posterior).detach()
|
|
|
|
# if self.use_fp16:
|
|
# masked_x = masked_x.half()
|
|
masked_x = masked_x.to(model.dtype)
|
|
|
|
info['masked_x'] = torch.cat([masked_x for _ in range(img_count)], dim=0)
|
|
|
|
hint = arr2tensor(np_hint, img_count, model.load_device, model.dtype)
|
|
|
|
font_hint_fg = np.sum(font_hint, axis=0).clip(0, 1)[..., 0:1]*255
|
|
if fonts.font_hollow and font_hint_fg.mean() > 0:
|
|
img = cv2.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), "bg_noise.png"))
|
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
img = cv2.resize(img, (font_hint_fg.shape[1], font_hint_fg.shape[0]))
|
|
img[img < 230] = 0
|
|
font_hint_bg = cv2.adaptiveThreshold(img.astype(np.uint8), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
|
|
kernel1 = np.ones((2, 2), dtype=np.uint8)
|
|
kernel2 = np.ones((3, 3), dtype=np.uint8)
|
|
dilate_img1 = cv2.dilate(font_hint_fg[..., 0].astype(np.uint8), kernel1, iterations=1)
|
|
dilate_img2 = cv2.dilate(font_hint_fg[..., 0].astype(np.uint8), kernel2, iterations=1)
|
|
dilate_text = dilate_img2 - dilate_img1
|
|
result = (font_hint_fg[..., 0]-font_hint_bg + dilate_text).clip(0, 255)
|
|
font_hint_bg[font_hint_fg[..., 0] > 0] = 0
|
|
result = (result + font_hint_bg).clip(0, 255)
|
|
font_hint_bg = result[..., None]
|
|
else:
|
|
font_hint_bg = font_hint_fg
|
|
|
|
text_prompt = "#"
|
|
for i in texts:
|
|
text_prompt+=" *"
|
|
|
|
info['font_hint'] = arr2tensor((font_hint_bg/255), img_count, model.load_device, model.dtype)
|
|
cond = model.model.get_learned_conditioning(dict(c_concat=[hint], c_crossattn=[[[prompt + ', ' + a_prompt] * img_count, [text_prompt.replace("# ", "")] * img_count]], text_info=info))
|
|
un_cond = model.model.get_learned_conditioning(dict(c_concat=[hint], c_crossattn=[[[n_prompt] * img_count, [""] * img_count]], text_info=info))
|
|
|
|
|
|
# font_img = []
|
|
# for f_i in gly_pos_imgs:
|
|
# f_i = torch.from_numpy(f_i).permute(2,0,1)
|
|
# font_img.append(f_i)
|
|
# font_img = torch.cat(font_img, dim=0)
|
|
|
|
# gly_line_img = []
|
|
# glyphs_img = []
|
|
# for g_la, g_lb in zip(gly_line_list, glyphs_list):
|
|
# g_la, g_lb = torch.from_numpy(g_la).permute(2,0,1), torch.from_numpy(g_lb).permute(2,0,1)
|
|
# gly_line_img.append(g_la)
|
|
# glyphs_img.append(g_lb)
|
|
# gly_line_img = torch.cat(gly_line_img, dim=0)
|
|
# glyphs_img = torch.cat(glyphs_img, dim=0)
|
|
|
|
# glyph_img = torch.from_numpy(np.sum(np.stack(gly_pos_imgs), axis=0).clip(0, 255).astype(np.uint8)).permute(2,0,1)
|
|
|
|
# font_hint_img = torch.from_numpy(np.repeat(font_hint_bg.astype(np.uint8), 3, axis=2)).permute(2,0,1)
|
|
|
|
glyph_img = font_hint_img = []
|
|
|
|
# return cond, un_cond, masked_x, font_img, gly_line_img, glyphs_img, masked_img.clone().cpu().float(), h, w, font_hint_img, glyph_img
|
|
return cond, un_cond, h, w, font_hint_img, glyph_img |