Spaces:
Running on Zero
Running on Zero
File size: 5,688 Bytes
86e3fda | 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 | """
Blur Engine - Applies various blur effects using segmentation masks
Supports Gaussian blur, pixelation, and solid color overlay
"""
import cv2
import numpy as np
from typing import Optional
class BlurEngine:
"""Applies blur effects to images using segmentation masks"""
def __init__(self, config):
self.config = config
def apply_blur(
self,
frame: np.ndarray,
mask: np.ndarray,
blur_type: Optional[str] = None,
blur_strength: Optional[int] = None,
edge_feather: Optional[int] = None,
) -> np.ndarray:
"""
Apply blur to masked region of frame
Args:
frame: BGR image (H, W, 3) uint8
mask: Binary mask (H, W) uint8, 255 = blur region
blur_type: "gaussian", "pixelate", or "black"
blur_strength: Kernel size for Gaussian (must be odd)
edge_feather: Edge softening kernel (must be odd)
Returns:
Blurred frame (H, W, 3) uint8
"""
blur_type = blur_type or self.config.blur.blur_type
blur_strength = blur_strength or self.config.blur.blur_strength
edge_feather = edge_feather or self.config.blur.edge_feather
# Ensure odd kernel sizes
blur_strength = blur_strength | 1
edge_feather = edge_feather | 1
# Skip if no mask
if mask is None or mask.max() == 0:
return frame.copy()
# Resize mask to match frame if needed
if mask.shape[:2] != frame.shape[:2]:
mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]),
interpolation=cv2.INTER_LINEAR)
mask = (mask > 127).astype(np.uint8) * 255
# Generate the blurred/modified version
if blur_type == "gaussian":
modified = self._gaussian_blur(frame, blur_strength)
elif blur_type == "pixelate":
modified = self._pixelate(frame, self.config.blur.pixelate_size)
elif blur_type == "black":
modified = np.zeros_like(frame)
else:
modified = self._gaussian_blur(frame, blur_strength)
# Apply edge feathering for smooth transitions
mask_soft = self._feather_mask(mask, edge_feather)
# Composite: blend original and modified using soft mask
result = self._composite(frame, modified, mask_soft)
return result
def _gaussian_blur(self, frame: np.ndarray, kernel_size: int) -> np.ndarray:
"""Apply Gaussian blur to entire frame"""
return cv2.GaussianBlur(frame, (kernel_size, kernel_size), 0)
def _pixelate(self, frame: np.ndarray, block_size: int) -> np.ndarray:
"""Apply pixelation effect"""
h, w = frame.shape[:2]
small = cv2.resize(frame, (w // block_size, h // block_size),
interpolation=cv2.INTER_LINEAR)
pixelated = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
return pixelated
def _feather_mask(self, mask: np.ndarray, kernel_size: int) -> np.ndarray:
"""
Create soft-edged mask for smooth blur transitions
Returns:
Float mask (H, W) in range [0, 1]
"""
# Optional: dilate slightly to ensure full coverage
dilate_kernel = np.ones((3, 3), np.uint8)
mask_dilated = cv2.dilate(mask, dilate_kernel, iterations=1)
# Apply Gaussian blur to create soft edges
mask_soft = cv2.GaussianBlur(
mask_dilated.astype(np.float32),
(kernel_size, kernel_size),
0
)
# Normalize to [0, 1]
mask_max = mask_soft.max()
if mask_max > 0:
mask_soft = mask_soft / mask_max
return mask_soft
def _composite(
self,
original: np.ndarray,
modified: np.ndarray,
mask_soft: np.ndarray
) -> np.ndarray:
"""
Alpha-blend original and modified frames using soft mask
result = original * (1 - mask) + modified * mask
"""
# Expand mask to 3 channels
mask_3ch = np.stack([mask_soft] * 3, axis=-1)
# Blend
result = (
original.astype(np.float32) * (1.0 - mask_3ch) +
modified.astype(np.float32) * mask_3ch
)
return np.clip(result, 0, 255).astype(np.uint8)
def visualize_mask(
self,
frame: np.ndarray,
mask: np.ndarray,
color: tuple = (0, 255, 0),
alpha: float = 0.4
) -> np.ndarray:
"""
Overlay colored mask on frame for visualization
Args:
frame: BGR image
mask: Binary mask (H, W) uint8
color: BGR color tuple
alpha: Overlay transparency
Returns:
Visualization image
"""
vis = frame.copy()
if mask is None or mask.max() == 0:
return vis
# Resize mask if needed
if mask.shape[:2] != frame.shape[:2]:
mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]))
# Create colored overlay
overlay = np.zeros_like(frame)
overlay[mask > 127] = color
# Blend
vis = cv2.addWeighted(vis, 1.0, overlay, alpha, 0)
# Draw contours
contours, _ = cv2.findContours(
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
cv2.drawContours(vis, contours, -1, color, 2)
return vis
|