Create overlay.py
Browse files- overlay.py +52 -0
overlay.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
def _read_png_rgba(path):
|
5 |
+
png = cv2.imread(path, cv2.IMREAD_UNCHANGED)
|
6 |
+
if png is None or png.shape[2] != 4:
|
7 |
+
raise ValueError("Hairstyle PNG must be RGBA with transparency.")
|
8 |
+
return png
|
9 |
+
|
10 |
+
def auto_align(png_rgba, mask, landmarks=None):
|
11 |
+
"""Scale & position the hairstyle PNG to cover the mask area, using landmarks if available."""
|
12 |
+
mh, mw = mask.shape[:2]
|
13 |
+
ys, xs = np.where(mask > 0)
|
14 |
+
if len(xs) == 0 or len(ys) == 0:
|
15 |
+
return cv2.resize(png_rgba, (mw, mh))
|
16 |
+
|
17 |
+
x0, x1 = xs.min(), xs.max()
|
18 |
+
y0, y1 = ys.min(), ys.max()
|
19 |
+
tw, th = int((x1 - x0) * 1.2), int((y1 - y0) * 1.1) # Slightly larger for better coverage
|
20 |
+
tw = max(1, min(tw, mw))
|
21 |
+
th = max(1, min(th, mh))
|
22 |
+
aligned = cv2.resize(png_rgba, (tw, th))
|
23 |
+
|
24 |
+
canvas = np.zeros((mh, mw, 4), dtype=np.uint8)
|
25 |
+
|
26 |
+
# Position: Use landmarks if available, else center on mask
|
27 |
+
if landmarks and "forehead_anchor" in landmarks:
|
28 |
+
fx, fy = landmarks["forehead_anchor"]
|
29 |
+
y = max(0, fy - int(0.8 * th)) # Anchor near forehead, offset up for hair
|
30 |
+
x = max(0, fx - int(tw / 2))
|
31 |
+
else:
|
32 |
+
y = max(0, y0 - int(0.25 * th))
|
33 |
+
x = max(0, x0 - int(0.05 * tw))
|
34 |
+
|
35 |
+
y2 = min(mh, y + th)
|
36 |
+
x2 = min(mw, x + tw)
|
37 |
+
crop_h, crop_w = y2 - y, x2 - x
|
38 |
+
canvas[y:y2, x:x2] = aligned[:crop_h, :crop_w]
|
39 |
+
return canvas
|
40 |
+
|
41 |
+
def _alpha_blend(base_bgr, overlay_rgba):
|
42 |
+
bgr = base_bgr.copy()
|
43 |
+
alpha = overlay_rgba[:, :, 3:4] / 255.0
|
44 |
+
rgb = overlay_rgba[:, :, :3]
|
45 |
+
bgr = (alpha * rgb + (1 - alpha) * bgr).astype(np.uint8)
|
46 |
+
return bgr
|
47 |
+
|
48 |
+
def apply_hairstyle(img_bgr, style_path, mask, landmarks=None):
|
49 |
+
png = _read_png_rgba(style_path)
|
50 |
+
aligned = auto_align(png, mask, landmarks)
|
51 |
+
out = _alpha_blend(img_bgr, aligned)
|
52 |
+
return out
|