| | import os
|
| | from PIL import Image, ImageOps
|
| | import math
|
| | import tqdm
|
| |
|
| | from modules import paths, shared, images, deepbooru
|
| | from modules.textual_inversion import autocrop
|
| |
|
| |
|
| | def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None):
|
| | try:
|
| | if process_caption:
|
| | shared.interrogator.load()
|
| |
|
| | if process_caption_deepbooru:
|
| | deepbooru.model.start()
|
| |
|
| | preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru, split_threshold, overlap_ratio, process_focal_crop, process_focal_crop_face_weight, process_focal_crop_entropy_weight, process_focal_crop_edges_weight, process_focal_crop_debug, process_multicrop, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold)
|
| |
|
| | finally:
|
| |
|
| | if process_caption:
|
| | shared.interrogator.send_blip_to_ram()
|
| |
|
| | if process_caption_deepbooru:
|
| | deepbooru.model.stop()
|
| |
|
| |
|
| | def listfiles(dirname):
|
| | return os.listdir(dirname)
|
| |
|
| |
|
| | class PreprocessParams:
|
| | src = None
|
| | dstdir = None
|
| | subindex = 0
|
| | flip = False
|
| | process_caption = False
|
| | process_caption_deepbooru = False
|
| | preprocess_txt_action = None
|
| |
|
| |
|
| | def save_pic_with_caption(image, index, params: PreprocessParams, existing_caption=None):
|
| | caption = ""
|
| |
|
| | if params.process_caption:
|
| | caption += shared.interrogator.generate_caption(image)
|
| |
|
| | if params.process_caption_deepbooru:
|
| | if caption:
|
| | caption += ", "
|
| | caption += deepbooru.model.tag_multi(image)
|
| |
|
| | filename_part = params.src
|
| | filename_part = os.path.splitext(filename_part)[0]
|
| | filename_part = os.path.basename(filename_part)
|
| |
|
| | basename = f"{index:05}-{params.subindex}-{filename_part}"
|
| | image.save(os.path.join(params.dstdir, f"{basename}.png"))
|
| |
|
| | if params.preprocess_txt_action == 'prepend' and existing_caption:
|
| | caption = f"{existing_caption} {caption}"
|
| | elif params.preprocess_txt_action == 'append' and existing_caption:
|
| | caption = f"{caption} {existing_caption}"
|
| | elif params.preprocess_txt_action == 'copy' and existing_caption:
|
| | caption = existing_caption
|
| |
|
| | caption = caption.strip()
|
| |
|
| | if caption:
|
| | with open(os.path.join(params.dstdir, f"{basename}.txt"), "w", encoding="utf8") as file:
|
| | file.write(caption)
|
| |
|
| | params.subindex += 1
|
| |
|
| |
|
| | def save_pic(image, index, params, existing_caption=None):
|
| | save_pic_with_caption(image, index, params, existing_caption=existing_caption)
|
| |
|
| | if params.flip:
|
| | save_pic_with_caption(ImageOps.mirror(image), index, params, existing_caption=existing_caption)
|
| |
|
| |
|
| | def split_pic(image, inverse_xy, width, height, overlap_ratio):
|
| | if inverse_xy:
|
| | from_w, from_h = image.height, image.width
|
| | to_w, to_h = height, width
|
| | else:
|
| | from_w, from_h = image.width, image.height
|
| | to_w, to_h = width, height
|
| | h = from_h * to_w // from_w
|
| | if inverse_xy:
|
| | image = image.resize((h, to_w))
|
| | else:
|
| | image = image.resize((to_w, h))
|
| |
|
| | split_count = math.ceil((h - to_h * overlap_ratio) / (to_h * (1.0 - overlap_ratio)))
|
| | y_step = (h - to_h) / (split_count - 1)
|
| | for i in range(split_count):
|
| | y = int(y_step * i)
|
| | if inverse_xy:
|
| | splitted = image.crop((y, 0, y + to_h, to_w))
|
| | else:
|
| | splitted = image.crop((0, y, to_w, y + to_h))
|
| | yield splitted
|
| |
|
| |
|
| | def center_crop(image: Image, w: int, h: int):
|
| | iw, ih = image.size
|
| | if ih / h < iw / w:
|
| | sw = w * ih / h
|
| | box = (iw - sw) / 2, 0, iw - (iw - sw) / 2, ih
|
| | else:
|
| | sh = h * iw / w
|
| | box = 0, (ih - sh) / 2, iw, ih - (ih - sh) / 2
|
| | return image.resize((w, h), Image.Resampling.LANCZOS, box)
|
| |
|
| |
|
| | def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold):
|
| | iw, ih = image.size
|
| | err = lambda w, h: 1-(lambda x: x if x < 1 else 1/x)(iw/ih/(w/h))
|
| | wh = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64)
|
| | if minarea <= w * h <= maxarea and err(w, h) <= threshold),
|
| | key= lambda wh: (wh[0]*wh[1], -err(*wh))[::1 if objective=='Maximize area' else -1],
|
| | default=None
|
| | )
|
| | return wh and center_crop(image, *wh)
|
| |
|
| |
|
| | def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_flip, process_split, process_caption, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None):
|
| | width = process_width
|
| | height = process_height
|
| | src = os.path.abspath(process_src)
|
| | dst = os.path.abspath(process_dst)
|
| | split_threshold = max(0.0, min(1.0, split_threshold))
|
| | overlap_ratio = max(0.0, min(0.9, overlap_ratio))
|
| |
|
| | assert src != dst, 'same directory specified as source and destination'
|
| |
|
| | os.makedirs(dst, exist_ok=True)
|
| |
|
| | files = listfiles(src)
|
| |
|
| | shared.state.job = "preprocess"
|
| | shared.state.textinfo = "Preprocessing..."
|
| | shared.state.job_count = len(files)
|
| |
|
| | params = PreprocessParams()
|
| | params.dstdir = dst
|
| | params.flip = process_flip
|
| | params.process_caption = process_caption
|
| | params.process_caption_deepbooru = process_caption_deepbooru
|
| | params.preprocess_txt_action = preprocess_txt_action
|
| |
|
| | pbar = tqdm.tqdm(files)
|
| | for index, imagefile in enumerate(pbar):
|
| | params.subindex = 0
|
| | filename = os.path.join(src, imagefile)
|
| | try:
|
| | img = Image.open(filename)
|
| | img = ImageOps.exif_transpose(img)
|
| | img = img.convert("RGB")
|
| | except Exception:
|
| | continue
|
| |
|
| | description = f"Preprocessing [Image {index}/{len(files)}]"
|
| | pbar.set_description(description)
|
| | shared.state.textinfo = description
|
| |
|
| | params.src = filename
|
| |
|
| | existing_caption = None
|
| | existing_caption_filename = f"{os.path.splitext(filename)[0]}.txt"
|
| | if os.path.exists(existing_caption_filename):
|
| | with open(existing_caption_filename, 'r', encoding="utf8") as file:
|
| | existing_caption = file.read()
|
| |
|
| | if shared.state.interrupted:
|
| | break
|
| |
|
| | if img.height > img.width:
|
| | ratio = (img.width * height) / (img.height * width)
|
| | inverse_xy = False
|
| | else:
|
| | ratio = (img.height * width) / (img.width * height)
|
| | inverse_xy = True
|
| |
|
| | process_default_resize = True
|
| |
|
| | if process_split and ratio < 1.0 and ratio <= split_threshold:
|
| | for splitted in split_pic(img, inverse_xy, width, height, overlap_ratio):
|
| | save_pic(splitted, index, params, existing_caption=existing_caption)
|
| | process_default_resize = False
|
| |
|
| | if process_focal_crop and img.height != img.width:
|
| |
|
| | dnn_model_path = None
|
| | try:
|
| | dnn_model_path = autocrop.download_and_cache_models(os.path.join(paths.models_path, "opencv"))
|
| | except Exception as e:
|
| | print("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", e)
|
| |
|
| | autocrop_settings = autocrop.Settings(
|
| | crop_width = width,
|
| | crop_height = height,
|
| | face_points_weight = process_focal_crop_face_weight,
|
| | entropy_points_weight = process_focal_crop_entropy_weight,
|
| | corner_points_weight = process_focal_crop_edges_weight,
|
| | annotate_image = process_focal_crop_debug,
|
| | dnn_model_path = dnn_model_path,
|
| | )
|
| | for focal in autocrop.crop_image(img, autocrop_settings):
|
| | save_pic(focal, index, params, existing_caption=existing_caption)
|
| | process_default_resize = False
|
| |
|
| | if process_multicrop:
|
| | cropped = multicrop_pic(img, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold)
|
| | if cropped is not None:
|
| | save_pic(cropped, index, params, existing_caption=existing_caption)
|
| | else:
|
| | print(f"skipped {img.width}x{img.height} image {filename} (can't find suitable size within error threshold)")
|
| | process_default_resize = False
|
| |
|
| | if process_keep_original_size:
|
| | save_pic(img, index, params, existing_caption=existing_caption)
|
| | process_default_resize = False
|
| |
|
| | if process_default_resize:
|
| | img = images.resize_image(1, img, width, height)
|
| | save_pic(img, index, params, existing_caption=existing_caption)
|
| |
|
| | shared.state.nextjob()
|
| |
|