Spaces:
Runtime error
Runtime error
| 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 | |