nishanth-saka commited on
Commit
1dab45f
·
verified ·
1 Parent(s): e7b0e0c
Files changed (1) hide show
  1. app.py +135 -43
app.py CHANGED
@@ -56,7 +56,9 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
56
  img_pil = base_image.convert("RGB")
57
  img_np = np.array(img_pil)
58
 
59
- # Prepare tensor
 
 
60
  img_resized = img_pil.resize((384, 384))
61
  img_tensor = torch.from_numpy(np.array(img_resized)).permute(2, 0, 1).unsqueeze(0).float() / 255.0
62
  mean = torch.as_tensor([0.5, 0.5, 0.5], device=img_tensor.device).view(1, 3, 1, 1)
@@ -74,73 +76,163 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
74
  depth_map = depth_map.squeeze().cpu().numpy()
75
 
76
  # Normalize depth
77
- depth_vis = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min())
78
 
79
  # Normal map
80
  normal_map = depth_to_normal(depth_vis)
81
 
 
82
  # Shading map (CLAHE)
 
83
  img_lab = cv2.cvtColor(img_np, cv2.COLOR_RGB2LAB)
84
  l_channel, _, _ = cv2.split(img_lab)
85
  clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
86
  l_clahe = clahe.apply(l_channel)
87
  shading_map = l_clahe / 255.0
88
-
89
- # Tile pattern
90
- pattern_np = np.array(pattern_image.convert("RGB"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  target_h, target_w = img_np.shape[:2]
92
- pattern_h, pattern_w = pattern_np.shape[:2]
93
- pattern_tiled = np.zeros((target_h, target_w, 3), dtype=np.uint8)
94
- for y in range(0, target_h, pattern_h):
95
- for x in range(0, target_w, pattern_w):
96
- end_y = min(y + pattern_h, target_h)
97
- end_x = min(x + pattern_w, target_w)
98
- pattern_tiled[y:end_y, x:end_x] = pattern_np[0:(end_y - y), 0:(end_x - x)]
99
-
100
- # Blend pattern
101
- normal_map_loaded = normal_map.astype(np.float32)
102
- shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
103
 
104
- alpha = 0.7
105
- blended_shading = alpha * shading_map_loaded + (1 - alpha)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- pattern_folded = pattern_tiled.astype(np.float32) / 255.0 * blended_shading
108
- normal_boost = 0.5 + 0.5 * normal_map_loaded[..., 2:3]
109
- pattern_folded *= normal_boost
110
- pattern_folded = np.clip(pattern_folded, 0, 1)
111
 
112
- # ==========================================================
113
- # Background removal with post-processing (no duplicate blur)
114
- # ==========================================================
115
  buf = BytesIO()
116
  base_image.save(buf, format="PNG")
117
  base_bytes = buf.getvalue()
118
 
119
- # Get RGBA from bgrem
120
  result_no_bg = bgrem_remove(base_bytes)
121
  mask_img = Image.open(BytesIO(result_no_bg)).convert("RGBA")
122
-
123
- # Extract alpha and clean edges
124
  mask_alpha = np.array(mask_img)[:, :, 3].astype(np.float32) / 255.0
125
 
126
- # 1. Slightly stronger shrink (balanced)
127
- kernel = np.ones((5, 5), np.uint8) # slightly larger kernel
128
- mask_binary = (mask_alpha > 0.05).astype(np.uint8) * 255 # slightly stricter threshold
129
- mask_eroded = cv2.erode(mask_binary, kernel, iterations=3) # balanced erosion
130
-
131
-
132
- # 2. Feather edges (blur)
133
  mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
 
 
 
 
 
 
 
134
 
135
- # 3. Normalize
136
- mask_blurred = mask_blurred.astype(np.float32) / 255.0
 
137
 
138
- # Final RGBA
139
- mask_stack = np.stack([mask_blurred] * 3, axis=-1)
140
- pattern_final = pattern_folded * mask_stack
141
- pattern_rgb = (pattern_final * 255).astype(np.uint8)
142
- alpha_channel = (mask_blurred * 255).astype(np.uint8)
143
- pattern_rgba = np.dstack((pattern_rgb, alpha_channel))
144
 
145
  return Image.fromarray(pattern_rgba, mode="RGBA")
146
 
 
56
  img_pil = base_image.convert("RGB")
57
  img_np = np.array(img_pil)
58
 
59
+ # ===============================
60
+ # Prepare tensor (depth surrogate)
61
+ # ===============================
62
  img_resized = img_pil.resize((384, 384))
63
  img_tensor = torch.from_numpy(np.array(img_resized)).permute(2, 0, 1).unsqueeze(0).float() / 255.0
64
  mean = torch.as_tensor([0.5, 0.5, 0.5], device=img_tensor.device).view(1, 3, 1, 1)
 
76
  depth_map = depth_map.squeeze().cpu().numpy()
77
 
78
  # Normalize depth
79
+ depth_vis = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min() + 1e-8)
80
 
81
  # Normal map
82
  normal_map = depth_to_normal(depth_vis)
83
 
84
+ # ===============================
85
  # Shading map (CLAHE)
86
+ # ===============================
87
  img_lab = cv2.cvtColor(img_np, cv2.COLOR_RGB2LAB)
88
  l_channel, _, _ = cv2.split(img_lab)
89
  clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
90
  l_clahe = clahe.apply(l_channel)
91
  shading_map = l_clahe / 255.0
92
+ shading_map_loaded = np.stack([shading_map] * 3, axis=-1) # (H,W,3)
93
+
94
+ # ===============================
95
+ # OVERLAY prep: preserve + feather alpha (NEW)
96
+ # ===============================
97
+ # pattern_np = np.array(pattern_image.convert("RGB")) # <-- ORIGINAL (kills alpha) [COMMENTED]
98
+ pattern_rgba_full = np.array(pattern_image.convert("RGBA"))
99
+ alpha = pattern_rgba_full[:, :, 3].astype(np.float32) / 255.0
100
+ # Feather alpha to soften edges a bit
101
+ alpha_feathered = cv2.GaussianBlur(alpha, (5, 5), sigmaX=2, sigmaY=2)
102
+ alpha_feathered = np.clip(alpha_feathered, 0.0, 1.0)
103
+ # Premultiply RGB by feathered alpha
104
+ rgb = pattern_rgba_full[:, :, :3].astype(np.float32) / 255.0
105
+ rgb_pm = rgb * alpha_feathered[..., None]
106
+
107
+ # Optional: crop to non‑transparent bbox to avoid tiling empty margins
108
+ alpha_thresh = 0.01
109
+ ys, xs = np.where(alpha_feathered > alpha_thresh)
110
+ if ys.size > 0:
111
+ y0, y1 = ys.min(), ys.max() + 1
112
+ x0, x1 = xs.min(), xs.max() + 1
113
+ rgb_pm = rgb_pm[y0:y1, x0:x1, :]
114
+ alpha_crop = alpha_feathered[y0:y1, x0:x1]
115
+ else:
116
+ alpha_crop = alpha_feathered
117
+
118
+ ph, pw = alpha_crop.shape[:2]
119
  target_h, target_w = img_np.shape[:2]
 
 
 
 
 
 
 
 
 
 
 
120
 
121
+ # ===============================
122
+ # Alpha-aware tiling WITH OVERLAP + crossfade (NEW)
123
+ # ===============================
124
+ # --- ORIGINAL hard RGB tiling (seams) [COMMENTED] ---
125
+ # pattern_h, pattern_w = pattern_np.shape[:2]
126
+ # pattern_tiled = np.zeros((target_h, target_w, 3), dtype=np.uint8)
127
+ # for y in range(0, target_h, pattern_h):
128
+ # for x in range(0, target_w, pattern_w):
129
+ # end_y = min(y + pattern_h, target_h)
130
+ # end_x = min(x + pattern_w, target_w)
131
+ # pattern_tiled[y:end_y, x:end_x] = pattern_np[0:(end_y - y), 0:(end_x - x)]
132
+
133
+ # Build weighted tile to cross‑fade edges
134
+ tile_rgb_pm = rgb_pm.astype(np.float32) # (ph,pw,3)
135
+ tile_a = alpha_crop.astype(np.float32)[..., None] # (ph,pw,1)
136
+
137
+ # Overlap size (tune 8–24 px depending on texture)
138
+ overlap = 16
139
+ overlap_y = min(overlap, max(1, ph // 4))
140
+ overlap_x = min(overlap, max(1, pw // 4))
141
+
142
+ # Horizontal & vertical ramps (0..1)
143
+ wx = np.ones((1, pw, 1), np.float32)
144
+ if overlap_x > 0:
145
+ rampx = np.linspace(0, 1, overlap_x, dtype=np.float32)[None, :, None]
146
+ wx[:, :overlap_x, :] = rampx
147
+ wx[:, -overlap_x:, :] = rampx[:, ::-1, :]
148
+
149
+ wy = np.ones((ph, 1, 1), np.float32)
150
+ if overlap_y > 0:
151
+ rampy = np.linspace(0, 1, overlap_y, dtype=np.float32)[:, None, None]
152
+ wy[:overlap_y, :, :] = rampy
153
+ wy[-overlap_y:, :, :] = rampy[::-1, :, :]
154
+
155
+ w_ramp = wx * wy # (ph,pw,1)
156
+
157
+ tile_rgb_pm_w = tile_rgb_pm * w_ramp
158
+ tile_a_w = tile_a * w_ramp
159
+
160
+ # Canvas in premultiplied form
161
+ canvas_rgb_pm = np.zeros((target_h, target_w, 3), np.float32)
162
+ canvas_a = np.zeros((target_h, target_w, 1), np.float32)
163
+
164
+ # Step so tiles overlap
165
+ step_y = ph - overlap_y
166
+ step_x = pw - overlap_x
167
+ if step_y <= 0: step_y = ph
168
+ if step_x <= 0: step_x = pw
169
+
170
+ for y0 in range(0, target_h, step_y):
171
+ for x0 in range(0, target_w, step_x):
172
+ y1 = min(y0 + ph, target_h)
173
+ x1 = min(x0 + pw, target_w)
174
+ h = y1 - y0
175
+ w_ = x1 - x0
176
+
177
+ src_rgb_pm = tile_rgb_pm_w[:h, :w_, :]
178
+ src_a = tile_a_w[:h, :w_, :]
179
+
180
+ dst_rgb_pm = canvas_rgb_pm[y0:y1, x0:x1, :]
181
+ dst_a = canvas_a[y0:y1, x0:x1, :]
182
+
183
+ # Porter‑Duff "over" in premultiplied space
184
+ out_rgb_pm = src_rgb_pm + dst_rgb_pm * (1.0 - src_a)
185
+ out_a = src_a + dst_a * (1.0 - src_a)
186
+
187
+ canvas_rgb_pm[y0:y1, x0:x1, :] = out_rgb_pm
188
+ canvas_a[y0:y1, x0:x1, :] = out_a
189
+
190
+ # Keep premultiplied; no un‑premultiply yet
191
+ pattern_rgb_pm_tiled = canvas_rgb_pm
192
+ pattern_alpha_tiled = np.clip(canvas_a[..., 0], 0.0, 1.0) # (H,W)
193
+
194
+ # ===============================
195
+ # Shading & normal boost (premultiplied)
196
+ # ===============================
197
+ normal_map_loaded = normal_map.astype(np.float32)
198
+ alpha_s = 0.7
199
+ blended_shading = alpha_s * shading_map_loaded + (1 - alpha_s)
200
 
201
+ # Multiply premultiplied RGB by shading & normal (alpha unchanged)
202
+ pattern_rgb_pm_shaded = pattern_rgb_pm_tiled * blended_shading
203
+ pattern_rgb_pm_shaded *= (0.5 + 0.5 * normal_map_loaded[..., 2:3])
204
+ pattern_rgb_pm_shaded = np.clip(pattern_rgb_pm_shaded, 0.0, 1.0)
205
 
206
+ # ===============================
207
+ # Background removal for BASE + edge clean
208
+ # ===============================
209
  buf = BytesIO()
210
  base_image.save(buf, format="PNG")
211
  base_bytes = buf.getvalue()
212
 
 
213
  result_no_bg = bgrem_remove(base_bytes)
214
  mask_img = Image.open(BytesIO(result_no_bg)).convert("RGBA")
 
 
215
  mask_alpha = np.array(mask_img)[:, :, 3].astype(np.float32) / 255.0
216
 
217
+ kernel = np.ones((5, 5), np.uint8)
218
+ mask_binary = (mask_alpha > 0.05).astype(np.uint8) * 255
219
+ mask_eroded = cv2.erode(mask_binary, kernel, iterations=3)
 
 
 
 
220
  mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
221
+ mask_blurred = mask_blurred.astype(np.float32) / 255.0 # [0,1]
222
+
223
+ # ===============================
224
+ # Final alpha & straight RGB out
225
+ # ===============================
226
+ # Combine base mask with tiled overlay alpha to avoid re‑introducing seams
227
+ alpha_final = np.clip(mask_blurred * pattern_alpha_tiled, 0.0, 1.0)
228
 
229
+ # Convert premultiplied → straight once, at the end
230
+ alpha_safe = np.clip(alpha_final, 1e-6, 1.0)
231
+ pattern_rgb_straight = np.clip(pattern_rgb_pm_shaded / alpha_safe[..., None], 0.0, 1.0)
232
 
233
+ pattern_rgb_u8 = (pattern_rgb_straight * 255).astype(np.uint8)
234
+ alpha_u8 = (alpha_final * 255).astype(np.uint8)
235
+ pattern_rgba = np.dstack((pattern_rgb_u8, alpha_u8))
 
 
 
236
 
237
  return Image.fromarray(pattern_rgba, mode="RGBA")
238