nishanth-saka commited on
Commit
42acb28
·
verified ·
1 Parent(s): 3b3ec63

REVERT to K=5

Browse files
Files changed (1) hide show
  1. app.py +25 -238
app.py CHANGED
@@ -53,57 +53,8 @@ def depth_to_normal(depth):
53
  # CORE PROCESSING FUNCTION
54
  # ===============================
55
  def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
56
- # img_pil = base_image.convert("RGB") # <-- COMMENTED: this injects black behind transparency
57
- # img_np = np.array(img_pil)
58
-
59
- # --- ORIGINAL (white matte) kept for reference ---
60
- # base_rgba = base_image.convert("RGBA")
61
- # _arr = np.array(base_rgba).astype(np.float32)
62
- # _rgb = _arr[..., :3]
63
- # _a = (_arr[..., 3:4] / 255.0)
64
- # _rgb_over_white = _rgb * _a + (1.0 - _a) * 255.0
65
- # _rgb_over_white = _rgb_over_white.astype(np.uint8)
66
- # img_pil = Image.fromarray(_rgb_over_white, mode="RGB")
67
- # img_np = _rgb_over_white
68
- # --- end ORIGINAL ---
69
-
70
- # --- NEW: alpha-aware RGB using median color along interior boundary as matte ---
71
- base_rgba = base_image.convert("RGBA")
72
- _arr = np.array(base_rgba).astype(np.float32) # (H,W,4), RGB in 0..255, A in 0..255
73
- _rgb = _arr[..., :3] # (H,W,3)
74
- _alpha8 = _arr[..., 3].astype(np.uint8) # (H,W) uint8 alpha
75
- _a = (_alpha8.astype(np.float32) / 255.0)[..., None] # (H,W,1) float alpha
76
-
77
- # Build a foreground mask from alpha (slightly strict to avoid wispy edges)
78
- _fg_mask = (_alpha8 > 128).astype(np.uint8) * 255 # (H,W) 0/255
79
-
80
- # Morphological interior boundary: foreground minus a 1-iteration erosion
81
- _k = np.ones((3, 3), np.uint8)
82
- _eroded = cv2.erode(_fg_mask, _k, iterations=1)
83
- _boundary = cv2.bitwise_and(_fg_mask, cv2.bitwise_not(_eroded)) # thin interior ring
84
-
85
- # If boundary is too thin/few pixels, widen the ring via morphological gradient
86
- if int((_boundary > 0).sum()) < 100:
87
- _dil = cv2.dilate(_fg_mask, _k, iterations=2)
88
- _ero = cv2.erode(_fg_mask, _k, iterations=2)
89
- _boundary = cv2.subtract(_dil, _ero)
90
-
91
- _idx = (_boundary > 0)
92
- if not np.any(_idx):
93
- # Fallback: use entire foreground if boundary not found
94
- _idx = (_fg_mask > 0)
95
-
96
- # Compute median color over the selected boundary pixels (in 0..255 space)
97
- _median_color = np.median(_rgb[_idx], axis=0) if np.any(_idx) else np.array([255.0, 255.0, 255.0], dtype=np.float32)
98
- _median_color = _median_color.reshape(1, 1, 3) # (1,1,3)
99
-
100
- # Composite RGB over median matte (avoid introducing black/white bias)
101
- _rgb_over_matte = _rgb * _a + (1.0 - _a) * _median_color
102
- _rgb_over_matte = np.clip(_rgb_over_matte, 0.0, 255.0).astype(np.uint8)
103
-
104
- img_pil = Image.fromarray(_rgb_over_matte, mode="RGB")
105
- img_np = _rgb_over_matte
106
- # --- end NEW ---
107
 
108
  # Prepare tensor
109
  img_resized = img_pil.resize((384, 384))
@@ -135,107 +86,28 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
135
  l_clahe = clahe.apply(l_channel)
136
  shading_map = l_clahe / 255.0
137
 
138
- # ==========================================================
139
- # [PATCHED] TILE PATTERN (soft crossfade) + BLEND
140
- # Replace the block from "# Tile pattern" through
141
- # "pattern_folded = np.clip(pattern_folded, 0, 1)"
142
- # and keep everything below (BG removal etc.) unchanged.
143
- # ==========================================================
144
-
145
- # ----- ORIGINAL (hard tiling) — kept for reference -----
146
- # # Tile pattern
147
- # pattern_np = np.array(pattern_image.convert("RGB"))
148
- # target_h, target_w = img_np.shape[:2]
149
- # pattern_h, pattern_w = pattern_np.shape[:2]
150
- # pattern_tiled = np.zeros((target_h, target_w, 3), dtype=np.uint8)
151
- # for y in range(0, target_h, pattern_h):
152
- # for x in range(0, target_w, pattern_w):
153
- # end_y = min(y + pattern_h, target_h)
154
- # end_x = min(x + pattern_w, target_w)
155
- # pattern_tiled[y:end_y, x:end_x] = pattern_np[0:(end_y - y), 0:(end_x - x)]
156
- #
157
- # # Blend pattern
158
- # normal_map_loaded = normal_map.astype(np.float32)
159
- # shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
160
- # alpha = 0.7
161
- # blended_shading = alpha * shading_map_loaded + (1 - alpha)
162
- # pattern_folded = pattern_tiled.astype(np.float32) / 255.0 * blended_shading
163
- # normal_boost = 0.5 + 0.5 * normal_map_loaded[..., 2:3]
164
- # pattern_folded *= normal_boost
165
- # pattern_folded = np.clip(pattern_folded, 0, 1)
166
- # ----- END ORIGINAL -----
167
-
168
- # ---------- NEW: seam‑free overlapped tiling ----------
169
- eps = 1e-6
170
-
171
- # 1) Clean the tile (unpremultiply RGBA to remove black fringe; optional 2px crop)
172
- _tile_rgba = np.array(pattern_image.convert("RGBA")).astype(np.float32) / 255.0
173
- _tile_rgb = _tile_rgba[..., :3]
174
- _tile_a = _tile_rgba[..., 3:4]
175
- _tile_rgb = np.where(_tile_a > eps, _tile_rgb / np.clip(_tile_a, eps, 1.0), _tile_rgb)
176
-
177
- _crop = 2 # try 2–3 if fringe persists
178
- if _tile_rgb.shape[0] > 2*_crop and _tile_rgb.shape[1] > 2*_crop:
179
- _tile_rgb = _tile_rgb[_crop:-_crop, _crop:-_crop, :]
180
-
181
- pattern_np = (np.clip(_tile_rgb, 0.0, 1.0) * 255).astype(np.uint8)
182
-
183
- # 2) Overlapped tiling with cosine crossfade
184
  target_h, target_w = img_np.shape[:2]
185
- th, tw = pattern_np.shape[:2]
186
- ov = 6 # overlap width in px (tune 4–12)
187
-
188
- def _make_weight(h, w, ov_):
189
- ovh = min(ov_, h // 2)
190
- ovw = min(ov_, w // 2)
191
- wy = np.ones((h,), dtype=np.float32)
192
- wx = np.ones((w,), dtype=np.float32)
193
- if ovh > 0:
194
- t = np.linspace(0, np.pi, ovh, endpoint=False)
195
- ramp = 0.5 - 0.5 * np.cos(t)
196
- wy[:ovh] = np.minimum(wy[:ovh], ramp)
197
- wy[-ovh:] = np.minimum(wy[-ovh:], ramp[::-1])
198
- if ovw > 0:
199
- t = np.linspace(0, np.pi, ovw, endpoint=False)
200
- ramp = 0.5 - 0.5 * np.cos(t)
201
- wx[:ovw] = np.minimum(wx[:ovw], ramp)
202
- wx[-ovw:] = np.minimum(wx[-ovw:], ramp[::-1])
203
- return np.outer(wy, wx).astype(np.float32)
204
-
205
- acc = np.zeros((target_h, target_w, 3), dtype=np.float32)
206
- wacc = np.zeros((target_h, target_w, 1), dtype=np.float32)
207
-
208
- stride_y = max(1, th - ov)
209
- stride_x = max(1, tw - ov)
210
-
211
- for y in range(0, target_h, stride_y):
212
- for x in range(0, target_w, stride_x):
213
- h_crop = min(th, target_h - y)
214
- w_crop = min(tw, target_w - x)
215
- tile = pattern_np[:h_crop, :w_crop, :].astype(np.float32) / 255.0
216
- wmask = _make_weight(h_crop, w_crop, ov)[..., None] # (h,w,1)
217
-
218
- acc[y:y+h_crop, x:x+w_crop] += tile * wmask
219
- wacc[y:y+h_crop, x:x+w_crop] += wmask
220
-
221
- pattern_tiled = acc / np.clip(wacc, eps, None)
222
- pattern_tiled = np.clip(pattern_tiled, 0.0, 1.0) # float32 in [0,1]
223
-
224
- # 3) Continue with your shading/normal blending (unchanged)
225
- normal_map_loaded = normal_map.astype(np.float32)
226
  shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
227
- alpha = 0.7
228
- blended_shading = alpha * shading_map_loaded + (1 - alpha)
229
 
230
- pattern_folded = pattern_tiled * blended_shading
231
- normal_boost = 0.5 + 0.5 * normal_map_loaded[..., 2:3]
232
- pattern_folded *= normal_boost
233
- pattern_folded = np.clip(pattern_folded, 0.0, 1.0)
234
-
235
- # ==========================================================
236
- # [END PATCHED]
237
- # ==========================================================
238
 
 
 
 
 
239
 
240
  # ==========================================================
241
  # Background removal with post-processing (no duplicate blur)
@@ -257,107 +129,22 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
257
  mask_binary = (mask_alpha > k/100).astype(np.uint8) * 255 # slightly stricter threshold
258
  mask_eroded = cv2.erode(mask_binary, kernel, iterations=3) # balanced erosion
259
 
 
260
  # 2. Feather edges (blur)
261
  mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
262
 
263
  # 3. Normalize
264
  mask_blurred = mask_blurred.astype(np.float32) / 255.0
265
 
266
- # ================================
267
- # NEW: SEAM-FIX UPSTREAM (3 steps)
268
- # ================================
269
- # (A) MASK EXPANSION / OVERLAP: expand slightly to ensure overlap across seams
270
- overlap_iters = 2 # <-- tune: 1..3 (px-ish with 5x5 kernel)
271
- # mask_expanded = cv2.dilate(mask_eroded, kernel, iterations=overlap_iters) # old idea
272
- # --- Better: expand the FLOAT feathered mask to preserve soft edge continuity ---
273
- _mask_float = (mask_blurred * 255).astype(np.uint8)
274
- _mask_expanded_u8 = cv2.dilate(_mask_float, kernel, iterations=overlap_iters)
275
- mask_expanded = _mask_expanded_u8.astype(np.float32) / 255.0 # [0..1]
276
-
277
- # (B) FEATHER AGAIN after expansion (very light) for a smooth transition band
278
- mask_expanded = cv2.GaussianBlur((mask_expanded * 255).astype(np.uint8), (7, 7), sigmaX=1.5, sigmaY=1.5)
279
- mask_expanded = mask_expanded.astype(np.float32) / 255.0
280
-
281
- # (C) BLEED-COLOR FILLING in premultiplied space for near-edge pixels
282
- # - Create a thin edge band where alpha is small (e.g., up to 8%)
283
- edge_upper = 0.08
284
  # Final RGBA
285
- # mask_stack = np.stack([mask_blurred] * 3, axis=-1)
286
- # pattern_final = pattern_folded * mask_stack
287
- # --- Replace above with expanded mask for overlap ---
288
- mask_stack = np.stack([mask_expanded] * 3, axis=-1)
289
- pattern_final = pattern_folded * mask_stack # premultiplied RGB (color * alpha) with overlap
290
-
291
- # - Dilate premultiplied RGB slightly so edge pixels borrow nearby garment color
292
- bleed_iters = 2 # <-- tune: 1..3
293
- _kernel_bleed = np.ones((3, 3), np.uint8)
294
- _premul_u8 = (pattern_final * 255).astype(np.uint8)
295
- _premul_bleed = cv2.dilate(_premul_u8, _kernel_bleed, iterations=bleed_iters).astype(np.float32) / 255.0
296
-
297
- # - Replace only in the very thin edge band (alpha between 0 and edge_upper)
298
- edge_band = (mask_expanded > 0.0) & (mask_expanded <= edge_upper)
299
- if np.any(edge_band):
300
- pattern_final[edge_band] = _premul_bleed[edge_band]
301
- # ================================
302
- # END NEW: SEAM-FIX UPSTREAM
303
- # ================================
304
-
305
- # ================================
306
- # POST-PROCESS: SEAM / BLACK-LINE FILL
307
- # (place this right before: "Premultiplied → Straight alpha")
308
- # ================================
309
-
310
- # Build a temporary straight-RGB for detection (do NOT export this)
311
- _alpha_tmp = np.clip(mask_expanded, eps, 1.0) # [H,W]
312
- _alpha_tmp3 = _alpha_tmp[..., None] # [H,W,1]
313
- straight_tmp = np.clip(pattern_final / _alpha_tmp3, 0.0, 1.0) # [H,W,3], float
314
-
315
- # Convert to 8-bit for OpenCV ops
316
- straight_u8 = (straight_tmp * 255).astype(np.uint8)
317
- gray8 = cv2.cvtColor(straight_u8, cv2.COLOR_RGB2GRAY)
318
-
319
- # Local median to find narrow dark troughs (seams)
320
- med8 = cv2.medianBlur(gray8, 5)
321
-
322
- # Heuristics: dark + locally darker than neighbors + inside garment
323
- dark_thr = int(0.22 * 255) # tune: 0.18..0.28
324
- delta_thr = int(0.08 * 255) # tune: 0.06..0.10
325
- dark = gray8 < dark_thr
326
- contrast = (med8.astype(np.int16) - gray8.astype(np.int16)) > delta_thr
327
- inside = (mask_expanded > 0.06) # avoid true background
328
-
329
- seam_mask = (dark & contrast & inside).astype(np.uint8) * 255
330
-
331
- # Keep seams thin (remove blobs); nudge toward single‑pixel cores
332
- k3 = np.ones((3, 3), np.uint8)
333
- seam_mask = cv2.morphologyEx(seam_mask, cv2.MORPH_OPEN, k3, iterations=1)
334
- seam_mask = cv2.erode(seam_mask, np.ones((2, 2), np.uint8), iterations=1)
335
-
336
- # Inpaint on straight space (Telea = good for thin scratches)
337
- inpaint_radius = 3 # try 2..4
338
- inpainted_u8 = cv2.inpaint(straight_u8, seam_mask, inpaint_radius, cv2.INPAINT_TELEA)
339
- inpainted = inpainted_u8.astype(np.float32) / 255.0
340
-
341
- # Re-premultiply and replace
342
-
343
-
344
- # Premultiplied → Straight alpha
345
- eps = 1e-6
346
- # _alpha = np.clip(mask_blurred, eps, 1.0)
347
- # --- Use the expanded mask for export, with a small alpha floor to hide hairlines ---
348
- alpha_floor = 0.02 # 2% floor; increase to 0.03 if a faint line persists
349
- _alpha = np.clip(mask_expanded, max(alpha_floor, eps), 1.0)
350
- _alpha3 = _alpha[..., None]
351
- rgb_straight = np.clip(pattern_final / _alpha3, 0.0, 1.0)
352
-
353
- pattern_rgb = (rgb_straight * 255).astype(np.uint8)
354
- alpha_channel = (_alpha * 255).astype(np.uint8)
355
  pattern_rgba = np.dstack((pattern_rgb, alpha_channel))
356
 
357
  return Image.fromarray(pattern_rgba, mode="RGBA")
358
 
359
-
360
-
361
  # ===============================
362
  # WRAPPER: ACCEPT BYTES OR BASE64
363
  # ===============================
 
53
  # CORE PROCESSING FUNCTION
54
  # ===============================
55
  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))
 
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)
 
129
  mask_binary = (mask_alpha > k/100).astype(np.uint8) * 255 # slightly stricter threshold
130
  mask_eroded = cv2.erode(mask_binary, kernel, iterations=3) # balanced erosion
131
 
132
+
133
  # 2. Feather edges (blur)
134
  mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
135
 
136
  # 3. Normalize
137
  mask_blurred = mask_blurred.astype(np.float32) / 255.0
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  # Final RGBA
140
+ mask_stack = np.stack([mask_blurred] * 3, axis=-1)
141
+ pattern_final = pattern_folded * mask_stack
142
+ pattern_rgb = (pattern_final * 255).astype(np.uint8)
143
+ alpha_channel = (mask_blurred * 255).astype(np.uint8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  pattern_rgba = np.dstack((pattern_rgb, alpha_channel))
145
 
146
  return Image.fromarray(pattern_rgba, mode="RGBA")
147
 
 
 
148
  # ===============================
149
  # WRAPPER: ACCEPT BYTES OR BASE64
150
  # ===============================