import cv2 import base64 import numpy as np def laplacian_blending(A, B, m, num_levels=7): assert A.shape == B.shape assert B.shape == m.shape height = m.shape[0] width = m.shape[1] size_list = np.array([4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]) size = size_list[np.where(size_list > max(height, width))][0] GA = np.zeros((size, size, 3), dtype=np.float32) GA[:height, :width, :] = A GB = np.zeros((size, size, 3), dtype=np.float32) GB[:height, :width, :] = B GM = np.zeros((size, size, 3), dtype=np.float32) GM[:height, :width, :] = m gpA = [GA] gpB = [GB] gpM = [GM] for i in range(num_levels): GA = cv2.pyrDown(GA) GB = cv2.pyrDown(GB) GM = cv2.pyrDown(GM) gpA.append(np.float32(GA)) gpB.append(np.float32(GB)) gpM.append(np.float32(GM)) lpA = [gpA[num_levels-1]] lpB = [gpB[num_levels-1]] gpMr = [gpM[num_levels-1]] for i in range(num_levels-1,0,-1): LA = np.subtract(gpA[i-1], cv2.pyrUp(gpA[i])) LB = np.subtract(gpB[i-1], cv2.pyrUp(gpB[i])) lpA.append(LA) lpB.append(LB) gpMr.append(gpM[i-1]) LS = [] for la,lb,gm in zip(lpA,lpB,gpMr): ls = la * gm + lb * (1.0 - gm) LS.append(ls) ls_ = LS[0] for i in range(1,num_levels): ls_ = cv2.pyrUp(ls_) ls_ = cv2.add(ls_, LS[i]) ls_ = ls_[:height, :width, :] #ls_ = (ls_ - np.min(ls_)) * (255.0 / (np.max(ls_) - np.min(ls_))) return ls_.clip(0, 255) def mask_crop(mask, crop): top, bottom, left, right = crop shape = mask.shape top = int(top) bottom = int(bottom) if top + bottom < shape[1]: if top > 0: mask[:top, :] = 0 if bottom > 0: mask[-bottom:, :] = 0 left = int(left) right = int(right) if left + right < shape[0]: if left > 0: mask[:, :left] = 0 if right > 0: mask[:, -right:] = 0 return mask def create_image_grid(images, size=128): num_images = len(images) num_cols = int(np.ceil(np.sqrt(num_images))) num_rows = int(np.ceil(num_images / num_cols)) grid = np.zeros((num_rows * size, num_cols * size, 3), dtype=np.uint8) for i, image in enumerate(images): row_idx = (i // num_cols) * size col_idx = (i % num_cols) * size image = cv2.resize(image.copy(), (size,size)) if image.dtype != np.uint8: image = (image.astype('float32') * 255).astype('uint8') if image.ndim == 2: image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) grid[row_idx:row_idx + size, col_idx:col_idx + size] = image return grid def paste_to_whole(foreground, background, matrix, mask=None, crop_mask=(0,0,0,0), blur_amount=0.1, erode_amount = 0.15, blend_method='linear'): inv_matrix = cv2.invertAffineTransform(matrix) fg_shape = foreground.shape[:2] bg_shape = (background.shape[1], background.shape[0]) foreground = cv2.warpAffine(foreground, inv_matrix, bg_shape, borderValue=0.0, borderMode=cv2.BORDER_REPLICATE) if mask is None: mask = np.full(fg_shape, 1., dtype=np.float32) mask = mask_crop(mask, crop_mask) mask = cv2.warpAffine(mask, inv_matrix, bg_shape, borderValue=0.0) else: assert fg_shape == mask.shape[:2], "foreground & mask shape mismatch!" mask = mask_crop(mask, crop_mask).astype('float32') mask = cv2.warpAffine(mask, inv_matrix, (background.shape[1], background.shape[0]), borderValue=0.0) _mask = mask.copy() _mask[_mask > 0.05] = 1. non_zero_points = cv2.findNonZero(_mask) _, _, w, h = cv2.boundingRect(non_zero_points) mask_size = int(np.sqrt(w * h)) if erode_amount > 0: kernel_size = max(int(mask_size * erode_amount), 1) structuring_element = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size)) mask = cv2.erode(mask, structuring_element) if blur_amount > 0: kernel_size = max(int(mask_size * blur_amount), 3) if kernel_size % 2 == 0: kernel_size += 1 mask = cv2.GaussianBlur(mask, (kernel_size, kernel_size), 0) mask = np.tile(np.expand_dims(mask, axis=-1), (1, 1, 3)) if blend_method == 'laplacian': composite_image = laplacian_blending(foreground, background, mask.clip(0,1), num_levels=4) else: composite_image = mask * foreground + (1 - mask) * background return composite_image.astype("uint8").clip(0, 255) def image_mask_overlay(img, mask): img = img.astype('float32') / 255. img *= (mask + 0.25).clip(0, 1) img = np.clip(img * 255., 0., 255.).astype('uint8') return img def resize_with_padding(img, expected_size=(640, 360), color=(0, 0, 0), max_flip=False): original_height, original_width = img.shape[:2] if max_flip and original_height > original_width: expected_size = (expected_size[1], expected_size[0]) aspect_ratio = original_width / original_height new_width = expected_size[0] new_height = int(new_width / aspect_ratio) if new_height > expected_size[1]: new_height = expected_size[1] new_width = int(new_height * aspect_ratio) resized_img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA) canvas = cv2.copyMakeBorder(resized_img, top=(expected_size[1] - new_height) // 2, bottom=(expected_size[1] - new_height + 1) // 2, left=(expected_size[0] - new_width) // 2, right=(expected_size[0] - new_width + 1) // 2, borderType=cv2.BORDER_CONSTANT, value=color) return canvas def create_image_grid(images, size=128): num_images = len(images) num_cols = int(np.ceil(np.sqrt(num_images))) num_rows = int(np.ceil(num_images / num_cols)) grid = np.zeros((num_rows * size, num_cols * size, 3), dtype=np.uint8) for i, image in enumerate(images): row_idx = (i // num_cols) * size col_idx = (i % num_cols) * size image = cv2.resize(image.copy(), (size,size)) if image.dtype != np.uint8: image = (image.astype('float32') * 255).astype('uint8') if image.ndim == 2: image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) grid[row_idx:row_idx + size, col_idx:col_idx + size] = image return grid def image_to_html(img, size=(640, 360), extension="jpg"): if img is not None: img = resize_with_padding(img, expected_size=size) buffer = cv2.imencode(f".{extension}", img)[1] base64_data = base64.b64encode(buffer.tobytes()) imgbs64 = f"data:image/{extension};base64," + base64_data.decode("utf-8") html = '
' html += f'No Preview' html += '
' return html return None def mix_two_image(a, b, opacity=1.): a_dtype = a.dtype b_dtype = b.dtype a = a.astype('float32') b = b.astype('float32') a = cv2.resize(a, (b.shape[0], b.shape[1])) opacity = min(max(opacity, 0.), 1.) mixed_img = opacity * b + (1 - opacity) * a return mixed_img.astype(a_dtype) resolution_map = { "Original": None, "240p": (426, 240), "360p": (640, 360), "480p": (854, 480), "720p": (1280, 720), "1080p": (1920, 1080), "1440p": (2560, 1440), "2160p": (3840, 2160), } def resize_image_by_resolution(img, quality): resolution = resolution_map.get(quality, None) if resolution is None: return img h, w = img.shape[:2] if h > w: ratio = resolution[0] / h else: ratio = resolution[0] / w new_h, new_w = int(h * ratio), int(w * ratio) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) return img def fast_pil_encode(pil_image): image_arr = np.asarray(pil_image)[:,:,::-1] buffer = cv2.imencode('.jpg', image_arr)[1] base64_data = base64.b64encode(buffer.tobytes()) return "data:image/jpg;base64," + base64_data.decode("utf-8") def fast_numpy_encode(img_array): buffer = cv2.imencode('.jpg', img_array)[1] base64_data = base64.b64encode(buffer.tobytes()) return "data:image/jpg;base64," + base64_data.decode("utf-8") crf_quality_by_resolution = { 240: {"poor": 45, "low": 35, "medium": 28, "high": 23, "best": 20}, 360: {"poor": 35, "low": 28, "medium": 23, "high": 20, "best": 18}, 480: {"poor": 28, "low": 23, "medium": 20, "high": 18, "best": 16}, 720: {"poor": 23, "low": 20, "medium": 18, "high": 16, "best": 14}, 1080: {"poor": 20, "low": 18, "medium": 16, "high": 14, "best": 12}, 1440: {"poor": 18, "low": 16, "medium": 14, "high": 12, "best": 10}, 2160: {"poor": 16, "low": 14, "medium": 12, "high": 10, "best": 8} } def get_crf_for_resolution(resolution, quality): available_resolutions = list(crf_quality_by_resolution.keys()) closest_resolution = min(available_resolutions, key=lambda x: abs(x - resolution)) return crf_quality_by_resolution[closest_resolution][quality]