File size: 6,910 Bytes
cc7361e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c092fae
 
 
cc7361e
c092fae
 
 
cc7361e
c092fae
 
 
 
 
 
 
 
 
cc7361e
 
c092fae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc7361e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c092fae
cc7361e
 
 
 
 
 
c092fae
cc7361e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c092fae
cc7361e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import gradio as gr
import torch
import numpy as np
from PIL import Image
from scipy.ndimage import gaussian_filter
from transformers import pipeline

def preprocess_image(image):
    """Resize and convert image to PIL format if needed."""
    if isinstance(image, np.ndarray):
        image = Image.fromarray(image)
    
    # Resize to 512x512 while maintaining aspect ratio
    image = image.resize((512, 512))
    return image

def segment_image(image, model_name="yolov8n-seg"):
    """
    Perform instance segmentation on the input image using YOLO segmentation model.
    
    Args:
        image (PIL.Image): Input image
        model_name (str): Name of the YOLO segmentation model
    
    Returns:
        numpy.ndarray: Segmentation mask with instance segmentation
    """
    from ultralytics import YOLO
    import numpy as np
    import torch
    
    # Load the YOLO segmentation model
    model = YOLO(model_name)
    
    # Run inference
    results = model(image)
    
    # Create a blank mask
    mask = np.zeros(image.size[::-1], dtype=np.uint8)
    
    # Process each detected object
    for result in results:
        # Get masks for all detected objects
        masks = result.masks
        
        if masks is not None:
            # Convert masks to numpy and add to the overall mask
            for single_mask in masks:
                # Convert mask to numpy and resize if needed
                mask_array = single_mask.data.cpu().numpy().squeeze()
                mask_array = (mask_array > 0.5).astype(np.uint8)
                
                # If mask size doesn't match image, resize
                if mask_array.shape != mask.shape:
                    from PIL import Image
                    mask_array = np.array(
                        Image.fromarray(mask_array).resize(
                            image.size[::-1], 
                            Image.NEAREST
                        )
                    )
                
                # Add this mask to the overall mask
                mask = np.maximum(mask, mask_array)
    
    return mask

def process_image(image, blur_type, sigma=15):
    """Process image based on blur type."""
    # Preprocess image
    pil_image = preprocess_image(image)
    
    # Apply appropriate blur
    if blur_type == "Gaussian Background Blur":
        # Get segmentation mask
        segmentation_mask = segment_image(pil_image)
        
        # Convert to 3-channel mask
        mask_3d = np.stack([segmentation_mask] * 3, axis=2)
        
        # Apply Gaussian blur
        image_array = np.array(pil_image)
        blurred = np.zeros_like(image_array)
        for channel in range(3):
            blurred[:, :, channel] = gaussian_filter(image_array[:, :, channel], sigma=sigma)
        
        # Combine original and blurred images
        result = image_array * mask_3d + blurred * (1 - mask_3d)
        result = Image.fromarray(result.astype(np.uint8))
    
    elif blur_type == "Depth-Aware Lens Blur":
        result = apply_depth_aware_blur(pil_image, max_sigma=sigma)
    else:
        result = pil_image
    
    return result

def apply_gaussian_blur(image, sigma=15):
    """Apply Gaussian blur to the background."""
    # Convert image to numpy array
    image_array = np.array(image)
    
    # Create segmentation mask (assuming we want to keep the foreground)
    segmentation_mask = segment_image(image)
    
    # Choose a prominent object class (e.g., person with ID 24 in Cityscapes)
    foreground_mask = (segmentation_mask == 24).astype(np.uint8)
    
    # Prepare blurred version
    blurred = np.zeros_like(image_array)
    for channel in range(3):
        blurred[:, :, channel] = gaussian_filter(image_array[:, :, channel], sigma=sigma)
    
    # Combine original and blurred images based on mask
    mask_3d = np.stack([foreground_mask] * 3, axis=2)
    result = image_array * mask_3d + blurred * (1 - mask_3d)
    
    return Image.fromarray(result.astype(np.uint8))

def estimate_depth(image, model_name="depth-anything/Depth-Anything-V2-Small-hf"):
    """Estimate depth of the image."""
    depth_estimator = pipeline(
        task="depth-estimation", 
        model=model_name,
        torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
    )
    
    depth_output = depth_estimator(image)
    depth_map = np.array(depth_output["depth"])
    
    # Normalize depth map
    depth_map = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min())
    
    return depth_map

def apply_depth_aware_blur(image, max_sigma=10, min_sigma=0):
    """Apply depth-aware blur to the image (REVERSED version)."""
    # Estimate depth
    depth_map = estimate_depth(image)
    
    image_array = np.array(image)
    blurred = np.zeros_like(image_array, dtype=np.float32)
    
    # REVERSED: Now we use depth_map directly (no inversion) so farther objects get more blur
    sigmas = np.interp(depth_map, [0, 1], [min_sigma, max_sigma])
    
    # Precompute blurred layers
    blur_stack = {}
    for sigma in np.unique(sigmas):
        if sigma > 0:
            blurred_layer = np.zeros_like(image_array, dtype=np.float32)
            for channel in range(3):
                blurred_layer[:, :, channel] = gaussian_filter(
                    image_array[:, :, channel].astype(np.float32),
                    sigma=sigma
                )
            blur_stack[sigma] = blurred_layer
    
    # Blend based on depth
    for sigma in np.unique(sigmas):
        if sigma > 0:
            mask = (sigmas == sigma)
            mask_3d = np.stack([mask] * 3, axis=2)
            blurred += mask_3d * blur_stack[sigma]
        else:
            mask = (sigmas == 0)
            mask_3d = np.stack([mask] * 3, axis=2)
            blurred += mask_3d * image_array
    
    return Image.fromarray(blurred.astype(np.uint8))



# Gradio Interface
def create_blur_app():
    with gr.Blocks() as demo:
        gr.Markdown("# Image Blur Effects")
        
        with gr.Row():
            input_image = gr.Image(label="Input Image", type="pil")
            output_image = gr.Image(label="Processed Image")
        
        with gr.Row():
            blur_type = gr.Dropdown(
                choices=[
                    "Gaussian Background Blur", 
                    "Depth-Aware Lens Blur"
                ], 
                label="Blur Type"
            )
            sigma = gr.Slider(
                minimum=0, 
                maximum=30, 
                value=15, 
                label="Blur Intensity"
            )
        
        process_btn = gr.Button("Apply Blur Effect")
        
        process_btn.click(
            fn=process_image, 
            inputs=[input_image, blur_type, sigma], 
            outputs=output_image
        )
    
    return demo

# Launch the app
if __name__ == "__main__":
    demo = create_blur_app()
    demo.launch()