54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Basic retouching tools: healing / inpainting and skin softening."""
|
||
from __future__ import annotations
|
||
|
||
from typing import Tuple
|
||
|
||
import numpy as np
|
||
from PIL import Image
|
||
|
||
|
||
def heal_spot(
|
||
img: Image.Image,
|
||
center: Tuple[int, int],
|
||
radius: int,
|
||
) -> Image.Image:
|
||
"""Remove blemish at *center* using OpenCV Telea inpainting."""
|
||
import cv2
|
||
|
||
arr = np.asarray(img.convert("RGB"))
|
||
bgr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
|
||
|
||
mask = np.zeros(arr.shape[:2], dtype=np.uint8)
|
||
cv2.circle(mask, center, radius, 255, -1)
|
||
|
||
result = cv2.inpaint(bgr, mask, inpaintRadius=radius * 2, flags=cv2.INPAINT_TELEA)
|
||
result = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
|
||
return Image.fromarray(result)
|
||
|
||
|
||
def soften_skin(
|
||
rgb_float01: np.ndarray,
|
||
mask: np.ndarray,
|
||
strength: float,
|
||
) -> np.ndarray:
|
||
"""Edge-preserving skin softening (bilateral filter) within *mask* area.
|
||
|
||
*strength*: 0..100. 0 = no effect.
|
||
*mask*: float01 (H, W) – typically the person/face mask.
|
||
"""
|
||
if strength <= 0:
|
||
return rgb_float01
|
||
import cv2
|
||
|
||
img8 = np.clip(rgb_float01 * 255, 0, 255).astype(np.uint8)
|
||
|
||
d = max(3, int(strength / 10))
|
||
sigma_color = 30 + strength * 0.7
|
||
sigma_space = 30 + strength * 0.7
|
||
smoothed = cv2.bilateralFilter(img8, d, sigma_color, sigma_space)
|
||
|
||
smoothed_f = smoothed.astype(np.float32) / 255.0
|
||
alpha = np.clip(mask * min(strength / 100.0, 1.0), 0.0, 1.0)[..., None]
|
||
result = rgb_float01 * (1.0 - alpha) + smoothed_f * alpha
|
||
return np.clip(result, 0.0, 1.0).astype(np.float32)
|