|
|
import numpy as np |
|
|
from PIL import Image, ImageFilter |
|
|
import torch |
|
|
from transformers import AutoImageProcessor, AutoModelForSemanticSegmentation, AutoModelForDepthEstimation |
|
|
from scipy.ndimage import gaussian_filter |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
segmentation_model = None |
|
|
segmentation_processor = None |
|
|
depth_model = None |
|
|
depth_processor = None |
|
|
device = None |
|
|
|
|
|
def load_models(): |
|
|
"""Load all required models at startup""" |
|
|
global segmentation_model, segmentation_processor, depth_model, depth_processor, device |
|
|
|
|
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
print(f"Using device: {device}") |
|
|
|
|
|
|
|
|
print("Loading segmentation model...") |
|
|
seg_model_id = "nvidia/segformer-b0-finetuned-ade-512-512" |
|
|
segmentation_processor = AutoImageProcessor.from_pretrained(seg_model_id) |
|
|
segmentation_model = AutoModelForSemanticSegmentation.from_pretrained(seg_model_id) |
|
|
segmentation_model.eval() |
|
|
segmentation_model.to(device) |
|
|
|
|
|
|
|
|
print("Loading depth estimation model...") |
|
|
depth_model_id = "depth-anything/Depth-Anything-V2-Base-hf" |
|
|
depth_processor = AutoImageProcessor.from_pretrained(depth_model_id) |
|
|
depth_model = AutoModelForDepthEstimation.from_pretrained(depth_model_id) |
|
|
depth_model.eval() |
|
|
depth_model.to(device) |
|
|
|
|
|
print("Models loaded successfully!") |
|
|
|
|
|
def get_person_mask(image): |
|
|
"""Extract person mask from image using semantic segmentation""" |
|
|
|
|
|
img_512 = image.resize((512, 512), Image.BILINEAR) |
|
|
|
|
|
|
|
|
inputs = segmentation_processor(images=img_512, return_tensors="pt").to(device) |
|
|
with torch.no_grad(): |
|
|
outputs = segmentation_model(**inputs) |
|
|
logits = torch.nn.functional.interpolate( |
|
|
outputs.logits, size=(512, 512), mode="bilinear", align_corners=False |
|
|
) |
|
|
pred = logits.argmax(dim=1)[0].cpu().numpy() |
|
|
|
|
|
|
|
|
id2label = segmentation_model.config.id2label |
|
|
label2id = {v.lower(): int(k) for k, v in id2label.items()} |
|
|
person_key = next((k for k in label2id.keys() if k in ["person", "people", "human"]), None) |
|
|
|
|
|
if person_key is None: |
|
|
|
|
|
return Image.new("L", (512, 512), 0) |
|
|
|
|
|
person_id = label2id[person_key] |
|
|
mask = (pred == person_id).astype(np.uint8) * 255 |
|
|
|
|
|
return Image.fromarray(mask, mode="L") |
|
|
|
|
|
def gaussian_blur_effect(image, blur_radius=15): |
|
|
"""Apply Gaussian blur to background, keep person sharp""" |
|
|
if image is None: |
|
|
return None |
|
|
|
|
|
|
|
|
if image.mode != "RGB": |
|
|
image = image.convert("RGB") |
|
|
|
|
|
|
|
|
img_512 = image.resize((512, 512), Image.BILINEAR) |
|
|
|
|
|
|
|
|
mask_img = get_person_mask(img_512) |
|
|
|
|
|
|
|
|
blurred_img = img_512.filter(ImageFilter.GaussianBlur(radius=blur_radius)) |
|
|
|
|
|
|
|
|
input_array = np.array(img_512) |
|
|
blurred_array = np.array(blurred_img) |
|
|
mask_array = np.array(mask_img) / 255.0 |
|
|
mask_3ch = np.stack([mask_array] * 3, axis=-1) |
|
|
|
|
|
output_array = (input_array * mask_3ch + blurred_array * (1 - mask_3ch)).astype(np.uint8) |
|
|
output_img = Image.fromarray(output_array) |
|
|
|
|
|
return output_img |
|
|
|
|
|
def get_depth_map(image): |
|
|
"""Estimate depth map from image""" |
|
|
|
|
|
img_512 = image.resize((512, 512), Image.BILINEAR) |
|
|
|
|
|
|
|
|
inputs = depth_processor(images=img_512, return_tensors="pt").to(device) |
|
|
with torch.no_grad(): |
|
|
outputs = depth_model(**inputs) |
|
|
predicted_depth = outputs.predicted_depth |
|
|
|
|
|
|
|
|
prediction = torch.nn.functional.interpolate( |
|
|
predicted_depth.unsqueeze(1), |
|
|
size=(512, 512), |
|
|
mode="bicubic", |
|
|
align_corners=False, |
|
|
) |
|
|
|
|
|
depth_map = prediction.squeeze().cpu().numpy() |
|
|
return depth_map |
|
|
|
|
|
def lens_blur_effect(image, max_blur=15, focus_threshold=5.0): |
|
|
"""Apply depth-based lens blur (foreground sharp, background blurred)""" |
|
|
if image is None: |
|
|
return None |
|
|
|
|
|
|
|
|
if image.mode != "RGB": |
|
|
image = image.convert("RGB") |
|
|
|
|
|
|
|
|
img_512 = image.resize((512, 512), Image.BILINEAR) |
|
|
|
|
|
|
|
|
depth_map = get_depth_map(img_512) |
|
|
|
|
|
|
|
|
depth_normalized = (depth_map.max() - depth_map) / (depth_map.max() - depth_map.min()) |
|
|
depth_normalized = depth_normalized * max_blur |
|
|
|
|
|
|
|
|
blur_map = np.zeros_like(depth_normalized) |
|
|
close_mask = depth_normalized <= focus_threshold |
|
|
blur_map[close_mask] = 0.0 |
|
|
|
|
|
far_mask = depth_normalized > focus_threshold |
|
|
blur_map[far_mask] = ((depth_normalized[far_mask] - focus_threshold) / (max_blur - focus_threshold)) * max_blur |
|
|
|
|
|
|
|
|
img_array = np.array(img_512).astype(np.float32) |
|
|
output_array = img_array.copy() |
|
|
|
|
|
num_blur_levels = 20 |
|
|
for level in range(1, num_blur_levels + 1): |
|
|
sigma_min = (level - 1) * max_blur / num_blur_levels |
|
|
sigma_max = level * max_blur / num_blur_levels |
|
|
sigma_avg = (sigma_min + sigma_max) / 2.0 |
|
|
|
|
|
mask = ((blur_map >= sigma_min) & (blur_map < sigma_max)).astype(np.float32) |
|
|
|
|
|
if mask.sum() > 0 and sigma_avg > 0.1: |
|
|
blurred = np.zeros_like(img_array) |
|
|
for c in range(3): |
|
|
blurred[:, :, c] = gaussian_filter(img_array[:, :, c], sigma=sigma_avg) |
|
|
|
|
|
mask_3ch = np.stack([mask] * 3, axis=-1) |
|
|
output_array = output_array * (1 - mask_3ch) + blurred * mask_3ch |
|
|
|
|
|
output_array = np.clip(output_array, 0, 255).astype(np.uint8) |
|
|
output_img = Image.fromarray(output_array) |
|
|
|
|
|
return output_img |
|
|
|
|
|
|
|
|
load_models() |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Image Blur Effects Demo") as demo: |
|
|
gr.Markdown(""" |
|
|
# 🎨 Image Blur Effects Demo |
|
|
|
|
|
Upload an image to apply **Gaussian Blur** or **Lens Blur** effects. |
|
|
|
|
|
- **Gaussian Blur**: Detects people and blurs the background, keeping the person sharp. |
|
|
- **Lens Blur**: Uses depth estimation to simulate camera lens bokeh effect (foreground sharp, background blurred). |
|
|
""") |
|
|
|
|
|
with gr.Tab("Gaussian Blur"): |
|
|
gr.Markdown("### Background blur with person detection") |
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gaussian_input = gr.Image(type="pil", label="Input Image") |
|
|
gaussian_radius = gr.Slider( |
|
|
minimum=5, maximum=30, value=15, step=1, |
|
|
label="Blur Radius (σ)" |
|
|
) |
|
|
gaussian_btn = gr.Button("Apply Gaussian Blur", variant="primary") |
|
|
with gr.Column(): |
|
|
gaussian_output = gr.Image(type="pil", label="Output Image") |
|
|
|
|
|
gaussian_btn.click( |
|
|
fn=gaussian_blur_effect, |
|
|
inputs=[gaussian_input, gaussian_radius], |
|
|
outputs=gaussian_output |
|
|
) |
|
|
|
|
|
gr.Examples( |
|
|
examples=[["self.jpg"], ["self-pic.jpg"]], |
|
|
inputs=gaussian_input, |
|
|
label="Example Images" |
|
|
) |
|
|
|
|
|
with gr.Tab("Lens Blur (Depth-Based)"): |
|
|
gr.Markdown("### Depth-based bokeh effect simulation") |
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
lens_input = gr.Image(type="pil", label="Input Image") |
|
|
lens_max_blur = gr.Slider( |
|
|
minimum=5, maximum=25, value=15, step=1, |
|
|
label="Max Blur Intensity" |
|
|
) |
|
|
lens_focus = gr.Slider( |
|
|
minimum=0, maximum=10, value=5.0, step=0.5, |
|
|
label="Focus Threshold (lower = more blur)" |
|
|
) |
|
|
lens_btn = gr.Button("Apply Lens Blur", variant="primary") |
|
|
with gr.Column(): |
|
|
lens_output = gr.Image(type="pil", label="Output Image") |
|
|
|
|
|
lens_btn.click( |
|
|
fn=lens_blur_effect, |
|
|
inputs=[lens_input, lens_max_blur, lens_focus], |
|
|
outputs=lens_output |
|
|
) |
|
|
|
|
|
gr.Examples( |
|
|
examples=[["self.jpg"], ["self-pic.jpg"]], |
|
|
inputs=lens_input, |
|
|
label="Example Images" |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
**Technical Details:** |
|
|
- Segmentation: NVIDIA SegFormer (ADE20K) |
|
|
- Depth Estimation: Depth Anything V2 |
|
|
- All images resized to 512×512 for processing |
|
|
""") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|
|
|
|