File size: 2,904 Bytes
8dff9a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import numpy as np
from PIL import Image
import mediapipe as mp

from baldhead import inference       # cạo tóc background
from segmentation import extract_hair

# MediaPipe Face Detection
mp_fd = mp.solutions.face_detection.FaceDetection(model_selection=1,
                                                  min_detection_confidence=0.5)

def get_face_bbox(img: Image.Image) -> tuple[int,int,int,int] | None:
    arr = np.array(img.convert("RGB"))
    res = mp_fd.process(arr)
    if not res.detections:
        return None
    d = res.detections[0].location_data.relative_bounding_box
    h, w = arr.shape[:2]
    x1 = int(d.xmin * w)
    y1 = int(d.ymin * h)
    x2 = x1 + int(d.width * w)
    y2 = y1 + int(d.height * h)
    return x1, y1, x2, y2

def compute_scale(w_bg, h_bg, w_src, h_src) -> float:
    return ((w_bg / w_src) + (h_bg / h_src)) / 2

def compute_offset(bbox_bg, bbox_src, scale) -> tuple[int,int]:
    x1, y1, x2, y2 = bbox_bg
    bg_cx = x1 + (x2 - x1)//2
    bg_cy = y1 + (y2 - y1)//2
    sx1, sy1, sx2, sy2 = bbox_src
    src_cx = int((sx1 + (sx2 - sx1)//2) * scale)
    src_cy = int((sy1 + (sy2 - sy1)//2) * scale)
    return bg_cx - src_cx, bg_cy - src_cy

def paste_with_alpha(bg: np.ndarray, src: np.ndarray, offset: tuple[int,int]) -> Image.Image:
    res = bg.copy()
    x, y = offset
    h, w = src.shape[:2]
    x1, y1 = max(x,0), max(y,0)
    x2 = min(x+w, bg.shape[1])
    y2 = min(y+h, bg.shape[0])
    if x1>=x2 or y1>=y2:
        return Image.fromarray(res)
    cs = src[y1-y:y2-y, x1-x:x2-x]
    cd = res[y1:y2, x1:x2]
    mask = cs[...,3] > 0
    if cd.shape[2] == 3:
        cd[mask] = cs[mask][...,:3]
    else:
        cd[mask] = cs[mask]
    res[y1:y2, x1:x2] = cd
    return Image.fromarray(res)

def overlay_source(background: Image.Image, source: Image.Image):
    # 1) detect bboxes
    bbox_bg  = get_face_bbox(background)
    bbox_src = get_face_bbox(source)
    if bbox_bg is None:
        return None, "❌ No face in background."
    if bbox_src is None:
        return None, "❌ No face in source."

    # 2) compute scale & resize source
    w_bg, h_bg = bbox_bg[2]-bbox_bg[0], bbox_bg[3]-bbox_bg[1]
    w_src, h_src = bbox_src[2]-bbox_src[0], bbox_src[3]-bbox_src[1]
    scale = compute_scale(w_bg, h_bg, w_src, h_src)
    src_scaled = source.resize(
        (int(source.width*scale), int(source.height*scale)),
        Image.Resampling.LANCZOS
    )

    # 3) compute offset
    offset = compute_offset(bbox_bg, bbox_src, scale)

    # 4) baldhead background
    bg_bald = inference(background)

    # 5) extract hair-only from source
    hair_only = extract_hair(src_scaled)

    # 6) paste onto bald background
    result = paste_with_alpha(
        np.array(bg_bald.convert("RGBA")),
        np.array(hair_only),
        offset
    )
    return result