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