Spaces:
Sleeping
Sleeping
File size: 9,467 Bytes
798d901 0404f22 61ae52e 798d901 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 798d901 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 798d901 0404f22 798d901 0404f22 61ae52e 0404f22 61ae52e 798d901 61ae52e 0404f22 798d901 61ae52e 798d901 61ae52e 798d901 61ae52e 798d901 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 798d901 61ae52e 0404f22 798d901 61ae52e 798d901 0404f22 61ae52e 798d901 61ae52e 798d901 61ae52e 798d901 61ae52e 798d901 61ae52e 798d901 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 798d901 61ae52e 0404f22 61ae52e 798d901 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 0404f22 61ae52e 81a80cc 61ae52e 798d901 61ae52e 798d901 61ae52e 798d901 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
import gradio as gr
import cv2
import numpy as np
import os
import pickle
import math
from skimage.metrics import structural_similarity as ssim
# ----------------- Constants -----------------
DATASET_FOLDER = "Dataset" # (Not used directly now)
KD_TREE_PATH = "kdtree_dataset.pkl" # Path to the precomputed KDTree file
KD_TILE_SIZE = (50, 50) # Must match the tile size used when building the KDTree
# ----------------- Feature Extraction Functions -----------------
def compute_features(image):
"""
Compute a set of features for an image:
- Average Lab color (using a Gaussian-blurred version)
- Edge density using Canny edge detection (normalized)
- Texture measure using the standard deviation of the grayscale image (normalized)
- Average gradient magnitude computed via Sobel operators (normalized)
Returns: (avg_lab, avg_edge, avg_texture, avg_grad)
"""
blurred = cv2.GaussianBlur(image, (5, 5), 0)
img_lab = cv2.cvtColor(blurred, cv2.COLOR_RGB2LAB)
avg_lab = np.mean(img_lab, axis=(0, 1))
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 100, 200)
avg_edge = np.mean(edges) / 255.0
avg_texture = np.std(gray) / 255.0
grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
grad_mag = np.sqrt(grad_x**2 + grad_y**2)
avg_grad = np.mean(grad_mag) / 255.0
return avg_lab, avg_edge, avg_texture, avg_grad
def compute_weighted_features(image):
"""
Compute the weighted feature vector for KDTree search.
The image should be resized to KD_TILE_SIZE before feature extraction.
Weights: for Lab channels use 1.0; for edge, texture, and gradient use 0.5
(implemented as multiplying by sqrt(0.5)).
"""
scale = np.array([1.0, 1.0, 1.0, math.sqrt(0.5), math.sqrt(0.5), math.sqrt(0.5)])
avg_lab, avg_edge, avg_texture, avg_grad = compute_features(image)
raw_feature = np.concatenate([avg_lab, [avg_edge, avg_texture, avg_grad]])
weighted_feature = raw_feature * scale
return weighted_feature
# ----------------- Utility Function -----------------
def ensure_min_size(image, min_size=7):
"""
Ensure that the image has at least a minimum size; if not, resize it.
"""
h, w = image.shape[:2]
if h < min_size or w < min_size:
new_w = max(min_size, w)
new_h = max(min_size, h)
image = cv2.resize(image, (new_w, new_h))
return image
# ----------------- Mosaic Generation Functions -----------------
def create_photo_mosaic(input_image, kdtree_path, num_tiles_y, progress=None):
"""
Create an image mosaic using a precomputed KDTree.
For each mosaic tile in the input image, the tile is resized to KD_TILE_SIZE,
its weighted features are computed, and the KDTree is queried to find the best match.
The matched dataset image is then resized to the tile’s actual size before placement.
"""
# Load the precomputed KDTree and dataset images
with open(kdtree_path, "rb") as f:
tree_data = pickle.load(f)
tree = tree_data['tree']
dataset_images = tree_data['images']
original_image = input_image.copy()
height, width, _ = original_image.shape
# Determine mosaic grid dimensions
tile_height = height // num_tiles_y
aspect_ratio = width / height
num_tiles_x = int(num_tiles_y * aspect_ratio)
tile_width = width // num_tiles_x
print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
mosaic = np.zeros_like(original_image)
rows = list(range(0, height, tile_height))
cols = list(range(0, width, tile_width))
total_tiles = len(rows) * len(cols)
tile_count = 0
for y in rows:
for x in cols:
y_end = min(y + tile_height, height)
x_end = min(x + tile_width, width)
tile = original_image[y:y_end, x:x_end]
# Resize the tile to the KDTree tile size for feature extraction
tile_resized = cv2.resize(tile, KD_TILE_SIZE)
query_feature = compute_weighted_features(tile_resized)
# Query the KDTree for the best match (returns index)
dist, ind = tree.query([query_feature], k=1)
best_index = ind[0][0]
best_match = dataset_images[best_index]
# Resize the best match image to the current tile size and place it into the mosaic
best_match_resized = cv2.resize(best_match, (x_end - x, y_end - y))
mosaic[y:y_end, x:x_end] = best_match_resized
tile_count += 1
if progress is not None:
progress(tile_count / total_tiles)
# Save the final mosaic (convert from RGB to BGR for saving with cv2)
output_path = "mosaic_output.jpg"
cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_RGB2BGR))
return output_path
def create_color_mosaic(input_image, num_tiles_y, progress=None):
"""
Create a simple color mosaic by dividing the image into grid cells and filling
each cell with its average RGB color.
"""
original_image = input_image.copy()
height, width, _ = original_image.shape
tile_height = height // num_tiles_y
aspect_ratio = width / height
num_tiles_x = int(num_tiles_y * aspect_ratio)
tile_width = width // num_tiles_x
print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
mosaic = np.zeros_like(original_image)
rows = list(range(0, height, tile_height))
cols = list(range(0, width, tile_width))
total_tiles = len(rows) * len(cols)
tile_count = 0
for y in rows:
for x in cols:
y_end = min(y + tile_height, height)
x_end = min(x + tile_width, width)
tile = original_image[y:y_end, x:x_end]
avg_color = np.mean(tile, axis=(0, 1)).astype(np.uint8)
mosaic[y:y_end, x:x_end] = avg_color
tile_count += 1
if progress is not None:
progress(tile_count / total_tiles)
output_path = "color_mosaic_output.jpg"
cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_RGB2BGR))
return output_path
# ----------------- Performance Metrics -----------------
def compute_mse(original, mosaic):
"""
Compute Mean Squared Error (MSE) between two images.
"""
original = original.astype("float")
mosaic = mosaic.astype("float")
err = np.sum((original - mosaic) ** 2)
mse = err / float(original.shape[0] * original.shape[1] * original.shape[2])
return mse
def compute_ssim_metric(original, mosaic):
"""
Compute Structural Similarity Index (SSIM) between two images.
"""
min_dim = min(original.shape[0], original.shape[1])
win_size = 7 if min_dim >= 7 else (min_dim if min_dim % 2 == 1 else min_dim - 1)
ssim_value, _ = ssim(original, mosaic, win_size=win_size, channel_axis=-1, full=True)
return ssim_value
# ----------------- Gradio Interface -----------------
def mosaic_gradio(input_image, num_tiles_y, mosaic_type, progress=gr.Progress()):
"""
Gradio interface function to generate and return the mosaic image along with performance metrics.
mosaic_type: "Color Mosaic" or "Image Mosaic"
Returns: (mosaic_image_file, performance_metrics_string)
"""
if mosaic_type == "Color Mosaic":
mosaic_path = create_color_mosaic(input_image, num_tiles_y, progress)
else:
mosaic_path = create_photo_mosaic(input_image, KD_TREE_PATH, num_tiles_y, progress)
mosaic_image = cv2.imread(mosaic_path)
if mosaic_image is None:
return None, "Error: Mosaic image could not be loaded."
mosaic_image = cv2.cvtColor(mosaic_image, cv2.COLOR_BGR2RGB)
input_for_metrics = ensure_min_size(input_image.copy())
mosaic_for_metrics = ensure_min_size(mosaic_image.copy())
mse_value = compute_mse(input_for_metrics, mosaic_for_metrics)
ssim_value = compute_ssim_metric(input_for_metrics, mosaic_for_metrics)
metrics_text = f"MSE: {mse_value:.2f}\nSSIM: {ssim_value:.4f}"
return mosaic_path, metrics_text
# ----------------- Gradio App Setup -----------------
examples = [
["input_images/1.jpg", 90, "Image Mosaic"],
["input_images/2.jpg", 90, "Image Mosaic"],
["input_images/3.jpg", 90, "Image Mosaic"],
["input_images/6.jpg", 90, "Image Mosaic"],
["input_images/7.jpg", 90, "Image Mosaic"],
["input_images/8.jpg", 90, "Image Mosaic"],
["input_images/9.jpg", 90, "Image Mosaic"],
["input_images/10.jpg", 90, "Image Mosaic"]
]
iface = gr.Interface(
fn=mosaic_gradio,
inputs=[
gr.Image(type="numpy", label="Upload Image"),
gr.Slider(10, 200, value=90, step=5, label="Number of Tiles (Height)"),
gr.Radio(choices=["Color Mosaic", "Image Mosaic"], label="Mosaic Type", value="Image Mosaic")
],
outputs=[
gr.Image(type="filepath", label="Generated Mosaic"),
gr.Textbox(label="Performance Metrics")
],
title="Photo Mosaic Generator",
description=("Upload an image, choose the number of tiles (height) and mosaic type. "
"Select 'Color Mosaic' for a mosaic using average colors, or 'Image Mosaic' to use dataset images "
"matched by color, edge density, texture, and gradient features. "
"After mosaic generation, performance metrics (MSE and SSIM) will be displayed."),
examples=examples
)
iface.launch() |