Seniordev22 commited on
Commit
eb4d078
·
verified ·
1 Parent(s): 0141af9

Update bald_processor.py

Browse files
Files changed (1) hide show
  1. bald_processor.py +67 -35
bald_processor.py CHANGED
@@ -1,4 +1,3 @@
1
- # bald_processor_clean.py
2
  import cv2
3
  import torch
4
  import numpy as np
@@ -24,81 +23,114 @@ except Exception as e:
24
  raise RuntimeError("SegFormer model load failed!")
25
 
26
  hair_class_id = 13
27
- ear_class_ids = [7, 8]
 
28
 
29
  def make_realistic_bald(input_image: Image.Image) -> Image.Image:
30
  if input_image is None:
31
  raise ValueError("No input image provided!")
32
-
33
  try:
34
  orig_w, orig_h = input_image.size
35
  original_np = np.array(input_image)
36
  original_bgr = cv2.cvtColor(original_np, cv2.COLOR_RGB2BGR)
37
-
38
  MAX_DIM = 2048
39
  scale_factor = 1.0
40
  working_np = original_np.copy()
41
  working_bgr = original_bgr.copy()
42
  working_h, working_w = orig_h, orig_w
43
-
44
  if max(orig_w, orig_h) > MAX_DIM:
45
  scale_factor = MAX_DIM / max(orig_w, orig_h)
46
- working_w, working_h = int(orig_w*scale_factor), int(orig_h*scale_factor)
47
  working_np = cv2.resize(original_np, (working_w, working_h), interpolation=cv2.INTER_AREA)
48
  working_bgr = cv2.cvtColor(working_np, cv2.COLOR_RGB2BGR)
49
-
50
  # Segmentation
51
  pil_working = Image.fromarray(working_np)
52
  inputs = processor(images=pil_working, return_tensors="pt").to(device)
53
  with torch.no_grad():
54
  outputs = model(**inputs)
55
  logits = outputs.logits
56
-
57
- upsampled = torch.nn.functional.interpolate(
58
- logits, size=(working_h, working_w),
59
- mode="bilinear", align_corners=False
60
- )
61
- parsing = upsampled.argmax(dim=1).squeeze(0).cpu().numpy()
62
-
63
- # Hair mask
64
  hair_mask = (parsing == hair_class_id).astype(np.uint8)
65
  ears_mask = np.zeros_like(hair_mask)
66
  for cls in ear_class_ids:
67
  ears_mask[parsing == cls] = 1
68
- hair_mask[ears_mask==1] = 0
69
-
70
- # Morphology to clean
71
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13,13))
72
  hair_mask = cv2.morphologyEx(hair_mask, cv2.MORPH_CLOSE, kernel, iterations=2)
73
  hair_mask = cv2.dilate(hair_mask, kernel, iterations=1)
74
- hair_mask = (cv2.GaussianBlur(hair_mask.astype(np.float32), (5,5), 0) > 0.28).astype(np.uint8)
75
-
76
  hair_pixels = np.sum(hair_mask)
77
  if hair_pixels < 50:
78
  raise ValueError("NO_HAIR_DETECTED")
79
-
80
- # Inpainting (no extra noise, no blur)
81
- radius = 15 if hair_pixels > 220000 else 10
82
  flag = cv2.INPAINT_TELEA if hair_pixels > 220000 else cv2.INPAINT_NS
83
- inpainted_bgr = cv2.inpaint(working_bgr, hair_mask*255, inpaintRadius=radius, flags=flag)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  inpainted_rgb = cv2.cvtColor(inpainted_bgr, cv2.COLOR_BGR2RGB)
85
-
86
- # Upscale bald area if needed
87
  if scale_factor < 1.0:
88
  bald_up = cv2.resize(inpainted_rgb, (orig_w, orig_h), interpolation=cv2.INTER_LANCZOS4)
89
- mask_up = cv2.resize(hair_mask.astype(np.uint8), (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
90
  else:
91
  bald_up = inpainted_rgb
92
  mask_up = hair_mask
93
-
94
- # Composite only bald area, rest untouched
95
- result = original_np.copy()
96
- result[mask_up==1] = bald_up[mask_up==1]
97
-
 
 
98
  return Image.fromarray(result)
99
-
100
  except UnidentifiedImageError:
101
  raise ValueError("Invalid image format or corrupt image!")
102
  except Exception as e:
103
  logger.error(f"Bald processing failed: {str(e)}", exc_info=True)
104
- raise RuntimeError(f"Bald processing failed: {str(e)}")
 
 
1
  import cv2
2
  import torch
3
  import numpy as np
 
23
  raise RuntimeError("SegFormer model load failed!")
24
 
25
  hair_class_id = 13
26
+ ear_class_ids = [8, 9] # l_ear=8, r_ear=9 (confirmed from model card)
27
+ skin_class_id = 1 # skin
28
 
29
  def make_realistic_bald(input_image: Image.Image) -> Image.Image:
30
  if input_image is None:
31
  raise ValueError("No input image provided!")
32
+
33
  try:
34
  orig_w, orig_h = input_image.size
35
  original_np = np.array(input_image)
36
  original_bgr = cv2.cvtColor(original_np, cv2.COLOR_RGB2BGR)
37
+
38
  MAX_DIM = 2048
39
  scale_factor = 1.0
40
  working_np = original_np.copy()
41
  working_bgr = original_bgr.copy()
42
  working_h, working_w = orig_h, orig_w
43
+
44
  if max(orig_w, orig_h) > MAX_DIM:
45
  scale_factor = MAX_DIM / max(orig_w, orig_h)
46
+ working_w, working_h = int(orig_w * scale_factor), int(orig_h * scale_factor)
47
  working_np = cv2.resize(original_np, (working_w, working_h), interpolation=cv2.INTER_AREA)
48
  working_bgr = cv2.cvtColor(working_np, cv2.COLOR_RGB2BGR)
49
+
50
  # Segmentation
51
  pil_working = Image.fromarray(working_np)
52
  inputs = processor(images=pil_working, return_tensors="pt").to(device)
53
  with torch.no_grad():
54
  outputs = model(**inputs)
55
  logits = outputs.logits
56
+ upsampled = torch.nn.functional.interpolate(
57
+ logits, size=(working_h, working_w), mode="bilinear", align_corners=False
58
+ )
59
+ parsing = upsampled.argmax(dim=1).squeeze(0).cpu().numpy()
60
+
61
+ # Masks
 
 
62
  hair_mask = (parsing == hair_class_id).astype(np.uint8)
63
  ears_mask = np.zeros_like(hair_mask)
64
  for cls in ear_class_ids:
65
  ears_mask[parsing == cls] = 1
66
+ hair_mask[ears_mask == 1] = 0
67
+
68
+ # Clean mask
69
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13))
70
  hair_mask = cv2.morphologyEx(hair_mask, cv2.MORPH_CLOSE, kernel, iterations=2)
71
  hair_mask = cv2.dilate(hair_mask, kernel, iterations=1)
72
+ hair_mask = (cv2.GaussianBlur(hair_mask.astype(np.float32), (5, 5), 0) > 0.28).astype(np.uint8)
73
+
74
  hair_pixels = np.sum(hair_mask)
75
  if hair_pixels < 50:
76
  raise ValueError("NO_HAIR_DETECTED")
77
+
78
+ # Inpainting
79
+ radius = 20 if hair_pixels > 220000 else 12 # Slightly larger for big areas
80
  flag = cv2.INPAINT_TELEA if hair_pixels > 220000 else cv2.INPAINT_NS
81
+ inpainted_bgr = cv2.inpaint(working_bgr, hair_mask * 255, inpaintRadius=radius, flags=flag)
82
+
83
+ # Large hair: Color correction + Seamless blending
84
+ if hair_pixels > 220000:
85
+ skin_mask = (parsing == skin_class_id).astype(np.uint8)
86
+ ref_mask = skin_mask.copy()
87
+ ref_mask[hair_mask == 1] = 0
88
+ ref_mask = cv2.erode(ref_mask, np.ones((3,3), np.uint8), iterations=1) # Avoid border artifacts
89
+
90
+ ref_mean = cv2.mean(working_bgr, mask=ref_mask * 255)[:3]
91
+ inp_mean = cv2.mean(inpainted_bgr, mask=hair_mask * 255)[:3]
92
+
93
+ if ref_mean != (0,0,0): # Valid reference
94
+ color_diff = np.array(ref_mean) - np.array(inp_mean)
95
+ color_diff = np.clip(color_diff * 0.7, -40, 40) # Softer adjustment to avoid over-correction
96
+
97
+ hair_3ch = np.repeat(hair_mask[..., None], 3, axis=2)
98
+ inpainted_bgr[hair_3ch == 1] = np.clip(
99
+ inpainted_bgr[hair_3ch == 1].astype(np.float32) + color_diff, 0, 255
100
+ ).astype(np.uint8)
101
+
102
+ # Seamless clone for better merge (MIXED_CLONE good for skin)
103
+ moments = cv2.moments(hair_mask * 255)
104
+ if moments["m00"] != 0:
105
+ cx = int(moments["m10"] / moments["m00"])
106
+ cy = int(moments["m01"] / moments["m00"])
107
+ else:
108
+ cx, cy = working_w // 2, working_h // 2
109
+
110
+ inpainted_bgr = cv2.seamlessClone(
111
+ inpainted_bgr, working_bgr, hair_mask * 255, (cx, cy), cv2.MIXED_CLONE
112
+ )
113
+
114
  inpainted_rgb = cv2.cvtColor(inpainted_bgr, cv2.COLOR_BGR2RGB)
115
+
116
+ # Upscale if downscaled
117
  if scale_factor < 1.0:
118
  bald_up = cv2.resize(inpainted_rgb, (orig_w, orig_h), interpolation=cv2.INTER_LANCZOS4)
119
+ mask_up = cv2.resize(hair_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)
120
  else:
121
  bald_up = inpainted_rgb
122
  mask_up = hair_mask
123
+
124
+ # Soft alpha blend (always, for smooth edges)
125
+ mask_float = cv2.GaussianBlur(mask_up.astype(np.float32) * 255, (25, 25), 0) / 255.0
126
+ result = (1 - mask_float[..., None]) * original_np.astype(np.float32) + \
127
+ mask_float[..., None] * bald_up.astype(np.float32)
128
+ result = np.clip(result, 0, 255).astype(np.uint8)
129
+
130
  return Image.fromarray(result)
131
+
132
  except UnidentifiedImageError:
133
  raise ValueError("Invalid image format or corrupt image!")
134
  except Exception as e:
135
  logger.error(f"Bald processing failed: {str(e)}", exc_info=True)
136
+ raise RuntimeError(f"Bald processing failed: {str(e)}")