import gradio as gr import numpy as np import cv2 from skimage.metrics import structural_similarity as ssim from PIL import Image def preprocess_image(image, grid_size): """Resize image to ensure consistent processing.""" if isinstance(image, str): # If image is a file path image = cv2.imread(image) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) height, width = image.shape[:2] max_dim = 512 scale = max_dim / max(height, width) new_width = int(width * scale) new_height = int(height * scale) return cv2.resize(image, (new_width, new_height)) def quantize_colors(image, n_colors=8): """Quantize colors using optimized K-means clustering.""" pixels = image.reshape(-1, 3).astype(np.float32) # Flatten image for K-means # Define K-means criteria and apply clustering criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2) _, labels, centers = cv2.kmeans(pixels, n_colors, None, criteria, 10, cv2.KMEANS_PP_CENTERS) # Convert back to uint8 and reshape centers = np.uint8(centers) quantized = centers[labels.flatten()] return quantized.reshape(image.shape) def create_grid(image, grid_size): """Divide image into grid cells.""" height, width = image.shape[:2] cell_height = height // grid_size cell_width = width // grid_size grid = [] for i in range(grid_size): row = [] for j in range(grid_size): y_start = i * cell_height x_start = j * cell_width y_end = height if i == grid_size - 1 else (i + 1) * cell_height x_end = width if j == grid_size - 1 else (j + 1) * cell_width cell = image[y_start:y_end, x_start:x_end] row.append(cell) grid.append(row) return grid def get_average_color(cell): """Calculate average color of a grid cell.""" return np.mean(cell, axis=(0, 1)).astype(np.uint8) def create_mosaic(image, grid_size, use_color_quantization, n_colors=8): """Create mosaic from input image using optional color quantization.""" processed_image = preprocess_image(image, grid_size) if use_color_quantization: processed_image = quantize_colors(processed_image, n_colors) grid = create_grid(processed_image, grid_size) height, width = processed_image.shape[:2] cell_height = height // grid_size cell_width = width // grid_size mosaic = np.zeros_like(processed_image) for i in range(grid_size): for j in range(grid_size): y_start = i * cell_height x_start = j * cell_width y_end = height if i == grid_size - 1 else (i + 1) * cell_height x_end = width if j == grid_size - 1 else (j + 1) * cell_width avg_color = get_average_color(grid[i][j]) mosaic[y_start:y_end, x_start:x_end] = avg_color return processed_image, mosaic def calculate_similarity(original, mosaic): """Calculate similarity between original and mosaic images.""" original_gray = cv2.cvtColor(original, cv2.COLOR_RGB2GRAY) mosaic_gray = cv2.cvtColor(mosaic, cv2.COLOR_RGB2GRAY) similarity = ssim(original_gray, mosaic_gray) mse = np.mean((original - mosaic) ** 2) return similarity, mse def process_image(input_image, grid_size, use_quantization, n_colors): """Process image with K-means color quantization instead of a fixed palette.""" original, mosaic = create_mosaic(input_image, grid_size, use_quantization, n_colors) similarity, mse = calculate_similarity(original, mosaic) return (mosaic, f"Structural Similarity: {similarity:.4f}", f"Mean Squared Error: {mse:.4f}") iface = gr.Interface( fn=process_image, inputs=[ gr.Image(type="numpy", label="Upload Image"), gr.Slider(minimum=8, maximum=64, step=8, value=16, label="Grid Size"), gr.Checkbox(label="Use Color Quantization"), gr.Slider(minimum=2, maximum=16, step=1, value=8, label="Number of Colors (K-Means)") ], outputs=[ gr.Image(type="numpy", label="Mosaic Result"), gr.Textbox(label="Structural Similarity (SSIM)"), gr.Textbox(label="Mean Squared Error (MSE)") ], title="Interactive Image Mosaic Generator (Optimized K-Means)", description="Upload an image to create a mosaic-style reconstruction. Adjust the grid size and color quantization settings.", examples=[["https://res.cloudinary.com/daigovpbf/image/upload/c_crop,g_auto,h_800,w_800/samples/cup-on-a-table", 8, False, 0], ["https://res.cloudinary.com/daigovpbf/image/upload/c_crop,g_auto,h_800,w_800/samples/dessert-on-a-plate", 16, True, 8], ["https://res.cloudinary.com/daigovpbf/image/upload/c_crop,g_auto,h_800,w_800/samples/breakfast", 32, True, 4], ["https://res.cloudinary.com/daigovpbf/image/upload/c_crop,g_auto,h_800,w_800/samples/balloons", 64, True, 8]], theme="default" ) if __name__ == "__main__": iface.launch(share=True)