nrtk-albumentations 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nrtk-albumentations might be problematic. Click here for more details.

Files changed (62) hide show
  1. albumentations/__init__.py +21 -0
  2. albumentations/augmentations/__init__.py +23 -0
  3. albumentations/augmentations/blur/__init__.py +0 -0
  4. albumentations/augmentations/blur/functional.py +438 -0
  5. albumentations/augmentations/blur/transforms.py +1633 -0
  6. albumentations/augmentations/crops/__init__.py +0 -0
  7. albumentations/augmentations/crops/functional.py +494 -0
  8. albumentations/augmentations/crops/transforms.py +3647 -0
  9. albumentations/augmentations/dropout/__init__.py +0 -0
  10. albumentations/augmentations/dropout/channel_dropout.py +134 -0
  11. albumentations/augmentations/dropout/coarse_dropout.py +567 -0
  12. albumentations/augmentations/dropout/functional.py +1017 -0
  13. albumentations/augmentations/dropout/grid_dropout.py +166 -0
  14. albumentations/augmentations/dropout/mask_dropout.py +274 -0
  15. albumentations/augmentations/dropout/transforms.py +461 -0
  16. albumentations/augmentations/dropout/xy_masking.py +186 -0
  17. albumentations/augmentations/geometric/__init__.py +0 -0
  18. albumentations/augmentations/geometric/distortion.py +1238 -0
  19. albumentations/augmentations/geometric/flip.py +752 -0
  20. albumentations/augmentations/geometric/functional.py +4151 -0
  21. albumentations/augmentations/geometric/pad.py +676 -0
  22. albumentations/augmentations/geometric/resize.py +956 -0
  23. albumentations/augmentations/geometric/rotate.py +864 -0
  24. albumentations/augmentations/geometric/transforms.py +1962 -0
  25. albumentations/augmentations/mixing/__init__.py +0 -0
  26. albumentations/augmentations/mixing/domain_adaptation.py +787 -0
  27. albumentations/augmentations/mixing/domain_adaptation_functional.py +453 -0
  28. albumentations/augmentations/mixing/functional.py +878 -0
  29. albumentations/augmentations/mixing/transforms.py +832 -0
  30. albumentations/augmentations/other/__init__.py +0 -0
  31. albumentations/augmentations/other/lambda_transform.py +180 -0
  32. albumentations/augmentations/other/type_transform.py +261 -0
  33. albumentations/augmentations/pixel/__init__.py +0 -0
  34. albumentations/augmentations/pixel/functional.py +4226 -0
  35. albumentations/augmentations/pixel/transforms.py +7556 -0
  36. albumentations/augmentations/spectrogram/__init__.py +0 -0
  37. albumentations/augmentations/spectrogram/transform.py +220 -0
  38. albumentations/augmentations/text/__init__.py +0 -0
  39. albumentations/augmentations/text/functional.py +272 -0
  40. albumentations/augmentations/text/transforms.py +299 -0
  41. albumentations/augmentations/transforms3d/__init__.py +0 -0
  42. albumentations/augmentations/transforms3d/functional.py +393 -0
  43. albumentations/augmentations/transforms3d/transforms.py +1422 -0
  44. albumentations/augmentations/utils.py +249 -0
  45. albumentations/core/__init__.py +0 -0
  46. albumentations/core/bbox_utils.py +920 -0
  47. albumentations/core/composition.py +1885 -0
  48. albumentations/core/hub_mixin.py +299 -0
  49. albumentations/core/keypoints_utils.py +521 -0
  50. albumentations/core/label_manager.py +339 -0
  51. albumentations/core/pydantic.py +239 -0
  52. albumentations/core/serialization.py +352 -0
  53. albumentations/core/transforms_interface.py +976 -0
  54. albumentations/core/type_definitions.py +127 -0
  55. albumentations/core/utils.py +605 -0
  56. albumentations/core/validation.py +129 -0
  57. albumentations/pytorch/__init__.py +1 -0
  58. albumentations/pytorch/transforms.py +189 -0
  59. nrtk_albumentations-2.1.0.dist-info/METADATA +196 -0
  60. nrtk_albumentations-2.1.0.dist-info/RECORD +62 -0
  61. nrtk_albumentations-2.1.0.dist-info/WHEEL +4 -0
  62. nrtk_albumentations-2.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1633 @@
1
+ """Transform classes for applying various blur operations to images.
2
+
3
+ This module contains transform classes that implement different blur effects including
4
+ standard blur, motion blur, median blur, Gaussian blur, glass blur, advanced blur, defocus,
5
+ and zoom blur. These transforms are designed to work within the albumentations pipeline
6
+ and support parameters for controlling the intensity and properties of the blur effects.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Annotated, Any, Literal, cast
12
+
13
+ import numpy as np
14
+ from pydantic import (
15
+ AfterValidator,
16
+ Field,
17
+ ValidationInfo,
18
+ field_validator,
19
+ model_validator,
20
+ )
21
+ from typing_extensions import Self
22
+
23
+ from albumentations.augmentations.pixel import functional as fpixel
24
+ from albumentations.core.pydantic import (
25
+ NonNegativeFloatRangeType,
26
+ OnePlusFloatRangeType,
27
+ OnePlusIntRangeType,
28
+ SymmetricRangeType,
29
+ check_range_bounds,
30
+ convert_to_0plus_range,
31
+ nondecreasing,
32
+ process_non_negative_range,
33
+ )
34
+ from albumentations.core.transforms_interface import (
35
+ BaseTransformInitSchema,
36
+ ImageOnlyTransform,
37
+ )
38
+ from albumentations.core.utils import to_tuple
39
+
40
+ from . import functional as fblur
41
+
42
+ __all__ = [
43
+ "AdvancedBlur",
44
+ "Blur",
45
+ "Defocus",
46
+ "GaussianBlur",
47
+ "GlassBlur",
48
+ "MedianBlur",
49
+ "MotionBlur",
50
+ "ZoomBlur",
51
+ ]
52
+
53
+
54
+ HALF = 0.5
55
+ TWO = 2
56
+
57
+
58
+ class BlurInitSchema(BaseTransformInitSchema):
59
+ blur_limit: tuple[int, int] | int
60
+
61
+ @field_validator("blur_limit")
62
+ @classmethod
63
+ def process_blur(cls, value: tuple[int, int] | int, info: ValidationInfo) -> tuple[int, int]:
64
+ return fblur.process_blur_limit(value, info, min_value=3)
65
+
66
+
67
+ class Blur(ImageOnlyTransform):
68
+ """Apply uniform box blur to the input image using a randomly sized square kernel.
69
+
70
+ This transform uses OpenCV's cv2.blur function, which performs a simple box filter blur.
71
+ The size of the blur kernel is randomly selected for each application, allowing for
72
+ varying degrees of blur intensity.
73
+
74
+ Args:
75
+ blur_limit (tuple[int, int] | int): Controls the range of the blur kernel size.
76
+ - If a single int is provided, the kernel size will be randomly chosen
77
+ between 3 and that value.
78
+ - If a tuple of two ints is provided, it defines the inclusive range
79
+ of possible kernel sizes.
80
+ The kernel size must be odd and greater than or equal to 3.
81
+ Larger kernel sizes produce stronger blur effects.
82
+ Default: (3, 7)
83
+
84
+ p (float): Probability of applying the transform. Default: 0.5
85
+
86
+ Notes:
87
+ - The blur kernel is always square (same width and height).
88
+ - Only odd kernel sizes are used to ensure the blur has a clear center pixel.
89
+ - Box blur is faster than Gaussian blur but may produce less natural results.
90
+ - This blur method averages all pixels under the kernel area, which can
91
+ reduce noise but also reduce image detail.
92
+
93
+ Targets:
94
+ image
95
+
96
+ Image types:
97
+ uint8, float32
98
+
99
+ Examples:
100
+ >>> import numpy as np
101
+ >>> import albumentations as A
102
+ >>> import cv2
103
+ >>>
104
+ >>> # Create a sample image for demonstration
105
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
106
+ >>> # Add some shapes to visualize blur effects
107
+ >>> cv2.rectangle(image, (50, 50), (250, 250), (255, 0, 0), -1) # Red square
108
+ >>> cv2.circle(image, (150, 150), 60, (0, 255, 0), -1) # Green circle
109
+ >>> cv2.line(image, (50, 150), (250, 150), (0, 0, 255), 5) # Blue line
110
+ >>>
111
+ >>> # Example 1: Basic usage with default parameters
112
+ >>> transform = A.Compose([
113
+ ... A.Blur(p=1.0) # Always apply with default blur_limit=(3, 7)
114
+ ... ])
115
+ >>>
116
+ >>> result = transform(image=image)
117
+ >>> blurred_image = result["image"]
118
+ >>> # The image will have a random blur with kernel size between 3 and 7
119
+ >>>
120
+ >>> # Example 2: Using a fixed blur kernel size
121
+ >>> fixed_transform = A.Compose([
122
+ ... A.Blur(blur_limit=5, p=1.0) # Always use kernel size 5x5
123
+ ... ])
124
+ >>>
125
+ >>> fixed_result = fixed_transform(image=image)
126
+ >>> fixed_blurred_image = fixed_result["image"]
127
+ >>> # The image will have a consistent 5x5 kernel blur
128
+ >>>
129
+ >>> # Example 3: Using a custom range for blur kernel sizes
130
+ >>> strong_transform = A.Compose([
131
+ ... A.Blur(blur_limit=(7, 13), p=1.0) # Use larger kernel for stronger blur
132
+ ... ])
133
+ >>>
134
+ >>> strong_result = strong_transform(image=image)
135
+ >>> strong_blurred = strong_result["image"]
136
+ >>> # The image will have a stronger blur with kernel size between 7 and 13
137
+ >>>
138
+ >>> # Example 4: As part of a pipeline with other transforms
139
+ >>> pipeline = A.Compose([
140
+ ... A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.7),
141
+ ... A.Blur(blur_limit=(3, 5), p=0.5), # 50% chance of applying blur
142
+ ... A.HorizontalFlip(p=0.5)
143
+ ... ])
144
+ >>>
145
+ >>> pipeline_result = pipeline(image=image)
146
+ >>> transformed_image = pipeline_result["image"]
147
+ >>> # The image may or may not be blurred depending on the random probability
148
+
149
+ """
150
+
151
+ class InitSchema(BlurInitSchema):
152
+ pass
153
+
154
+ def __init__(
155
+ self,
156
+ blur_limit: tuple[int, int] | int = (3, 7),
157
+ p: float = 0.5,
158
+ ):
159
+ super().__init__(p=p)
160
+ self.blur_limit = cast("tuple[int, int]", blur_limit)
161
+
162
+ def apply(self, img: np.ndarray, kernel: int, **params: Any) -> np.ndarray:
163
+ """Apply blur to the input image.
164
+
165
+ Args:
166
+ img (np.ndarray): Image to blur.
167
+ kernel (int): Size of the kernel for blur.
168
+ **params (Any): Additional parameters.
169
+
170
+ Returns:
171
+ np.ndarray: Blurred image.
172
+
173
+ """
174
+ return fblur.box_blur(img, kernel)
175
+
176
+ def get_params(self) -> dict[str, Any]:
177
+ """Get parameters for the transform.
178
+
179
+ Returns:
180
+ dict[str, Any]: Dictionary with parameters.
181
+
182
+ """
183
+ kernel = fblur.sample_odd_from_range(
184
+ self.py_random,
185
+ self.blur_limit[0],
186
+ self.blur_limit[1],
187
+ )
188
+ return {"kernel": kernel}
189
+
190
+
191
+ class MotionBlur(Blur):
192
+ """Apply motion blur to the input image using a directional kernel.
193
+
194
+ This transform simulates motion blur effects that occur during image capture,
195
+ such as camera shake or object movement. It creates a directional blur using
196
+ a line-shaped kernel with controllable angle, direction, and position.
197
+
198
+ Args:
199
+ blur_limit (int | tuple[int, int]): Maximum kernel size for blurring.
200
+ Should be in range [3, inf).
201
+ - If int: kernel size will be randomly chosen from [3, blur_limit]
202
+ - If tuple: kernel size will be randomly chosen from [min, max]
203
+ Larger values create stronger blur effects.
204
+ Default: (3, 7)
205
+
206
+ angle_range (tuple[float, float]): Range of possible angles in degrees.
207
+ Controls the rotation of the motion blur line:
208
+ - 0°: Horizontal motion blur →
209
+ - 45°: Diagonal motion blur ↗
210
+ - 90°: Vertical motion blur ↑
211
+ - 135°: Diagonal motion blur ↖
212
+ Default: (0, 360)
213
+
214
+ direction_range (tuple[float, float]): Range for motion bias.
215
+ Controls how the blur extends from the center:
216
+ - -1.0: Blur extends only backward (←)
217
+ - 0.0: Blur extends equally in both directions (←→)
218
+ - 1.0: Blur extends only forward (→)
219
+ For example, with angle=0:
220
+ - direction=-1.0: ←•
221
+ - direction=0.0: ←•→
222
+ - direction=1.0: •→
223
+ Default: (-1.0, 1.0)
224
+
225
+ allow_shifted (bool): Allow random kernel position shifts.
226
+ - If True: Kernel can be randomly offset from center
227
+ - If False: Kernel will always be centered
228
+ Default: True
229
+
230
+ p (float): Probability of applying the transform. Default: 0.5
231
+
232
+ Examples of angle vs direction:
233
+ 1. Horizontal motion (angle=0°):
234
+ - direction=0.0: ←•→ (symmetric blur)
235
+ - direction=1.0: •→ (forward blur)
236
+ - direction=-1.0: ←• (backward blur)
237
+
238
+ 2. Vertical motion (angle=90°):
239
+ - direction=0.0: ↑•↓ (symmetric blur)
240
+ - direction=1.0: •↑ (upward blur)
241
+ - direction=-1.0: ↓• (downward blur)
242
+
243
+ 3. Diagonal motion (angle=45°):
244
+ - direction=0.0: ↙•↗ (symmetric blur)
245
+ - direction=1.0: •↗ (forward diagonal blur)
246
+ - direction=-1.0: ↙• (backward diagonal blur)
247
+
248
+ Note:
249
+ - angle controls the orientation of the motion line
250
+ - direction controls the distribution of the blur along that line
251
+ - Together they can simulate various motion effects:
252
+ * Camera shake: Small angle range + direction near 0
253
+ * Object motion: Specific angle + direction=1.0
254
+ * Complex motion: Random angle + random direction
255
+
256
+ Examples:
257
+ >>> import numpy as np
258
+ >>> import albumentations as A
259
+ >>> import cv2
260
+ >>>
261
+ >>> # Create a sample image for demonstration
262
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
263
+ >>> # Add some shapes to visualize motion blur effects
264
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
265
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
266
+ >>> cv2.putText(image, "Motion Blur", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
267
+ >>>
268
+ >>> # Example 1: Horizontal camera shake (symmetric)
269
+ >>> horizontal_shake = A.Compose([
270
+ ... A.MotionBlur(
271
+ ... blur_limit=(10, 12), # Strong blur
272
+ ... angle_range=(-5, 5), # Near-horizontal motion (±5°)
273
+ ... direction_range=(0, 0), # Symmetric blur (equally in both directions)
274
+ ... p=1.0 # Always apply
275
+ ... )
276
+ ... ])
277
+ >>>
278
+ >>> horizontal_result = horizontal_shake(image=image)
279
+ >>> horizontal_blur = horizontal_result["image"]
280
+ >>> # The image will have a horizontal camera shake effect, blurring equally in both directions
281
+ >>>
282
+ >>> # Example 2: Object moving right (directional motion)
283
+ >>> rightward_motion = A.Compose([
284
+ ... A.MotionBlur(
285
+ ... blur_limit=(7, 9), # Medium blur
286
+ ... angle_range=(0, 0), # Exactly horizontal motion (0°)
287
+ ... direction_range=(0.8, 1.0), # Strong forward bias (mostly rightward)
288
+ ... p=1.0
289
+ ... )
290
+ ... ])
291
+ >>>
292
+ >>> rightward_result = rightward_motion(image=image)
293
+ >>> rightward_blur = rightward_result["image"]
294
+ >>> # The image will simulate an object moving rightward, with blur mostly to the right
295
+ >>>
296
+ >>> # Example 3: Object moving diagonally down-right
297
+ >>> diagonal_motion = A.Compose([
298
+ ... A.MotionBlur(
299
+ ... blur_limit=(9, 11), # Stronger blur
300
+ ... angle_range=(135, 135), # 135° motion (down-right diagonal)
301
+ ... direction_range=(0.7, 0.9), # Forward bias
302
+ ... p=1.0
303
+ ... )
304
+ ... ])
305
+ >>>
306
+ >>> diagonal_result = diagonal_motion(image=image)
307
+ >>> diagonal_blur = diagonal_result["image"]
308
+ >>> # The image will simulate diagonal motion down and to the right
309
+ >>>
310
+ >>> # Example 4: Vertical motion (up-down)
311
+ >>> vertical_motion = A.Compose([
312
+ ... A.MotionBlur(
313
+ ... blur_limit=9, # Fixed kernel size
314
+ ... angle_range=(90, 90), # Vertical motion (90°)
315
+ ... direction_range=(-0.2, 0.2), # Near-symmetric (slight bias)
316
+ ... p=1.0
317
+ ... )
318
+ ... ])
319
+ >>>
320
+ >>> vertical_result = vertical_motion(image=image)
321
+ >>> vertical_blur = vertical_result["image"]
322
+ >>> # The image will simulate vertical motion blur
323
+ >>>
324
+ >>> # Example 5: Random motion blur (can be in any direction)
325
+ >>> random_motion = A.Compose([
326
+ ... A.MotionBlur(
327
+ ... blur_limit=(5, 12), # Variable strength
328
+ ... angle_range=(0, 360), # Any angle
329
+ ... direction_range=(-1.0, 1.0), # Any direction bias
330
+ ... allow_shifted=True, # Allow kernel to be shifted from center
331
+ ... p=1.0
332
+ ... )
333
+ ... ])
334
+ >>>
335
+ >>> random_result = random_motion(image=image)
336
+ >>> random_blur = random_result["image"]
337
+ >>> # The image will have a random motion blur in any direction
338
+ >>>
339
+ >>> # Example 6: Multiple random parameters with kernel centered (not shifted)
340
+ >>> centered_motion = A.Compose([
341
+ ... A.MotionBlur(
342
+ ... blur_limit=(5, 9),
343
+ ... angle_range=(0, 360),
344
+ ... direction_range=(-1.0, 1.0),
345
+ ... allow_shifted=False, # Kernel will always be centered
346
+ ... p=1.0
347
+ ... )
348
+ ... ])
349
+ >>>
350
+ >>> centered_result = centered_motion(image=image)
351
+ >>> centered_blur = centered_result["image"]
352
+ >>> # The image will have motion blur with the kernel centered (not shifted)
353
+ >>>
354
+ >>> # Example 7: In a composition with other transforms
355
+ >>> pipeline = A.Compose([
356
+ ... A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.5),
357
+ ... A.MotionBlur( # 30% chance of applying motion blur
358
+ ... blur_limit=(3, 7),
359
+ ... angle_range=(0, 180), # Only horizontal to vertical
360
+ ... direction_range=(-0.5, 0.5), # Moderate direction bias
361
+ ... p=0.3
362
+ ... ),
363
+ ... A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=0.3)
364
+ ... ])
365
+ >>>
366
+ >>> pipeline_result = pipeline(image=image)
367
+ >>> transformed_image = pipeline_result["image"]
368
+ >>> # The image may have motion blur applied with 30% probability along with other effects
369
+
370
+ References:
371
+ - Motion blur fundamentals:
372
+ https://en.wikipedia.org/wiki/Motion_blur
373
+
374
+ - Directional blur kernels:
375
+ https://www.sciencedirect.com/topics/computer-science/directional-blur
376
+
377
+ - OpenCV filter2D (used for convolution):
378
+ https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga27c049795ce870216ddfb366086b5a04
379
+
380
+ - Research on motion blur simulation:
381
+ "Understanding and Evaluating Blind Deconvolution Algorithms" (CVPR 2009)
382
+ https://doi.org/10.1109/CVPR.2009.5206815
383
+
384
+ - Motion blur in photography:
385
+ "The Manual of Photography", Chapter 7: Motion in Photography
386
+ ISBN: 978-0240520377
387
+
388
+ - Kornia's implementation (similar approach):
389
+ https://kornia.readthedocs.io/en/latest/augmentation.html#kornia.augmentation.RandomMotionBlur
390
+
391
+ See Also:
392
+ - GaussianBlur: For uniform blur effects
393
+ - MedianBlur: For noise reduction while preserving edges
394
+ - RandomRain: Another motion-based effect
395
+ - Perspective: For geometric motion-like distortions
396
+
397
+ """
398
+
399
+ class InitSchema(BlurInitSchema):
400
+ allow_shifted: bool
401
+ angle_range: Annotated[
402
+ tuple[float, float],
403
+ AfterValidator(nondecreasing),
404
+ AfterValidator(check_range_bounds(0, 360)),
405
+ ]
406
+ direction_range: Annotated[
407
+ tuple[float, float],
408
+ AfterValidator(nondecreasing),
409
+ AfterValidator(check_range_bounds(min_val=-1.0, max_val=1.0)),
410
+ ]
411
+
412
+ def __init__(
413
+ self,
414
+ blur_limit: tuple[int, int] | int = (3, 7),
415
+ allow_shifted: bool = True,
416
+ angle_range: tuple[float, float] = (0, 360),
417
+ direction_range: tuple[float, float] = (-1.0, 1.0),
418
+ p: float = 0.5,
419
+ ):
420
+ super().__init__(blur_limit=blur_limit, p=p)
421
+ self.allow_shifted = allow_shifted
422
+ self.blur_limit = cast("tuple[int, int]", blur_limit)
423
+ self.angle_range = angle_range
424
+ self.direction_range = direction_range
425
+
426
+ def apply(self, img: np.ndarray, kernel: np.ndarray, **params: Any) -> np.ndarray:
427
+ """Apply motion blur to the input image.
428
+
429
+ Args:
430
+ img (np.ndarray): Image to blur.
431
+ kernel (np.ndarray): Kernel for motion blur.
432
+ **params (Any): Additional parameters.
433
+
434
+ Returns:
435
+ np.ndarray: Motion blurred image.
436
+
437
+ """
438
+ return fpixel.convolve(img, kernel=kernel)
439
+
440
+ def get_params(self) -> dict[str, Any]:
441
+ """Get parameters for the transform.
442
+
443
+ Returns:
444
+ dict[str, Any]: Dictionary with parameters.
445
+
446
+ """
447
+ ksize = fblur.sample_odd_from_range(
448
+ self.py_random,
449
+ self.blur_limit[0],
450
+ self.blur_limit[1],
451
+ )
452
+
453
+ angle = self.py_random.uniform(*self.angle_range)
454
+ direction = self.py_random.uniform(*self.direction_range)
455
+
456
+ # Create motion blur kernel
457
+ kernel = fblur.create_motion_kernel(
458
+ ksize,
459
+ angle,
460
+ direction,
461
+ allow_shifted=self.allow_shifted,
462
+ random_state=self.py_random,
463
+ )
464
+
465
+ return {"kernel": kernel.astype(np.float32) / np.sum(kernel)}
466
+
467
+
468
+ class MedianBlur(Blur):
469
+ """Apply median blur to the input image.
470
+
471
+ This transform uses a median filter to blur the input image. Median filtering is particularly
472
+ effective at removing salt-and-pepper noise while preserving edges, making it a popular choice
473
+ for noise reduction in image processing.
474
+
475
+ Args:
476
+ blur_limit (int | tuple[int, int]): Maximum aperture linear size for blurring the input image.
477
+ Must be odd and in the range [3, inf).
478
+ - If a single int is provided, the kernel size will be randomly chosen
479
+ between 3 and that value.
480
+ - If a tuple of two ints is provided, it defines the inclusive range
481
+ of possible kernel sizes.
482
+ Default: (3, 7)
483
+
484
+ p (float): Probability of applying the transform. Default: 0.5
485
+
486
+ Targets:
487
+ image
488
+
489
+ Image types:
490
+ uint8, float32
491
+
492
+ Number of channels:
493
+ Any
494
+
495
+ Note:
496
+ - The kernel size (aperture linear size) must always be odd and greater than 1.
497
+ - Unlike mean blur or Gaussian blur, median blur uses the median of all pixels under
498
+ the kernel area, making it more robust to outliers.
499
+ - This transform is particularly useful for:
500
+ * Removing salt-and-pepper noise
501
+ * Preserving edges while smoothing images
502
+ * Pre-processing images for edge detection algorithms
503
+ - For color images, the median is calculated independently for each channel.
504
+ - Larger kernel sizes result in stronger blurring effects but may also remove
505
+ fine details from the image.
506
+
507
+ Examples:
508
+ >>> import numpy as np
509
+ >>> import albumentations as A
510
+ >>> import cv2
511
+ >>>
512
+ >>> # Create a sample image for demonstration
513
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
514
+ >>> # Add some shapes to visualize blur effects
515
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
516
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
517
+ >>> cv2.putText(image, "Sample Text", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
518
+ >>>
519
+ >>> # Add salt and pepper noise to demonstrate median blur's noise removal capability
520
+ >>> noise = np.zeros((300, 300, 3), dtype=np.uint8)
521
+ >>> noise_points = np.random.random((300, 300)) > 0.95 # 5% of pixels as noise
522
+ >>> image[noise_points] = 255 # White noise (salt)
523
+ >>> noise_points = np.random.random((300, 300)) > 0.95 # Another 5% of pixels
524
+ >>> image[noise_points] = 0 # Black noise (pepper)
525
+ >>>
526
+ >>> # Example 1: Minimal median blur (3x3 kernel)
527
+ >>> minimal_blur = A.Compose([
528
+ ... A.MedianBlur(
529
+ ... blur_limit=3, # Fixed 3x3 kernel
530
+ ... p=1.0 # Always apply
531
+ ... )
532
+ ... ])
533
+ >>>
534
+ >>> minimal_result = minimal_blur(image=image)
535
+ >>> minimal_blurred = minimal_result["image"]
536
+ >>> # The image will have minimal median blur, removing most salt and pepper noise
537
+ >>> # while preserving edges and details
538
+ >>>
539
+ >>> # Example 2: Medium median blur
540
+ >>> medium_blur = A.Compose([
541
+ ... A.MedianBlur(
542
+ ... blur_limit=5, # Fixed 5x5 kernel
543
+ ... p=1.0
544
+ ... )
545
+ ... ])
546
+ >>>
547
+ >>> medium_result = medium_blur(image=image)
548
+ >>> medium_blurred = medium_result["image"]
549
+ >>> # The image will have a medium median blur, removing noise and small details
550
+ >>> # while still preserving major edges
551
+ >>>
552
+ >>> # Example 3: Strong median blur
553
+ >>> strong_blur = A.Compose([
554
+ ... A.MedianBlur(
555
+ ... blur_limit=9, # Fixed 9x9 kernel
556
+ ... p=1.0
557
+ ... )
558
+ ... ])
559
+ >>>
560
+ >>> strong_result = strong_blur(image=image)
561
+ >>> strong_blurred = strong_result["image"]
562
+ >>> # The image will have a strong median blur, potentially removing smaller
563
+ >>> # features while still preserving major edges better than other blur types
564
+ >>>
565
+ >>> # Example 4: Random kernel size range
566
+ >>> random_kernel = A.Compose([
567
+ ... A.MedianBlur(
568
+ ... blur_limit=(3, 9), # Kernel size between 3x3 and 9x9
569
+ ... p=1.0
570
+ ... )
571
+ ... ])
572
+ >>>
573
+ >>> random_result = random_kernel(image=image)
574
+ >>> random_blurred = random_result["image"]
575
+ >>> # The image will have a random median blur strength
576
+ >>>
577
+ >>> # Example 5: In a pipeline for noise reduction
578
+ >>> pipeline = A.Compose([
579
+ ... A.GaussNoise(var_limit=(10, 50), p=0.5), # Possibly add some noise
580
+ ... A.MedianBlur(blur_limit=(3, 5), p=0.7), # 70% chance of applying median blur
581
+ ... A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.3)
582
+ ... ])
583
+ >>>
584
+ >>> pipeline_result = pipeline(image=image)
585
+ >>> processed_image = pipeline_result["image"]
586
+ >>> # The image may have been denoised with the median blur (70% probability)
587
+
588
+ References:
589
+ - Median filter: https://en.wikipedia.org/wiki/Median_filter
590
+ - OpenCV medianBlur: https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9
591
+
592
+ """
593
+
594
+ def __init__(
595
+ self,
596
+ blur_limit: tuple[int, int] | int = (3, 7),
597
+ p: float = 0.5,
598
+ ):
599
+ super().__init__(blur_limit=blur_limit, p=p)
600
+
601
+ def apply(self, img: np.ndarray, kernel: int, **params: Any) -> np.ndarray:
602
+ """Apply median blur to the input image.
603
+
604
+ Args:
605
+ img (np.ndarray): Image to blur.
606
+ kernel (int): Size of the kernel for blur.
607
+ **params (Any): Additional parameters.
608
+
609
+ Returns:
610
+ np.ndarray: Median blurred image.
611
+
612
+ """
613
+ return fblur.median_blur(img, kernel)
614
+
615
+
616
+ class GaussianBlur(ImageOnlyTransform):
617
+ """Apply Gaussian blur to the input image using a randomly sized kernel.
618
+
619
+ This transform blurs the input image using a Gaussian filter with a random kernel size
620
+ and sigma value. Gaussian blur is a widely used image processing technique that reduces
621
+ image noise and detail, creating a smoothing effect.
622
+
623
+ Args:
624
+ sigma_limit (tuple[float, float] | float): Range for the Gaussian kernel standard
625
+ deviation (sigma). Must be more or equal than 0.
626
+ - If a single float is provided, sigma will be randomly chosen
627
+ between 0 and that value.
628
+ - If a tuple of two floats is provided, it defines the inclusive range
629
+ of possible sigma values.
630
+ Default: (0.5, 3.0)
631
+
632
+ blur_limit (tuple[int, int] | int): Controls the range of the Gaussian kernel size.
633
+ - If a single int is provided, the kernel size will be randomly chosen
634
+ between 0 and that value.
635
+ - If a tuple of two ints is provided, it defines the inclusive range
636
+ of possible kernel sizes.
637
+ Must be zero or odd and in range [0, inf). If set to 0 (default), the kernel size
638
+ will be computed from sigma as `int(sigma * 3.5) * 2 + 1` to exactly match PIL's
639
+ implementation.
640
+ Default: 0
641
+
642
+ p (float): Probability of applying the transform. Default: 0.5
643
+
644
+ Targets:
645
+ image
646
+
647
+ Image types:
648
+ uint8, float32
649
+
650
+ Number of channels:
651
+ Any
652
+
653
+ Note:
654
+ - When blur_limit=0 (default), this implementation exactly matches PIL's
655
+ GaussianBlur behavior:
656
+ * Kernel size is computed as int(sigma * 3.5) * 2 + 1
657
+ * Gaussian values are computed using the standard formula
658
+ * Kernel is normalized to preserve image luminance
659
+ - When blur_limit is specified, the kernel size is randomly sampled from that range
660
+ regardless of sigma, which might result in inconsistent blur effects.
661
+ - The default sigma range (0.5, 3.0) provides a good balance between subtle
662
+ and strong blur effects:
663
+ * sigma=0.5 results in a subtle blur
664
+ * sigma=3.0 results in a stronger blur
665
+
666
+ Examples:
667
+ >>> import numpy as np
668
+ >>> import albumentations as A
669
+ >>> import cv2
670
+ >>>
671
+ >>> # Create a sample image for demonstration
672
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
673
+ >>> # Add some shapes to visualize blur effects
674
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
675
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
676
+ >>> cv2.putText(image, "Sample Text", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
677
+ >>>
678
+ >>> # Example 1: Default Gaussian blur (automatic kernel size)
679
+ >>> default_blur = A.Compose([
680
+ ... A.GaussianBlur(p=1.0) # Using default parameters
681
+ ... ])
682
+ >>>
683
+ >>> default_result = default_blur(image=image)
684
+ >>> default_blurred = default_result["image"]
685
+ >>> # The image will have a medium Gaussian blur with sigma between 0.5 and 3.0
686
+ >>>
687
+ >>> # Example 2: Light Gaussian blur
688
+ >>> light_blur = A.Compose([
689
+ ... A.GaussianBlur(
690
+ ... sigma_limit=(0.2, 0.5), # Small sigma for subtle blur
691
+ ... blur_limit=0, # Auto-compute kernel size
692
+ ... p=1.0
693
+ ... )
694
+ ... ])
695
+ >>>
696
+ >>> light_result = light_blur(image=image)
697
+ >>> light_blurred = light_result["image"]
698
+ >>> # The image will have a subtle Gaussian blur effect
699
+ >>>
700
+ >>> # Example 3: Strong Gaussian blur
701
+ >>> strong_blur = A.Compose([
702
+ ... A.GaussianBlur(
703
+ ... sigma_limit=(3.0, 7.0), # Larger sigma for stronger blur
704
+ ... blur_limit=0, # Auto-compute kernel size
705
+ ... p=1.0
706
+ ... )
707
+ ... ])
708
+ >>>
709
+ >>> strong_result = strong_blur(image=image)
710
+ >>> strong_blurred = strong_result["image"]
711
+ >>> # The image will have a strong Gaussian blur effect
712
+ >>>
713
+ >>> # Example 4: Fixed kernel size
714
+ >>> fixed_kernel = A.Compose([
715
+ ... A.GaussianBlur(
716
+ ... sigma_limit=(0.5, 2.0),
717
+ ... blur_limit=(9, 9), # Fixed 9x9 kernel size
718
+ ... p=1.0
719
+ ... )
720
+ ... ])
721
+ >>>
722
+ >>> fixed_result = fixed_kernel(image=image)
723
+ >>> fixed_kernel_blur = fixed_result["image"]
724
+ >>> # The image will have Gaussian blur with a fixed 9x9 kernel
725
+ >>>
726
+ >>> # Example 5: Random kernel size range
727
+ >>> random_kernel = A.Compose([
728
+ ... A.GaussianBlur(
729
+ ... sigma_limit=(1.0, 2.0),
730
+ ... blur_limit=(5, 9), # Kernel size between 5x5 and 9x9
731
+ ... p=1.0
732
+ ... )
733
+ ... ])
734
+ >>>
735
+ >>> random_result = random_kernel(image=image)
736
+ >>> random_kernel_blur = random_result["image"]
737
+ >>> # The image will have Gaussian blur with a kernel size between 5x5 and 9x9
738
+ >>>
739
+ >>> # Example 6: In an augmentation pipeline
740
+ >>> pipeline = A.Compose([
741
+ ... A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),
742
+ ... A.GaussianBlur(sigma_limit=(0.5, 1.5), p=0.3), # 30% chance of applying
743
+ ... A.RGBShift(r_shift_limit=10, g_shift_limit=10, b_shift_limit=10, p=0.3)
744
+ ... ])
745
+ >>>
746
+ >>> pipeline_result = pipeline(image=image)
747
+ >>> transformed_image = pipeline_result["image"]
748
+ >>> # The image may have Gaussian blur applied with 30% probability along with other effects
749
+
750
+ References:
751
+ - OpenCV Gaussian Blur: https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1
752
+ - PIL GaussianBlur: https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html#PIL.ImageFilter.GaussianBlur
753
+
754
+ """
755
+
756
+ class InitSchema(BaseTransformInitSchema):
757
+ sigma_limit: Annotated[
758
+ tuple[float, float] | float,
759
+ AfterValidator(process_non_negative_range),
760
+ AfterValidator(nondecreasing),
761
+ ]
762
+ blur_limit: Annotated[
763
+ tuple[int, int] | int,
764
+ AfterValidator(convert_to_0plus_range),
765
+ AfterValidator(nondecreasing),
766
+ ]
767
+
768
+ def __init__(
769
+ self,
770
+ blur_limit: tuple[int, int] | int = 0,
771
+ sigma_limit: tuple[float, float] | float = (0.5, 3.0),
772
+ p: float = 0.5,
773
+ ):
774
+ super().__init__(p=p)
775
+ self.blur_limit = cast("tuple[int, int]", blur_limit)
776
+ self.sigma_limit = cast("tuple[float, float]", sigma_limit)
777
+
778
+ def apply(
779
+ self,
780
+ img: np.ndarray,
781
+ kernel: np.ndarray,
782
+ **params: Any,
783
+ ) -> np.ndarray:
784
+ """Apply Gaussian blur to the input image.
785
+
786
+ Args:
787
+ img (np.ndarray): Image to blur.
788
+ kernel (np.ndarray): Kernel for Gaussian blur.
789
+ **params (Any): Additional parameters.
790
+
791
+ Returns:
792
+ np.ndarray: Gaussian blurred image.
793
+
794
+ """
795
+ return fpixel.separable_convolve(img, kernel=kernel)
796
+
797
+ def get_params_dependent_on_data(self, params: dict[str, Any], data: dict[str, Any]) -> dict[str, float]:
798
+ """Get parameters that depend on input data.
799
+
800
+ Args:
801
+ params (dict[str, Any]): Parameters.
802
+ data (dict[str, Any]): Input data.
803
+
804
+ Returns:
805
+ dict[str, float]: Dictionary with parameters.
806
+
807
+ """
808
+ sigma = self.py_random.uniform(*self.sigma_limit)
809
+ ksize = self.py_random.randint(*self.blur_limit)
810
+ return {"kernel": fblur.create_gaussian_kernel_1d(sigma, ksize)}
811
+
812
+
813
+ class GlassBlur(ImageOnlyTransform):
814
+ """Apply a glass blur effect to the input image.
815
+
816
+ This transform simulates the effect of looking through textured glass by locally
817
+ shuffling pixels in the image. It creates a distorted, frosted glass-like appearance.
818
+
819
+ Args:
820
+ sigma (float): Standard deviation for the Gaussian kernel used in the process.
821
+ Higher values increase the blur effect. Must be non-negative.
822
+ Default: 0.7
823
+
824
+ max_delta (int): Maximum distance in pixels for shuffling.
825
+ Determines how far pixels can be moved. Larger values create more distortion.
826
+ Must be a positive integer.
827
+ Default: 4
828
+
829
+ iterations (int): Number of times to apply the glass blur effect.
830
+ More iterations create a stronger effect but increase computation time.
831
+ Must be a positive integer.
832
+ Default: 2
833
+
834
+ mode (Literal["fast", "exact"]): Mode of computation. Options are:
835
+ - "fast": Uses a faster but potentially less accurate method.
836
+ - "exact": Uses a slower but more precise method.
837
+ Default: "fast"
838
+
839
+ p (float): Probability of applying the transform. Should be in the range [0, 1].
840
+ Default: 0.5
841
+
842
+ Targets:
843
+ image
844
+
845
+ Image types:
846
+ uint8, float32
847
+
848
+ Number of channels:
849
+ Any
850
+
851
+ Note:
852
+ - This transform is particularly effective for creating a 'looking through
853
+ glass' effect or simulating the view through a frosted window.
854
+ - The 'fast' mode is recommended for most use cases as it provides a good
855
+ balance between effect quality and computation speed.
856
+ - Increasing 'iterations' will strengthen the effect but also increase the
857
+ processing time linearly.
858
+
859
+ Examples:
860
+ >>> import numpy as np
861
+ >>> import albumentations as A
862
+ >>> import cv2
863
+ >>>
864
+ >>> # Create a sample image for demonstration
865
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
866
+ >>> # Add some shapes to visualize glass blur effects
867
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
868
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
869
+ >>> cv2.putText(image, "Text Sample", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
870
+ >>>
871
+ >>> # Example 1: Subtle glass effect (light frosting)
872
+ >>> subtle_transform = A.Compose([
873
+ ... A.GlassBlur(
874
+ ... sigma=0.4, # Lower sigma for gentler blur
875
+ ... max_delta=2, # Small displacement
876
+ ... iterations=1, # Single iteration
877
+ ... mode="fast",
878
+ ... p=1.0 # Always apply
879
+ ... )
880
+ ... ])
881
+ >>>
882
+ >>> subtle_result = subtle_transform(image=image)
883
+ >>> subtle_glass = subtle_result["image"]
884
+ >>> # The image will have a subtle glass-like distortion, like light frosting
885
+ >>>
886
+ >>> # Example 2: Medium glass effect (typical frosted glass)
887
+ >>> medium_transform = A.Compose([
888
+ ... A.GlassBlur(
889
+ ... sigma=0.7, # Default sigma
890
+ ... max_delta=4, # Default displacement
891
+ ... iterations=2, # Default iterations
892
+ ... mode="fast",
893
+ ... p=1.0
894
+ ... )
895
+ ... ])
896
+ >>>
897
+ >>> medium_result = medium_transform(image=image)
898
+ >>> medium_glass = medium_result["image"]
899
+ >>> # The image will have a moderate glass-like effect, similar to standard frosted glass
900
+ >>>
901
+ >>> # Example 3: Strong glass effect (heavy distortion)
902
+ >>> strong_transform = A.Compose([
903
+ ... A.GlassBlur(
904
+ ... sigma=1.0, # Higher sigma for stronger blur
905
+ ... max_delta=6, # Larger displacement
906
+ ... iterations=3, # More iterations
907
+ ... mode="fast",
908
+ ... p=1.0
909
+ ... )
910
+ ... ])
911
+ >>>
912
+ >>> strong_result = strong_transform(image=image)
913
+ >>> strong_glass = strong_result["image"]
914
+ >>> # The image will have a strong glass-like distortion, heavily obscuring details
915
+ >>>
916
+ >>> # Example 4: Using exact mode for higher quality
917
+ >>> exact_transform = A.Compose([
918
+ ... A.GlassBlur(
919
+ ... sigma=0.7,
920
+ ... max_delta=4,
921
+ ... iterations=2,
922
+ ... mode="exact", # More precise but slower
923
+ ... p=1.0
924
+ ... )
925
+ ... ])
926
+ >>>
927
+ >>> exact_result = exact_transform(image=image)
928
+ >>> exact_glass = exact_result["image"]
929
+ >>> # The image will have a similar effect to medium, but with potentially better quality
930
+ >>>
931
+ >>> # Example 5: In a pipeline with other transforms
932
+ >>> pipeline = A.Compose([
933
+ ... A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.7),
934
+ ... A.GlassBlur(sigma=0.7, max_delta=4, iterations=2, p=0.5), # 50% chance of applying
935
+ ... A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=0.3)
936
+ ... ])
937
+ >>>
938
+ >>> pipeline_result = pipeline(image=image)
939
+ >>> transformed_image = pipeline_result["image"]
940
+ >>> # The image may have glass blur applied with 50% probability along with other effects
941
+
942
+ References:
943
+ - This implementation is based on the technique described in:
944
+ "ImageNet-trained CNNs are biased towards texture; increasing shape bias improves accuracy and robustness"
945
+ https://arxiv.org/abs/1903.12261
946
+ - Original implementation:
947
+ https://github.com/hendrycks/robustness/blob/master/ImageNet-C/create_c/make_imagenet_c.py
948
+
949
+ """
950
+
951
+ class InitSchema(BaseTransformInitSchema):
952
+ sigma: float = Field(ge=0)
953
+ max_delta: int = Field(ge=1)
954
+ iterations: int = Field(ge=1)
955
+ mode: Literal["fast", "exact"]
956
+
957
+ def __init__(
958
+ self,
959
+ sigma: float = 0.7,
960
+ max_delta: int = 4,
961
+ iterations: int = 2,
962
+ mode: Literal["fast", "exact"] = "fast",
963
+ p: float = 0.5,
964
+ ):
965
+ super().__init__(p=p)
966
+ self.sigma = sigma
967
+ self.max_delta = max_delta
968
+ self.iterations = iterations
969
+ self.mode = mode
970
+
971
+ def apply(
972
+ self,
973
+ img: np.ndarray,
974
+ *args: Any,
975
+ dxy: np.ndarray,
976
+ **params: Any,
977
+ ) -> np.ndarray:
978
+ """Apply glass blur effect to the input image.
979
+
980
+ Args:
981
+ img (np.ndarray): Image to blur.
982
+ *args (Any): Additional positional arguments.
983
+ dxy (np.ndarray): Displacement map.
984
+ **params (Any): Additional parameters.
985
+
986
+ Returns:
987
+ np.ndarray: Image with glass blur effect.
988
+
989
+ """
990
+ return fblur.glass_blur(
991
+ img,
992
+ self.sigma,
993
+ self.max_delta,
994
+ self.iterations,
995
+ dxy,
996
+ self.mode,
997
+ )
998
+
999
+ def get_params_dependent_on_data(
1000
+ self,
1001
+ params: dict[str, Any],
1002
+ data: dict[str, Any],
1003
+ ) -> dict[str, np.ndarray]:
1004
+ """Get parameters that depend on input data.
1005
+
1006
+ Args:
1007
+ params (dict[str, Any]): Parameters.
1008
+ data (dict[str, Any]): Input data.
1009
+
1010
+ Returns:
1011
+ dict[str, np.ndarray]: Dictionary with parameters.
1012
+
1013
+ """
1014
+ height, width = params["shape"][:2]
1015
+ # generate array containing all necessary values for transformations
1016
+ width_pixels = height - self.max_delta * 2
1017
+ height_pixels = width - self.max_delta * 2
1018
+ total_pixels = int(width_pixels * height_pixels)
1019
+ dxy = self.random_generator.integers(
1020
+ -self.max_delta,
1021
+ self.max_delta,
1022
+ size=(total_pixels, self.iterations, 2),
1023
+ )
1024
+
1025
+ return {"dxy": dxy}
1026
+
1027
+
1028
+ class AdvancedBlur(ImageOnlyTransform):
1029
+ """Applies a Generalized Gaussian blur to the input image with randomized parameters for advanced data augmentation.
1030
+
1031
+ This transform creates a custom blur kernel based on the Generalized Gaussian distribution,
1032
+ which allows for a wide range of blur effects beyond standard Gaussian blur. It then applies
1033
+ this kernel to the input image through convolution. The transform also incorporates noise
1034
+ into the kernel, resulting in a unique combination of blurring and noise injection.
1035
+
1036
+ Key features of this augmentation:
1037
+
1038
+ 1. Generalized Gaussian Kernel: Uses a generalized normal distribution to create kernels
1039
+ that can range from box-like blurs to very peaked blurs, controlled by the beta parameter.
1040
+
1041
+ 2. Anisotropic Blurring: Allows for different blur strengths in horizontal and vertical
1042
+ directions (controlled by sigma_x and sigma_y), and rotation of the kernel.
1043
+
1044
+ 3. Kernel Noise: Adds multiplicative noise to the kernel before applying it to the image,
1045
+ creating more diverse and realistic blur effects.
1046
+
1047
+ Implementation Details:
1048
+ The kernel is generated using a 2D Generalized Gaussian function. The process involves:
1049
+ 1. Creating a 2D grid based on the kernel size
1050
+ 2. Applying rotation to this grid
1051
+ 3. Calculating the kernel values using the Generalized Gaussian formula
1052
+ 4. Adding multiplicative noise to the kernel
1053
+ 5. Normalizing the kernel
1054
+
1055
+ The resulting kernel is then applied to the image using convolution.
1056
+
1057
+ Args:
1058
+ blur_limit (tuple[int, int] | int, optional): Controls the size of the blur kernel. If a single int
1059
+ is provided, the kernel size will be randomly chosen between 3 and that value.
1060
+ Must be odd and ≥ 3. Larger values create stronger blur effects.
1061
+ Default: (3, 7)
1062
+
1063
+ sigma_x_limit (tuple[float, float] | float): Controls the spread of the blur in the x direction.
1064
+ Higher values increase blur strength.
1065
+ If a single float is provided, the range will be (0, limit).
1066
+ Default: (0.2, 1.0)
1067
+
1068
+ sigma_y_limit (tuple[float, float] | float): Controls the spread of the blur in the y direction.
1069
+ Higher values increase blur strength.
1070
+ If a single float is provided, the range will be (0, limit).
1071
+ Default: (0.2, 1.0)
1072
+
1073
+ rotate_limit (tuple[int, int] | int): Range of angles (in degrees) for rotating the kernel.
1074
+ This rotation allows for diagonal blur directions. If limit is a single int, an angle is picked
1075
+ from (-rotate_limit, rotate_limit).
1076
+ Default: (-90, 90)
1077
+
1078
+ beta_limit (tuple[float, float] | float): Shape parameter of the Generalized Gaussian distribution.
1079
+ - beta = 1 gives a standard Gaussian distribution
1080
+ - beta < 1 creates heavier tails, resulting in more uniform, box-like blur
1081
+ - beta > 1 creates lighter tails, resulting in more peaked, focused blur
1082
+ Default: (0.5, 8.0)
1083
+
1084
+ noise_limit (tuple[float, float] | float): Controls the strength of multiplicative noise
1085
+ applied to the kernel. Values around 1.0 keep the original kernel mostly intact,
1086
+ while values further from 1.0 introduce more variation.
1087
+ Default: (0.75, 1.25)
1088
+
1089
+ p (float): Probability of applying the transform. Default: 0.5
1090
+
1091
+ Notes:
1092
+ - This transform is particularly useful for simulating complex, real-world blur effects
1093
+ that go beyond simple Gaussian blur.
1094
+ - The combination of blur and noise can help in creating more robust models by simulating
1095
+ a wider range of image degradations.
1096
+ - Extreme values, especially for beta and noise, may result in unrealistic effects and
1097
+ should be used cautiously.
1098
+
1099
+ Examples:
1100
+ >>> import numpy as np
1101
+ >>> import albumentations as A
1102
+ >>> import cv2
1103
+ >>>
1104
+ >>> # Create a sample image for demonstration
1105
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
1106
+ >>> # Add some shapes to visualize blur effects
1107
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
1108
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
1109
+ >>> cv2.putText(image, "Text Example", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
1110
+ >>> cv2.line(image, (50, 250), (250, 250), (0, 0, 255), 3) # Blue line
1111
+ >>>
1112
+ >>> # Example 1: Gaussian-like blur (beta = 1)
1113
+ >>> gaussian_like = A.Compose([
1114
+ ... A.AdvancedBlur(
1115
+ ... blur_limit=5,
1116
+ ... sigma_x_limit=(0.5, 0.5),
1117
+ ... sigma_y_limit=(0.5, 0.5),
1118
+ ... rotate_limit=0,
1119
+ ... beta_limit=(1.0, 1.0), # Standard Gaussian (beta = 1)
1120
+ ... noise_limit=(1.0, 1.0), # No noise
1121
+ ... p=1.0
1122
+ ... )
1123
+ ... ])
1124
+ >>>
1125
+ >>> gaussian_result = gaussian_like(image=image)
1126
+ >>> gaussian_image = gaussian_result["image"]
1127
+ >>> # The image will have a standard Gaussian blur applied
1128
+ >>>
1129
+ >>> # Example 2: Box-like blur (beta < 1)
1130
+ >>> box_like = A.Compose([
1131
+ ... A.AdvancedBlur(
1132
+ ... blur_limit=(7, 9),
1133
+ ... sigma_x_limit=(0.6, 0.8),
1134
+ ... sigma_y_limit=(0.6, 0.8),
1135
+ ... rotate_limit=0,
1136
+ ... beta_limit=(0.5, 0.7), # Box-like blur (beta < 1)
1137
+ ... noise_limit=(0.9, 1.1), # Slight noise
1138
+ ... p=1.0
1139
+ ... )
1140
+ ... ])
1141
+ >>>
1142
+ >>> box_result = box_like(image=image)
1143
+ >>> box_image = box_result["image"]
1144
+ >>> # The image will have a more box-like blur with heavier tails
1145
+ >>>
1146
+ >>> # Example 3: Peaked blur (beta > 1)
1147
+ >>> peaked = A.Compose([
1148
+ ... A.AdvancedBlur(
1149
+ ... blur_limit=(7, 9),
1150
+ ... sigma_x_limit=(0.6, 0.8),
1151
+ ... sigma_y_limit=(0.6, 0.8),
1152
+ ... rotate_limit=0,
1153
+ ... beta_limit=(3.0, 6.0), # Peaked blur (beta > 1)
1154
+ ... noise_limit=(0.9, 1.1), # Slight noise
1155
+ ... p=1.0
1156
+ ... )
1157
+ ... ])
1158
+ >>>
1159
+ >>> peaked_result = peaked(image=image)
1160
+ >>> peaked_image = peaked_result["image"]
1161
+ >>> # The image will have a more focused, peaked blur with lighter tails
1162
+ >>>
1163
+ >>> # Example 4: Anisotropic blur (directional)
1164
+ >>> directional = A.Compose([
1165
+ ... A.AdvancedBlur(
1166
+ ... blur_limit=(9, 11),
1167
+ ... sigma_x_limit=(0.8, 1.0), # Stronger x blur
1168
+ ... sigma_y_limit=(0.2, 0.3), # Weaker y blur
1169
+ ... rotate_limit=(0, 0), # No rotation
1170
+ ... beta_limit=(1.0, 2.0),
1171
+ ... noise_limit=(0.9, 1.1),
1172
+ ... p=1.0
1173
+ ... )
1174
+ ... ])
1175
+ >>>
1176
+ >>> directional_result = directional(image=image)
1177
+ >>> directional_image = directional_result["image"]
1178
+ >>> # The image will have a horizontal directional blur
1179
+ >>>
1180
+ >>> # Example 5: Rotated directional blur
1181
+ >>> rotated = A.Compose([
1182
+ ... A.AdvancedBlur(
1183
+ ... blur_limit=(9, 11),
1184
+ ... sigma_x_limit=(0.8, 1.0), # Stronger x blur
1185
+ ... sigma_y_limit=(0.2, 0.3), # Weaker y blur
1186
+ ... rotate_limit=(45, 45), # 45 degree rotation
1187
+ ... beta_limit=(1.0, 2.0),
1188
+ ... noise_limit=(0.9, 1.1),
1189
+ ... p=1.0
1190
+ ... )
1191
+ ... ])
1192
+ >>>
1193
+ >>> rotated_result = rotated(image=image)
1194
+ >>> rotated_image = rotated_result["image"]
1195
+ >>> # The image will have a diagonal directional blur
1196
+ >>>
1197
+ >>> # Example 6: Noisy blur
1198
+ >>> noisy = A.Compose([
1199
+ ... A.AdvancedBlur(
1200
+ ... blur_limit=(5, 7),
1201
+ ... sigma_x_limit=(0.4, 0.6),
1202
+ ... sigma_y_limit=(0.4, 0.6),
1203
+ ... rotate_limit=(-30, 30),
1204
+ ... beta_limit=(0.8, 1.2),
1205
+ ... noise_limit=(0.7, 1.3), # Strong noise variation
1206
+ ... p=1.0
1207
+ ... )
1208
+ ... ])
1209
+ >>>
1210
+ >>> noisy_result = noisy(image=image)
1211
+ >>> noisy_image = noisy_result["image"]
1212
+ >>> # The image will have blur with significant noise in the kernel
1213
+ >>>
1214
+ >>> # Example 7: Random parameters (for general augmentation)
1215
+ >>> random_blur = A.Compose([
1216
+ ... A.AdvancedBlur(p=0.5) # Using default parameter ranges
1217
+ ... ])
1218
+ >>>
1219
+ >>> random_result = random_blur(image=image)
1220
+ >>> random_image = random_result["image"]
1221
+ >>> # The image may have a random advanced blur applied with 50% probability
1222
+
1223
+ Reference:
1224
+ This transform is inspired by techniques described in:
1225
+ "Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data"
1226
+ https://arxiv.org/abs/2107.10833
1227
+
1228
+ Targets:
1229
+ image
1230
+
1231
+ Image types:
1232
+ uint8, float32
1233
+
1234
+ """
1235
+
1236
+ class InitSchema(BlurInitSchema):
1237
+ sigma_x_limit: NonNegativeFloatRangeType
1238
+ sigma_y_limit: NonNegativeFloatRangeType
1239
+ beta_limit: NonNegativeFloatRangeType
1240
+ noise_limit: NonNegativeFloatRangeType
1241
+ rotate_limit: SymmetricRangeType
1242
+
1243
+ @field_validator("beta_limit")
1244
+ @classmethod
1245
+ def _check_beta_limit(cls, value: tuple[float, float] | float) -> tuple[float, float]:
1246
+ result = to_tuple(value, low=0)
1247
+ if not (result[0] < 1.0 < result[1]):
1248
+ raise ValueError(
1249
+ f"Beta limit should include 1.0, got {result}",
1250
+ )
1251
+ return result
1252
+
1253
+ @model_validator(mode="after")
1254
+ def _validate_limits(self) -> Self:
1255
+ if (
1256
+ isinstance(self.sigma_x_limit, (tuple, list))
1257
+ and self.sigma_x_limit[0] == 0
1258
+ and isinstance(self.sigma_y_limit, (tuple, list))
1259
+ and self.sigma_y_limit[0] == 0
1260
+ ):
1261
+ msg = "sigma_x_limit and sigma_y_limit minimum value cannot be both equal to 0."
1262
+ raise ValueError(msg)
1263
+ return self
1264
+
1265
+ def __init__(
1266
+ self,
1267
+ blur_limit: tuple[int, int] | int = (3, 7),
1268
+ sigma_x_limit: tuple[float, float] | float = (0.2, 1.0),
1269
+ sigma_y_limit: tuple[float, float] | float = (0.2, 1.0),
1270
+ rotate_limit: tuple[int, int] | int = (-90, 90),
1271
+ beta_limit: tuple[float, float] | float = (0.5, 8.0),
1272
+ noise_limit: tuple[float, float] | float = (0.9, 1.1),
1273
+ p: float = 0.5,
1274
+ ):
1275
+ super().__init__(p=p)
1276
+
1277
+ self.blur_limit = cast("tuple[int, int]", blur_limit)
1278
+ self.sigma_x_limit = cast("tuple[float, float]", sigma_x_limit)
1279
+ self.sigma_y_limit = cast("tuple[float, float]", sigma_y_limit)
1280
+ self.rotate_limit = cast("tuple[int, int]", rotate_limit)
1281
+ self.beta_limit = cast("tuple[float, float]", beta_limit)
1282
+ self.noise_limit = cast("tuple[float, float]", noise_limit)
1283
+
1284
+ def apply(self, img: np.ndarray, kernel: np.ndarray, **params: Any) -> np.ndarray:
1285
+ """Apply advanced blur to the input image.
1286
+
1287
+ Args:
1288
+ img (np.ndarray): Image to blur.
1289
+ kernel (np.ndarray): Kernel for blur.
1290
+ **params (Any): Additional parameters.
1291
+
1292
+ Returns:
1293
+ np.ndarray: Blurred image.
1294
+
1295
+ """
1296
+ return fpixel.convolve(img, kernel=kernel)
1297
+
1298
+ def get_params(self) -> dict[str, np.ndarray]:
1299
+ """Get parameters for the transform.
1300
+
1301
+ Returns:
1302
+ dict[str, np.ndarray]: Dictionary with parameters.
1303
+
1304
+ """
1305
+ ksize = fblur.sample_odd_from_range(
1306
+ self.py_random,
1307
+ self.blur_limit[0],
1308
+ self.blur_limit[1],
1309
+ )
1310
+ sigma_x = self.py_random.uniform(*self.sigma_x_limit)
1311
+ sigma_y = self.py_random.uniform(*self.sigma_y_limit)
1312
+ angle = np.deg2rad(self.py_random.uniform(*self.rotate_limit))
1313
+
1314
+ # Split into 2 cases to avoid selection of narrow kernels (beta > 1) too often.
1315
+ beta = (
1316
+ self.py_random.uniform(self.beta_limit[0], 1)
1317
+ if self.py_random.random() < HALF
1318
+ else self.py_random.uniform(1, self.beta_limit[1])
1319
+ )
1320
+
1321
+ noise_matrix = self.random_generator.uniform(
1322
+ *self.noise_limit,
1323
+ size=(ksize, ksize),
1324
+ )
1325
+
1326
+ # Generate mesh grid centered at zero.
1327
+ ax = np.arange(-ksize // 2 + 1.0, ksize // 2 + 1.0)
1328
+ # > Shape (ksize, ksize, 2)
1329
+ grid = np.stack(np.meshgrid(ax, ax), axis=-1)
1330
+
1331
+ # Calculate rotated sigma matrix
1332
+ d_matrix = np.array([[sigma_x**2, 0], [0, sigma_y**2]])
1333
+ u_matrix = np.array(
1334
+ [[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]],
1335
+ )
1336
+ sigma_matrix = np.dot(u_matrix, np.dot(d_matrix, u_matrix.T))
1337
+
1338
+ inverse_sigma = np.linalg.inv(sigma_matrix)
1339
+ # Described in "Parameter Estimation For Multivariate Generalized Gaussian Distributions"
1340
+ kernel = np.exp(
1341
+ -0.5 * np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta),
1342
+ )
1343
+ # Add noise
1344
+ kernel *= noise_matrix
1345
+
1346
+ # Normalize kernel
1347
+ kernel = kernel.astype(np.float32) / np.sum(kernel)
1348
+ return {"kernel": kernel}
1349
+
1350
+
1351
+ class Defocus(ImageOnlyTransform):
1352
+ """Apply defocus blur to the input image.
1353
+
1354
+ This transform simulates the effect of an out-of-focus camera by applying a defocus blur
1355
+ to the image. It uses a combination of disc kernels and Gaussian blur to create a realistic
1356
+ defocus effect.
1357
+
1358
+ Args:
1359
+ radius (tuple[int, int] | int): Range for the radius of the defocus blur.
1360
+ If a single int is provided, the range will be [1, radius].
1361
+ Larger values create a stronger blur effect.
1362
+ Default: (3, 10)
1363
+
1364
+ alias_blur (tuple[float, float] | float): Range for the standard deviation of the Gaussian blur
1365
+ applied after the main defocus blur. This helps to reduce aliasing artifacts.
1366
+ If a single float is provided, the range will be (0, alias_blur).
1367
+ Larger values create a smoother, more aliased effect.
1368
+ Default: (0.1, 0.5)
1369
+
1370
+ p (float): Probability of applying the transform. Should be in the range [0, 1].
1371
+ Default: 0.5
1372
+
1373
+ Targets:
1374
+ image
1375
+
1376
+ Image types:
1377
+ uint8, float32
1378
+
1379
+ Note:
1380
+ - The defocus effect is created using a disc kernel, which simulates the shape of a camera's aperture.
1381
+ - The additional Gaussian blur (alias_blur) helps to soften the edges of the disc kernel, creating a
1382
+ more natural-looking defocus effect.
1383
+ - Larger radius values will create a stronger, more noticeable defocus effect.
1384
+ - The alias_blur parameter can be used to fine-tune the appearance of the defocus, with larger values
1385
+ creating a smoother, potentially more realistic effect.
1386
+
1387
+ Examples:
1388
+ >>> import numpy as np
1389
+ >>> import albumentations as A
1390
+ >>> import cv2
1391
+ >>>
1392
+ >>> # Create a sample image for demonstration
1393
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
1394
+ >>> # Add some shapes to visualize defocus effects
1395
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
1396
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
1397
+ >>> cv2.putText(image, "Sharp Text", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
1398
+ >>>
1399
+ >>> # Example 1: Subtle defocus effect (small aperture)
1400
+ >>> subtle_transform = A.Compose([
1401
+ ... A.Defocus(
1402
+ ... radius=(2, 3), # Small defocus radius
1403
+ ... alias_blur=(0.1, 0.2), # Minimal aliasing
1404
+ ... p=1.0 # Always apply
1405
+ ... )
1406
+ ... ])
1407
+ >>>
1408
+ >>> subtle_result = subtle_transform(image=image)
1409
+ >>> subtle_defocus = subtle_result["image"]
1410
+ >>> # The image will have a subtle defocus effect, with just slight blurring
1411
+ >>>
1412
+ >>> # Example 2: Moderate defocus effect (medium aperture)
1413
+ >>> moderate_transform = A.Compose([
1414
+ ... A.Defocus(
1415
+ ... radius=(4, 6), # Medium defocus radius
1416
+ ... alias_blur=(0.2, 0.3), # Moderate aliasing
1417
+ ... p=1.0
1418
+ ... )
1419
+ ... ])
1420
+ >>>
1421
+ >>> moderate_result = moderate_transform(image=image)
1422
+ >>> moderate_defocus = moderate_result["image"]
1423
+ >>> # The image will have a noticeable defocus effect, similar to a poorly focused camera
1424
+ >>>
1425
+ >>> # Example 3: Strong defocus effect (large aperture)
1426
+ >>> strong_transform = A.Compose([
1427
+ ... A.Defocus(
1428
+ ... radius=(8, 12), # Large defocus radius
1429
+ ... alias_blur=(0.4, 0.6), # Strong aliasing
1430
+ ... p=1.0
1431
+ ... )
1432
+ ... ])
1433
+ >>>
1434
+ >>> strong_result = strong_transform(image=image)
1435
+ >>> strong_defocus = strong_result["image"]
1436
+ >>> # The image will have a strong defocus effect, heavily blurring the details
1437
+ >>>
1438
+ >>> # Example 4: Using in a pipeline with other transforms
1439
+ >>> pipeline = A.Compose([
1440
+ ... A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.7),
1441
+ ... A.Defocus(radius=(3, 8), alias_blur=0.3, p=0.5), # 50% chance of applying defocus
1442
+ ... A.GaussNoise(var_limit=(10, 30), p=0.3) # Possible noise after defocus
1443
+ ... ])
1444
+ >>>
1445
+ >>> pipeline_result = pipeline(image=image)
1446
+ >>> transformed_image = pipeline_result["image"]
1447
+ >>> # The image may have defocus blur applied with 50% probability
1448
+
1449
+ References:
1450
+ Defocus aberration: https://en.wikipedia.org/wiki/Defocus_aberration
1451
+
1452
+ """
1453
+
1454
+ class InitSchema(BaseTransformInitSchema):
1455
+ radius: OnePlusIntRangeType
1456
+ alias_blur: NonNegativeFloatRangeType
1457
+
1458
+ def __init__(
1459
+ self,
1460
+ radius: tuple[int, int] | int = (3, 10),
1461
+ alias_blur: tuple[float, float] | float = (0.1, 0.5),
1462
+ p: float = 0.5,
1463
+ ):
1464
+ super().__init__(p=p)
1465
+ self.radius = cast("tuple[int, int]", radius)
1466
+ self.alias_blur = cast("tuple[float, float]", alias_blur)
1467
+
1468
+ def apply(
1469
+ self,
1470
+ img: np.ndarray,
1471
+ radius: int,
1472
+ alias_blur: float,
1473
+ **params: Any,
1474
+ ) -> np.ndarray:
1475
+ """Apply defocus blur to the input image.
1476
+
1477
+ Args:
1478
+ img (np.ndarray): Image to blur.
1479
+ radius (int): Radius of the defocus blur.
1480
+ alias_blur (float): Standard deviation of the Gaussian blur.
1481
+ **params (Any): Additional parameters.
1482
+
1483
+ Returns:
1484
+ np.ndarray: Defocused image.
1485
+
1486
+ """
1487
+ return fblur.defocus(img, radius, alias_blur)
1488
+
1489
+ def get_params(self) -> dict[str, Any]:
1490
+ """Get parameters for the transform.
1491
+
1492
+ Returns:
1493
+ dict[str, Any]: Dictionary with parameters.
1494
+
1495
+ """
1496
+ return {
1497
+ "radius": self.py_random.randint(*self.radius),
1498
+ "alias_blur": self.py_random.uniform(*self.alias_blur),
1499
+ }
1500
+
1501
+
1502
+ class ZoomBlur(ImageOnlyTransform):
1503
+ """Apply zoom blur transform.
1504
+
1505
+ This transform simulates the effect of zooming during exposure, creating a dynamic radial blur.
1506
+ It works by averaging multiple versions of the image at different zoom levels, creating
1507
+ a smooth transition from the center outward.
1508
+
1509
+ Args:
1510
+ max_factor ((float, float) or float): range for max factor for blurring.
1511
+ If max_factor is a single float, the range will be (1, limit). Default: (1, 1.31).
1512
+ All max_factor values should be larger than 1.
1513
+ step_factor ((float, float) or float): If single float will be used as step parameter for np.arange.
1514
+ If tuple of float step_factor will be in range `[step_factor[0], step_factor[1])`. Default: (0.01, 0.03).
1515
+ All step_factor values should be positive.
1516
+ p (float): probability of applying the transform. Default: 0.5.
1517
+
1518
+ Targets:
1519
+ image
1520
+
1521
+ Image types:
1522
+ uint8, float32
1523
+
1524
+ Examples:
1525
+ >>> import numpy as np
1526
+ >>> import albumentations as A
1527
+ >>> import cv2
1528
+ >>>
1529
+ >>> # Create a sample image for demonstration
1530
+ >>> image = np.zeros((300, 300, 3), dtype=np.uint8)
1531
+ >>> # Add some shapes to visualize zoom blur effects
1532
+ >>> cv2.rectangle(image, (100, 100), (200, 200), (255, 0, 0), -1) # Red square
1533
+ >>> cv2.circle(image, (150, 150), 30, (0, 255, 0), -1) # Green circle
1534
+ >>> cv2.line(image, (50, 150), (250, 150), (0, 0, 255), 5) # Blue line
1535
+ >>>
1536
+ >>> # Example 1: Subtle zoom blur
1537
+ >>> subtle_transform = A.Compose([
1538
+ ... A.ZoomBlur(
1539
+ ... max_factor=(1.05, 1.10), # Small zoom range
1540
+ ... step_factor=0.01, # Fine steps
1541
+ ... p=1.0 # Always apply
1542
+ ... )
1543
+ ... ])
1544
+ >>>
1545
+ >>> subtle_result = subtle_transform(image=image)
1546
+ >>> subtle_blur = subtle_result["image"]
1547
+ >>> # The image will have a subtle zoom blur effect, simulating a slight zoom during exposure
1548
+ >>>
1549
+ >>> # Example 2: Moderate zoom blur
1550
+ >>> moderate_transform = A.Compose([
1551
+ ... A.ZoomBlur(
1552
+ ... max_factor=(1.15, 1.25), # Medium zoom range
1553
+ ... step_factor=0.02, # Medium steps
1554
+ ... p=1.0
1555
+ ... )
1556
+ ... ])
1557
+ >>>
1558
+ >>> moderate_result = moderate_transform(image=image)
1559
+ >>> moderate_blur = moderate_result["image"]
1560
+ >>> # The image will have a more noticeable zoom blur effect
1561
+ >>>
1562
+ >>> # Example 3: Strong zoom blur
1563
+ >>> strong_transform = A.Compose([
1564
+ ... A.ZoomBlur(
1565
+ ... max_factor=(1.3, 1.5), # Large zoom range
1566
+ ... step_factor=(0.03, 0.05), # Larger steps (randomly chosen)
1567
+ ... p=1.0
1568
+ ... )
1569
+ ... ])
1570
+ >>>
1571
+ >>> strong_result = strong_transform(image=image)
1572
+ >>> strong_blur = strong_result["image"]
1573
+ >>> # The image will have a strong zoom blur effect, simulating fast zooming
1574
+ >>>
1575
+ >>> # Example 4: In a pipeline with other transforms
1576
+ >>> pipeline = A.Compose([
1577
+ ... A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.7),
1578
+ ... A.ZoomBlur(max_factor=(1.1, 1.3), step_factor=0.02, p=0.5),
1579
+ ... A.HorizontalFlip(p=0.5)
1580
+ ... ])
1581
+ >>>
1582
+ >>> pipeline_result = pipeline(image=image)
1583
+ >>> transformed_image = pipeline_result["image"]
1584
+ >>> # The image may have zoom blur applied with 50% probability
1585
+
1586
+ Reference:
1587
+ Zoom Blur: https://arxiv.org/abs/1903.12261
1588
+
1589
+ """
1590
+
1591
+ class InitSchema(BaseTransformInitSchema):
1592
+ max_factor: OnePlusFloatRangeType
1593
+ step_factor: NonNegativeFloatRangeType
1594
+
1595
+ def __init__(
1596
+ self,
1597
+ max_factor: tuple[float, float] | float = (1, 1.31),
1598
+ step_factor: tuple[float, float] | float = (0.01, 0.03),
1599
+ p: float = 0.5,
1600
+ ):
1601
+ super().__init__(p=p)
1602
+ self.max_factor = cast("tuple[float, float]", max_factor)
1603
+ self.step_factor = cast("tuple[float, float]", step_factor)
1604
+
1605
+ def apply(
1606
+ self,
1607
+ img: np.ndarray,
1608
+ zoom_factors: np.ndarray,
1609
+ **params: Any,
1610
+ ) -> np.ndarray:
1611
+ """Apply zoom blur to the input image.
1612
+
1613
+ Args:
1614
+ img (np.ndarray): Image to blur.
1615
+ zoom_factors (np.ndarray): Array of zoom factors.
1616
+ **params (Any): Additional parameters.
1617
+
1618
+ Returns:
1619
+ np.ndarray: Zoom blurred image.
1620
+
1621
+ """
1622
+ return fblur.zoom_blur(img, zoom_factors)
1623
+
1624
+ def get_params(self) -> dict[str, Any]:
1625
+ """Get parameters for the transform.
1626
+
1627
+ Returns:
1628
+ dict[str, Any]: Dictionary with parameters.
1629
+
1630
+ """
1631
+ step_factor = self.py_random.uniform(*self.step_factor)
1632
+ max_factor = max(1 + step_factor, self.py_random.uniform(*self.max_factor))
1633
+ return {"zoom_factors": np.arange(1.0, max_factor, step_factor)}