""" 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