Spaces:
Running
Running
File size: 2,685 Bytes
acb9f1e a068458 acb9f1e d5e1b6d acb9f1e d5e1b6d acb9f1e | 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 | import cv2
import numpy as np
from PIL import Image
def pil_to_bgr(pil_img: Image.Image) -> np.ndarray:
"""Convert PIL image to OpenCV BGR. Handles RGB, RGBA, palette, grayscale."""
pil_img = pil_img.convert("RGB") # always normalise to 3-channel RGB
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
def bgr_to_pil(bgr_img: np.ndarray) -> Image.Image:
"""Convert OpenCV BGR numpy array to PIL RGB image."""
return Image.fromarray(cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB))
def resize_to_max(image: np.ndarray, max_size: int = 2048) -> np.ndarray:
"""Downscale image so its longest side does not exceed max_size."""
h, w = image.shape[:2]
if max(h, w) <= max_size:
return image
scale = max_size / max(h, w)
return cv2.resize(image, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_LANCZOS4)
def apply_color_correction(source: np.ndarray, target: np.ndarray, mask: np.ndarray) -> np.ndarray:
"""
Shift source pixel statistics (mean/std per LAB channel) inside the masked
region to match those of the target, improving blending realism.
"""
source_lab = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype(float)
target_lab = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype(float)
mask_bool = mask > 128
for ch in range(3):
src_vals = source_lab[:, :, ch][mask_bool]
tgt_vals = target_lab[:, :, ch][mask_bool]
src_mean, src_std = src_vals.mean(), src_vals.std()
tgt_mean, tgt_std = tgt_vals.mean(), tgt_vals.std()
if src_std > 1e-6:
source_lab[:, :, ch][mask_bool] = (
(src_vals - src_mean) * (tgt_std / src_std) + tgt_mean
)
source_lab = np.clip(source_lab, 0, 255).astype(np.uint8)
return cv2.cvtColor(source_lab, cv2.COLOR_LAB2BGR)
def feather_mask(mask: np.ndarray, blur_radius: int = 15) -> np.ndarray:
"""Apply Gaussian blur to soften mask edges."""
if blur_radius % 2 == 0:
blur_radius += 1
return cv2.GaussianBlur(mask, (blur_radius, blur_radius), 0)
def alpha_blend(foreground: np.ndarray, background: np.ndarray, mask: np.ndarray) -> np.ndarray:
"""
Blend foreground onto background using a soft mask.
Args:
foreground: BGR image (same size as background).
background: BGR image.
mask: Single-channel uint8 mask (0-255).
Returns:
Blended BGR image.
"""
alpha = mask.astype(float) / 255.0
alpha_3ch = np.stack([alpha] * 3, axis=-1)
blended = (foreground.astype(float) * alpha_3ch +
background.astype(float) * (1.0 - alpha_3ch))
return np.clip(blended, 0, 255).astype(np.uint8)
|