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