mansi.modi@streebo.com commited on
Commit
61ae52e
·
1 Parent(s): 4ad513f

Added metrics, sample images

Browse files
Files changed (1) hide show
  1. app.py +204 -90
app.py CHANGED
@@ -1,170 +1,279 @@
1
- # Two options
2
-
3
- # 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 access token
4
-
5
  import gradio as gr
6
  import cv2
7
  import numpy as np
8
  import os
9
- import matplotlib.pyplot as plt
10
 
11
  # Folder containing your dataset of tiles (small images)
12
- DATASET_FOLDER = "Dataset"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def load_dataset_images(folder_path, tile_size):
15
- """Loads images from a folder, resizes them, and calculates their average color."""
 
 
 
16
  dataset = []
17
- image_paths = [os.path.join(folder_path, img) for img in os.listdir(folder_path)
18
  if img.lower().endswith(('.png', '.jpg', '.jpeg'))]
19
-
20
  for img_path in image_paths:
21
  img = cv2.imread(img_path)
22
  if img is None:
23
  continue # Skip unreadable images
24
- img = cv2.resize(img, tile_size) # Resize to match tile size
25
- avg_color = np.mean(img, axis=(0, 1)) # Compute average color
26
- dataset.append((img, avg_color, img_path)) # Store image, its average color, and path
27
-
 
 
 
 
 
28
  return dataset
29
 
30
- def find_best_match(tile_avg_color, dataset):
31
- """Finds the best matching image from the dataset based on color similarity (Euclidean distance)."""
 
 
 
 
 
 
 
 
 
32
  min_dist = float('inf')
33
  best_match = None
34
-
35
- for img, avg_color, path in dataset:
36
- dist = np.linalg.norm(tile_avg_color - avg_color) # Euclidean distance
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  if dist < min_dist:
38
  min_dist = dist
39
- best_match = img
40
-
41
  return best_match
42
 
43
  def create_photo_mosaic(input_image, dataset_folder, num_tiles_y, progress=None):
44
- """Creates an image mosaic using dataset images while maintaining aspect ratio.
45
- Updates the progress callback after processing each tile.
46
  """
47
- # Convert the uploaded image from RGB to BGR for OpenCV processing
48
- original_image = input_image
49
-
50
- # Get image dimensions
 
 
51
  height, width, _ = original_image.shape
52
-
53
- # Compute tile height dynamically
54
  tile_height = height // num_tiles_y
55
-
56
- # Maintain aspect ratio to compute tile width
57
  aspect_ratio = width / height
58
- num_tiles_x = int(num_tiles_y * aspect_ratio) # Adjust width based on aspect ratio
59
  tile_width = width // num_tiles_x
60
-
61
  print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
62
-
63
- # Load dataset images (using linear search)
64
  dataset = load_dataset_images(dataset_folder, (tile_width, tile_height))
65
  if not dataset:
66
  print("No images found in dataset folder!")
67
  return None
68
-
69
- # Create an empty mosaic image (in BGR)
70
  mosaic = np.zeros_like(original_image)
71
-
72
- # Total number of tiles (for progress updates)
73
  rows = list(range(0, height, tile_height))
74
  cols = list(range(0, width, tile_width))
75
  total_tiles = len(rows) * len(cols)
76
  tile_count = 0
77
-
78
- # Loop over the original image in tile-sized steps
79
- for y in range(0, height, tile_height):
80
- for x in range(0, width, tile_width):
81
- # Define the tile region
82
  y_end = min(y + tile_height, height)
83
  x_end = min(x + tile_width, width)
84
  tile = original_image[y:y_end, x:x_end]
85
-
86
- # Compute the average color of the tile
87
- tile_avg_color = np.mean(tile, axis=(0, 1))
88
-
89
- # Find the best matching dataset image
90
- best_match = find_best_match(tile_avg_color, dataset)
91
-
92
- # Place the best matching image in the mosaic (crop if necessary)
93
  if best_match is not None:
94
- mosaic[y:y_end, x:x_end] = best_match[:y_end-y, :x_end-x]
95
-
96
- # Update progress
97
  tile_count += 1
98
  if progress is not None:
99
  progress(tile_count / total_tiles)
100
-
101
- # Before saving, convert the mosaic from BGR back to RGB
102
  output_path = "mosaic_output.jpg"
103
- cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB))
104
-
105
- return output_path # Return the file path of the saved mosaic image
106
 
107
  def create_color_mosaic(input_image, num_tiles_y, progress=None):
108
- """Creates a simple color mosaic by dividing the image into grid cells and
109
- filling each cell with the average color of that cell.
110
  """
111
- # input_image is assumed to be in RGB format
112
- original_image = input_image.copy() # Keep in RGB
113
-
114
- # Get image dimensions
115
  height, width, _ = original_image.shape
116
-
117
- # Compute tile height dynamically
118
  tile_height = height // num_tiles_y
119
-
120
- # Maintain aspect ratio to compute tile width
121
  aspect_ratio = width / height
122
  num_tiles_x = int(num_tiles_y * aspect_ratio)
123
  tile_width = width // num_tiles_x
124
-
125
  print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
126
-
127
- # Create an empty mosaic image (in RGB)
128
  mosaic = np.zeros_like(original_image)
129
-
130
  rows = list(range(0, height, tile_height))
131
  cols = list(range(0, width, tile_width))
132
  total_tiles = len(rows) * len(cols)
133
-
134
  tile_count = 0
135
-
136
- # Loop over the image and fill each grid cell with its average color
137
- for y in range(0, height, tile_height):
138
- for x in range(0, width, tile_width):
139
  y_end = min(y + tile_height, height)
140
  x_end = min(x + tile_width, width)
141
  tile = original_image[y:y_end, x:x_end]
142
  avg_color = np.mean(tile, axis=(0, 1)).astype(np.uint8)
143
- mosaic[y:y_end, x:x_end] = avg_color # Fill cell with the average color
144
-
145
  tile_count += 1
146
  if progress is not None:
147
  progress(tile_count / total_tiles)
148
-
149
  output_path = "color_mosaic_output.jpg"
150
- # Since mosaic is in RGB, convert to BGR before saving with OpenCV
151
  cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_RGB2BGR))
152
-
153
  return output_path
154
 
155
- # Gradio Interface Function with progress callback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  def mosaic_gradio(input_image, num_tiles_y, mosaic_type, progress=gr.Progress()):
157
  """
158
- Gradio interface function to generate and return the mosaic image.
159
  mosaic_type: Either "Color Mosaic" or "Image Mosaic"
 
160
  """
 
161
  if mosaic_type == "Color Mosaic":
162
  mosaic_path = create_color_mosaic(input_image, num_tiles_y, progress)
163
  else:
164
  mosaic_path = create_photo_mosaic(input_image, DATASET_FOLDER, num_tiles_y, progress)
165
- return mosaic_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Gradio Interface
168
  iface = gr.Interface(
169
  fn=mosaic_gradio,
170
  inputs=[
@@ -172,11 +281,16 @@ iface = gr.Interface(
172
  gr.Slider(10, 200, value=90, step=5, label="Number of Tiles (Height)"),
173
  gr.Radio(choices=["Color Mosaic", "Image Mosaic"], label="Mosaic Type", value="Image Mosaic")
174
  ],
175
- outputs=gr.Image(type="filepath", label="Generated Mosaic"),
 
 
 
176
  title="Photo Mosaic Generator",
177
  description=("Upload an image, choose the number of tiles (height) and mosaic type. "
178
- "Select 'Color Mosaic' to create a mosaic using average colors, or 'Image Mosaic' to use dataset images. ")
 
 
 
179
  )
180
 
181
- # Launch Gradio App
182
  iface.launch()
 
 
 
 
 
1
  import gradio as gr
2
  import cv2
3
  import numpy as np
4
  import os
5
+ from skimage.metrics import structural_similarity as ssim
6
 
7
  # Folder containing your dataset of tiles (small images)
8
+ DATASET_FOLDER = "D://NEU/5330/Lab-1/Dataset/"
9
+
10
+ def compute_features(image):
11
+ """
12
+ Compute a set of features for an image:
13
+ - Average Lab color (using a Gaussian-blurred version)
14
+ - Edge density using Canny edge detection (normalized)
15
+ - Texture measure using the standard deviation of the grayscale image (normalized)
16
+ - Average gradient magnitude computed via Sobel operators (normalized)
17
+ Returns: (avg_lab, avg_edge, avg_texture, avg_grad)
18
+ """
19
+ # Apply Gaussian blur to reduce noise before computing Lab color
20
+ blurred = cv2.GaussianBlur(image, (5, 5), 0)
21
+ img_lab = cv2.cvtColor(blurred, cv2.COLOR_RGB2LAB)
22
+ avg_lab = np.mean(img_lab, axis=(0, 1))
23
+
24
+ # Convert to grayscale for edge and texture computations
25
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
26
+
27
+ # Edge density: apply Canny and normalize the average edge intensity
28
+ edges = cv2.Canny(gray, 100, 200)
29
+ avg_edge = np.mean(edges) / 255.0 # Normalized edge density
30
+
31
+ # Texture measure: standard deviation of grayscale values (normalized)
32
+ avg_texture = np.std(gray) / 255.0
33
+
34
+ # Gradient magnitude: using Sobel operators in x and y directions, then average
35
+ grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
36
+ grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
37
+ grad_mag = np.sqrt(grad_x**2 + grad_y**2)
38
+ avg_grad = np.mean(grad_mag) / 255.0
39
+
40
+ return avg_lab, avg_edge, avg_texture, avg_grad
41
 
42
  def load_dataset_images(folder_path, tile_size):
43
+ """
44
+ Loads images from a folder, resizes them to tile_size, and computes a set of features:
45
+ (RGB image, average Lab color, edge density, texture measure, gradient magnitude, image path)
46
+ """
47
  dataset = []
48
+ image_paths = [os.path.join(folder_path, img) for img in os.listdir(folder_path)
49
  if img.lower().endswith(('.png', '.jpg', '.jpeg'))]
50
+
51
  for img_path in image_paths:
52
  img = cv2.imread(img_path)
53
  if img is None:
54
  continue # Skip unreadable images
55
+ # Resize the image to the given tile size
56
+ img = cv2.resize(img, tile_size)
57
+ # Convert from BGR to RGB
58
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
59
+
60
+ # Compute the feature vector for this dataset image
61
+ avg_lab, avg_edge, avg_texture, avg_grad = compute_features(img)
62
+ dataset.append((img, avg_lab, avg_edge, avg_texture, avg_grad, img_path))
63
+
64
  return dataset
65
 
66
+ def find_best_match(tile_features, dataset, weights=(1.0, 0.5, 0.5, 0.5)):
67
+ """
68
+ Finds the best matching dataset image based on a weighted combination of:
69
+ - Color difference (in Lab space)
70
+ - Edge density difference
71
+ - Texture difference
72
+ - Gradient magnitude difference
73
+
74
+ The weights parameter is a tuple with weights for each feature in the same order.
75
+ """
76
+ tile_lab, tile_edge, tile_texture, tile_grad = tile_features
77
  min_dist = float('inf')
78
  best_match = None
79
+
80
+ for data in dataset:
81
+ ds_img, ds_lab, ds_edge, ds_texture, ds_grad, ds_path = data
82
+
83
+ # Compute the difference for each feature:
84
+ color_diff = np.linalg.norm(tile_lab - ds_lab)
85
+ edge_diff = abs(tile_edge - ds_edge)
86
+ texture_diff = abs(tile_texture - ds_texture)
87
+ grad_diff = abs(tile_grad - ds_grad)
88
+
89
+ # Compute a weighted distance (using a Euclidean combination)
90
+ dist = np.sqrt(weights[0] * (color_diff ** 2) +
91
+ weights[1] * (edge_diff ** 2) +
92
+ weights[2] * (texture_diff ** 2) +
93
+ weights[3] * (grad_diff ** 2))
94
+
95
  if dist < min_dist:
96
  min_dist = dist
97
+ best_match = ds_img
98
+
99
  return best_match
100
 
101
  def create_photo_mosaic(input_image, dataset_folder, num_tiles_y, progress=None):
 
 
102
  """
103
+ Creates an image mosaic using dataset images. For each tile of the input image,
104
+ it computes a feature vector (color, edge, texture, gradient) and finds the best
105
+ matching dataset image based on these features.
106
+ """
107
+ # Assume the uploaded image from Gradio is in RGB format
108
+ original_image = input_image.copy()
109
  height, width, _ = original_image.shape
110
+
111
+ # Compute tile height and determine tile width based on aspect ratio
112
  tile_height = height // num_tiles_y
 
 
113
  aspect_ratio = width / height
114
+ num_tiles_x = int(num_tiles_y * aspect_ratio)
115
  tile_width = width // num_tiles_x
 
116
  print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
117
+
118
+ # Load the dataset images with the new feature set
119
  dataset = load_dataset_images(dataset_folder, (tile_width, tile_height))
120
  if not dataset:
121
  print("No images found in dataset folder!")
122
  return None
123
+
124
+ # Create an empty mosaic image in RGB
125
  mosaic = np.zeros_like(original_image)
126
+
127
+ # Calculate the grid ranges and total tile count for progress tracking
128
  rows = list(range(0, height, tile_height))
129
  cols = list(range(0, width, tile_width))
130
  total_tiles = len(rows) * len(cols)
131
  tile_count = 0
132
+
133
+ # Process each tile of the input image
134
+ for y in rows:
135
+ for x in cols:
 
136
  y_end = min(y + tile_height, height)
137
  x_end = min(x + tile_width, width)
138
  tile = original_image[y:y_end, x:x_end]
139
+
140
+ # Compute feature vector for the tile
141
+ tile_features = compute_features(tile)
142
+
143
+ # Find the best matching dataset image using the combined feature metric
144
+ best_match = find_best_match(tile_features, dataset)
145
+
 
146
  if best_match is not None:
147
+ # Crop the dataset image if necessary to match the tile size
148
+ mosaic[y:y_end, x:x_end] = best_match[:y_end - y, :x_end - x]
149
+
150
  tile_count += 1
151
  if progress is not None:
152
  progress(tile_count / total_tiles)
153
+
154
+ # Save the final mosaic. Since mosaic is in RGB, convert to BGR for cv2.imwrite.
155
  output_path = "mosaic_output.jpg"
156
+ cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_RGB2BGR))
157
+
158
+ return output_path
159
 
160
  def create_color_mosaic(input_image, num_tiles_y, progress=None):
 
 
161
  """
162
+ Creates a simple color mosaic by dividing the image into grid cells and
163
+ filling each cell with its average RGB color.
164
+ """
165
+ original_image = input_image.copy()
166
  height, width, _ = original_image.shape
 
 
167
  tile_height = height // num_tiles_y
 
 
168
  aspect_ratio = width / height
169
  num_tiles_x = int(num_tiles_y * aspect_ratio)
170
  tile_width = width // num_tiles_x
 
171
  print(f"Adjusted number of tiles: {num_tiles_x} (width) x {num_tiles_y} (height)")
172
+
 
173
  mosaic = np.zeros_like(original_image)
 
174
  rows = list(range(0, height, tile_height))
175
  cols = list(range(0, width, tile_width))
176
  total_tiles = len(rows) * len(cols)
 
177
  tile_count = 0
178
+
179
+ for y in rows:
180
+ for x in cols:
 
181
  y_end = min(y + tile_height, height)
182
  x_end = min(x + tile_width, width)
183
  tile = original_image[y:y_end, x:x_end]
184
  avg_color = np.mean(tile, axis=(0, 1)).astype(np.uint8)
185
+ mosaic[y:y_end, x:x_end] = avg_color
 
186
  tile_count += 1
187
  if progress is not None:
188
  progress(tile_count / total_tiles)
189
+
190
  output_path = "color_mosaic_output.jpg"
 
191
  cv2.imwrite(output_path, cv2.cvtColor(mosaic, cv2.COLOR_RGB2BGR))
 
192
  return output_path
193
 
194
+ # ----------------- Performance Metrics Functions -----------------
195
+
196
+ def compute_mse(original, mosaic):
197
+ """
198
+ Compute Mean Squared Error (MSE) between two images.
199
+ """
200
+ original = original.astype("float")
201
+ mosaic = mosaic.astype("float")
202
+ err = np.sum((original - mosaic) ** 2)
203
+ mse = err / float(original.shape[0] * original.shape[1] * original.shape[2])
204
+ return mse
205
+
206
+ def compute_ssim(original, mosaic):
207
+ """
208
+ Compute Structural Similarity Index (SSIM) between two images.
209
+ """
210
+ min_dim = min(original.shape[0], original.shape[1])
211
+ if min_dim >= 7:
212
+ win_size = 7
213
+ else:
214
+ # Ensure the window size is odd.
215
+ win_size = min_dim if min_dim % 2 == 1 else min_dim - 1
216
+ ssim_value, _ = ssim(original, mosaic, win_size=win_size, channel_axis=-1, full=True)
217
+ return ssim_value
218
+
219
+ def ensure_min_size(image, min_size=7):
220
+ """
221
+ Ensure that the image has a minimum size; if not, resize it.
222
+ """
223
+ h, w = image.shape[:2]
224
+ if h < min_size or w < min_size:
225
+ new_w = max(min_size, w)
226
+ new_h = max(min_size, h)
227
+ image = cv2.resize(image, (new_w, new_h))
228
+ return image
229
+
230
+ # ----------------- Gradio Interface Function -----------------
231
+
232
  def mosaic_gradio(input_image, num_tiles_y, mosaic_type, progress=gr.Progress()):
233
  """
234
+ Gradio interface function to generate and return the mosaic image along with performance metrics.
235
  mosaic_type: Either "Color Mosaic" or "Image Mosaic"
236
+ Returns: (mosaic_image_file, performance_metrics_string)
237
  """
238
+ # Generate mosaic based on selected type
239
  if mosaic_type == "Color Mosaic":
240
  mosaic_path = create_color_mosaic(input_image, num_tiles_y, progress)
241
  else:
242
  mosaic_path = create_photo_mosaic(input_image, DATASET_FOLDER, num_tiles_y, progress)
243
+
244
+ # Load the mosaic image from file (convert from BGR to RGB)
245
+ mosaic_image = cv2.imread(mosaic_path)
246
+ if mosaic_image is None:
247
+ return None, "Error: Mosaic image could not be loaded."
248
+ mosaic_image = cv2.cvtColor(mosaic_image, cv2.COLOR_BGR2RGB)
249
+
250
+ # Ensure both images meet minimum size requirements for metric calculations
251
+ input_for_metrics = ensure_min_size(input_image.copy())
252
+ mosaic_for_metrics = ensure_min_size(mosaic_image.copy())
253
+
254
+ # Compute performance metrics
255
+ mse_value = compute_mse(input_for_metrics, mosaic_for_metrics)
256
+ ssim_value = compute_ssim(input_for_metrics, mosaic_for_metrics)
257
+
258
+ metrics_text = f"MSE: {mse_value:.2f}\nSSIM: {ssim_value:.4f}"
259
+
260
+ return mosaic_path, metrics_text
261
+
262
+ # ----------------- Gradio App Setup -----------------
263
+
264
+ # Adding examples so that test images appear as clickable examples.
265
+ # Adjust the paths as needed.
266
+ examples = [
267
+ ["D:/NEU/5330/Lab-1/input_images/1.jpg", 90, "Image Mosaic"],
268
+ ["D:/NEU/5330/Lab-1/input_images/2.jpg", 90, "Image Mosaic"],
269
+ ["D:/NEU/5330/Lab-1/input_images/3.jpg", 90, "Image Mosaic"],
270
+ ["D:/NEU/5330/Lab-1/input_images/6.jpg", 90, "Image Mosaic"],
271
+ ["D:/NEU/5330/Lab-1/input_images/7.jpg", 90, "Image Mosaic"],
272
+ ["D:/NEU/5330/Lab-1/input_images/8.jpg", 90, "Image Mosaic"],
273
+ ["D:/NEU/5330/Lab-1/input_images/9.jpg", 90, "Image Mosaic"],
274
+ ["D:/NEU/5330/Lab-1/input_images/10.jpg", 90, "Image Mosaic"]
275
+ ]
276
 
 
277
  iface = gr.Interface(
278
  fn=mosaic_gradio,
279
  inputs=[
 
281
  gr.Slider(10, 200, value=90, step=5, label="Number of Tiles (Height)"),
282
  gr.Radio(choices=["Color Mosaic", "Image Mosaic"], label="Mosaic Type", value="Image Mosaic")
283
  ],
284
+ outputs=[
285
+ gr.Image(type="filepath", label="Generated Mosaic"),
286
+ gr.Textbox(label="Performance Metrics")
287
+ ],
288
  title="Photo Mosaic Generator",
289
  description=("Upload an image, choose the number of tiles (height) and mosaic type. "
290
+ "Select 'Color Mosaic' for a mosaic using average colors, or 'Image Mosaic' to use dataset images "
291
+ "matched by color, edge density, texture, and gradient features. "
292
+ "After mosaic generation, performance metrics (MSE and SSIM) will be displayed."),
293
+ examples=examples
294
  )
295
 
 
296
  iface.launch()