RojaKatta commited on
Commit
6983604
·
verified ·
1 Parent(s): fd12481

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +5 -131
app.py CHANGED
@@ -5,7 +5,7 @@ from PIL import Image
5
  # ---------------------- Paths (hair/ first) ----------------------
6
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
7
  CANDIDATES = [
8
- os.path.join(BASE_DIR, "hair"), # <- your current folder
9
  os.path.join(BASE_DIR, "assets", "hairstyles"),
10
  os.path.join(BASE_DIR, "assets", "Hairstyles"),
11
  os.path.join(BASE_DIR, "hairstyles"),
@@ -15,28 +15,24 @@ for p in CANDIDATES:
15
  if os.path.isdir(p):
16
  HAIR_DIR = p
17
  break
18
- if HAIR_DIR is None: # create the canonical path if nothing exists yet
19
  HAIR_DIR = os.path.join(BASE_DIR, "hair")
20
  os.makedirs(HAIR_DIR, exist_ok=True)
21
 
22
  META_PATH = os.path.join(HAIR_DIR, "meta.json") # optional per-style anchors
23
 
24
- # ---------------------- Dependencies ----------------------
25
  try:
26
- import mediapipe as mp # FaceMesh + SelfieSeg
27
  except Exception as e:
28
- raise RuntimeError(
29
- f"Failed to import mediapipe. Check requirements.txt pins. Details: {e}"
30
- )
31
 
32
  mp_face_mesh = mp.solutions.face_mesh
33
  mp_selfie_seg = mp.solutions.selfie_segmentation
34
-
35
  LM = {"left_eye_outer": 33, "right_eye_outer": 263, "mid_forehead": 10}
36
 
37
  # ---------------------- Helpers ----------------------
38
  def load_hairstyles():
39
- """Return sorted list of .png files in HAIR_DIR."""
40
  try:
41
  files = [f for f in os.listdir(HAIR_DIR) if f.lower().endswith(".png")]
42
  except FileNotFoundError:
@@ -55,11 +51,9 @@ def load_meta():
55
  except Exception:
56
  return {}
57
  return {}
58
-
59
  META = load_meta()
60
 
61
  def detect_face_keypoints(img_bgr):
62
- """Return 3 keypoints (left eye outer, right eye outer, mid-forehead) or None."""
63
  h, w = img_bgr.shape[:2]
64
  with mp_face_mesh.FaceMesh(
65
  static_image_mode=True, max_num_faces=1, refine_landmarks=True,
@@ -73,124 +67,4 @@ def detect_face_keypoints(img_bgr):
73
  return np.stack([xy(LM["left_eye_outer"]), xy(LM["right_eye_outer"]), xy(LM["mid_forehead"])])
74
 
75
  def person_mask(img_bgr):
76
- """Rough head isolation using selfie segmentation + feathering."""
77
  with mp_selfie_seg.SelfieSegmentation(model_selection=1) as seg:
78
- rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
79
- m = seg.process(rgb).segmentation_mask
80
- mask = (m > 0.5).astype(np.float32)
81
- mask = cv2.GaussianBlur(mask, (35, 35), 0)
82
- return mask
83
-
84
- def load_hair_png(name):
85
- path = os.path.join(HAIR_DIR, name)
86
- hair = cv2.imread(path, cv2.IMREAD_UNCHANGED) # BGRA
87
- if hair is None or hair.shape[2] != 4:
88
- raise ValueError(f"Invalid hair asset: {name} (must be RGBA PNG)")
89
- return hair
90
-
91
- def hair_reference_points(hair_bgra, filename):
92
- """Three anchors on hair image; override via meta.json if present."""
93
- h, w = hair_bgra.shape[:2]
94
- if filename in META:
95
- pts = np.array(META[filename], dtype=np.float32)
96
- if pts.shape == (3, 2):
97
- return pts
98
- # Defaults (works for many styles; refine via meta.json for perfection)
99
- pL = np.array([0.30*w, 0.60*h], dtype=np.float32)
100
- pR = np.array([0.70*w, 0.60*h], dtype=np.float32)
101
- pM = np.array([0.50*w, 0.40*h], dtype=np.float32)
102
- return np.stack([pL, pR, pM], axis=0)
103
-
104
- def warp_and_alpha_blend(base_bgr, hair_bgra, M, opacity=1.0):
105
- H, W = base_bgr.shape[:2]
106
- hair_rgb = hair_bgra[:, :, :3]
107
- hair_a = hair_bgra[:, :, 3] / 255.0
108
- hair_warp = cv2.warpAffine(hair_rgb, M, (W, H), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT)
109
- a_warp = cv2.warpAffine(hair_a, M, (W, H), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT)
110
- a = np.clip(a_warp * opacity, 0, 1)[..., None]
111
- out = (a * hair_warp + (1 - a) * base_bgr).astype(np.uint8)
112
- return out
113
-
114
- def apply_tryon(image, hairstyle, scale_pct, rot_deg, dx, dy, opacity):
115
- if image is None:
116
- return None, "Upload a photo or enable webcam."
117
- if not hairstyle:
118
- return np.array(image), "Pick a hairstyle first."
119
-
120
- img_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
121
-
122
- kpts = detect_face_keypoints(img_bgr)
123
- if kpts is None:
124
- return image, "No face detected. Try a brighter, front-facing photo."
125
-
126
- hair = load_hair_png(hairstyle)
127
- hair_pts = hair_reference_points(hair, hairstyle)
128
-
129
- # User nudges on destination points
130
- dst = kpts.copy()
131
- dst[:, 0] += dx
132
- dst[:, 1] += dy
133
-
134
- # Scale + rotate hair anchors around their centroid
135
- center = hair_pts.mean(axis=0)
136
- theta = np.deg2rad(rot_deg)
137
- s = max(0.5, scale_pct / 100.0)
138
- R = np.array([[np.cos(theta), -np.sin(theta)],
139
- [np.sin(theta), np.cos(theta)]], dtype=np.float32)
140
- hair_pts_adj = (hair_pts - center) @ R.T * s + center
141
-
142
- M, _ = cv2.estimateAffinePartial2D(hair_pts_adj, dst, method=cv2.LMEDS)
143
- if M is None:
144
- return image, "Could not compute alignment for this image/style."
145
-
146
- out = warp_and_alpha_blend(img_bgr, hair, M, opacity=opacity)
147
-
148
- # Restrict to head region for cleaner look
149
- head = person_mask(img_bgr)
150
- head3 = head[..., None]
151
- out = (head3 * out + (1 - head3) * img_bgr).astype(np.uint8)
152
-
153
- out_rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB)
154
- return out_rgb, "OK"
155
-
156
- def save_png(img):
157
- if img is None:
158
- return None
159
- p = os.path.join(tempfile.gettempdir(), "tryon_result.png")
160
- Image.fromarray(img).save(p)
161
- return p
162
-
163
- def hair_preview(hairstyle):
164
- if not hairstyle:
165
- return None
166
- # Show the raw PNG on checkerboard background for visibility
167
- hair = load_hair_png(hairstyle)
168
- h, w = hair.shape[:2]
169
- # Make checkerboard
170
- tile = 16
171
- bg = np.kron(
172
- ((np.indices((h//tile+1, w//tile+1)).sum(axis=0) % 2) * 64 + 192).astype(np.uint8),
173
- np.ones((tile, tile), np.uint8)
174
- )[:h, :w]
175
- bg_rgb = np.dstack([bg, bg, bg])
176
- a = (hair[:, :, 3:4] / 255.0)
177
- comp = (a * hair[:, :, :3] + (1 - a) * bg_rgb).astype(np.uint8)
178
- comp = cv2.cvtColor(comp, cv2.COLOR_BGR2RGB)
179
- return comp
180
-
181
- # ---------------------- UI ----------------------
182
- def ui():
183
- with gr.Blocks(title="Virtual Try-On (FR1–FR8)", css="""
184
- .gradio-container {max-width: 980px; margin: auto;}
185
- @media (max-width: 768px){ .gradio-container {padding: 8px;} }
186
- """) as demo:
187
- gr.Markdown("## Salon Hairstyle Virtual Try-On\nUpload or use webcam, pick a style from **Select Hairstyle**, adjust, then download.")
188
-
189
- if not HAIR_FILES:
190
- gr.Markdown("⚠️ **No hairstyle PNGs found.** Upload files into **`hair/`** (or `assets/hairstyles/`) and reload this Space.")
191
-
192
- with gr.Tabs():
193
- # ---------------- Photo Tab (FR1,3–7) ----------------
194
- with gr.Tab("Photo"):
195
- with gr.Row():
196
- i
 
5
  # ---------------------- Paths (hair/ first) ----------------------
6
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
7
  CANDIDATES = [
8
+ os.path.join(BASE_DIR, "hair"), # <- your folder
9
  os.path.join(BASE_DIR, "assets", "hairstyles"),
10
  os.path.join(BASE_DIR, "assets", "Hairstyles"),
11
  os.path.join(BASE_DIR, "hairstyles"),
 
15
  if os.path.isdir(p):
16
  HAIR_DIR = p
17
  break
18
+ if HAIR_DIR is None: # create canonical path if nothing exists yet
19
  HAIR_DIR = os.path.join(BASE_DIR, "hair")
20
  os.makedirs(HAIR_DIR, exist_ok=True)
21
 
22
  META_PATH = os.path.join(HAIR_DIR, "meta.json") # optional per-style anchors
23
 
24
+ # ---------------------- MediaPipe ----------------------
25
  try:
26
+ import mediapipe as mp
27
  except Exception as e:
28
+ raise RuntimeError(f"Mediapipe import failed. Check requirements.txt pins. Details: {e}")
 
 
29
 
30
  mp_face_mesh = mp.solutions.face_mesh
31
  mp_selfie_seg = mp.solutions.selfie_segmentation
 
32
  LM = {"left_eye_outer": 33, "right_eye_outer": 263, "mid_forehead": 10}
33
 
34
  # ---------------------- Helpers ----------------------
35
  def load_hairstyles():
 
36
  try:
37
  files = [f for f in os.listdir(HAIR_DIR) if f.lower().endswith(".png")]
38
  except FileNotFoundError:
 
51
  except Exception:
52
  return {}
53
  return {}
 
54
  META = load_meta()
55
 
56
  def detect_face_keypoints(img_bgr):
 
57
  h, w = img_bgr.shape[:2]
58
  with mp_face_mesh.FaceMesh(
59
  static_image_mode=True, max_num_faces=1, refine_landmarks=True,
 
67
  return np.stack([xy(LM["left_eye_outer"]), xy(LM["right_eye_outer"]), xy(LM["mid_forehead"])])
68
 
69
  def person_mask(img_bgr):
 
70
  with mp_selfie_seg.SelfieSegmentation(model_selection=1) as seg: