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