pragnakalp commited on
Commit
a0cee52
1 Parent(s): 2803aa5

upload degradation.py file for GFPGAN's basicsr torchvision error

Browse files
Files changed (1) hide show
  1. degradations.py +765 -0
degradations.py ADDED
@@ -0,0 +1,765 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import math
3
+ import numpy as np
4
+ import random
5
+ import torch
6
+ from scipy import special
7
+ from scipy.stats import multivariate_normal
8
+ from torchvision.transforms.functional import rgb_to_grayscale
9
+ # from torchvision.transforms.functional_tensor import rgb_to_grayscale
10
+
11
+ # -------------------------------------------------------------------- #
12
+ # --------------------------- blur kernels --------------------------- #
13
+ # -------------------------------------------------------------------- #
14
+
15
+
16
+ # --------------------------- util functions --------------------------- #
17
+ def sigma_matrix2(sig_x, sig_y, theta):
18
+ """Calculate the rotated sigma matrix (two dimensional matrix).
19
+
20
+ Args:
21
+ sig_x (float):
22
+ sig_y (float):
23
+ theta (float): Radian measurement.
24
+
25
+ Returns:
26
+ ndarray: Rotated sigma matrix.
27
+ """
28
+ d_matrix = np.array([[sig_x**2, 0], [0, sig_y**2]])
29
+ u_matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
30
+ return np.dot(u_matrix, np.dot(d_matrix, u_matrix.T))
31
+
32
+
33
+ def mesh_grid(kernel_size):
34
+ """Generate the mesh grid, centering at zero.
35
+
36
+ Args:
37
+ kernel_size (int):
38
+
39
+ Returns:
40
+ xy (ndarray): with the shape (kernel_size, kernel_size, 2)
41
+ xx (ndarray): with the shape (kernel_size, kernel_size)
42
+ yy (ndarray): with the shape (kernel_size, kernel_size)
43
+ """
44
+ ax = np.arange(-kernel_size // 2 + 1., kernel_size // 2 + 1.)
45
+ xx, yy = np.meshgrid(ax, ax)
46
+ xy = np.hstack((xx.reshape((kernel_size * kernel_size, 1)), yy.reshape(kernel_size * kernel_size,
47
+ 1))).reshape(kernel_size, kernel_size, 2)
48
+ return xy, xx, yy
49
+
50
+
51
+ def pdf2(sigma_matrix, grid):
52
+ """Calculate PDF of the bivariate Gaussian distribution.
53
+
54
+ Args:
55
+ sigma_matrix (ndarray): with the shape (2, 2)
56
+ grid (ndarray): generated by :func:`mesh_grid`,
57
+ with the shape (K, K, 2), K is the kernel size.
58
+
59
+ Returns:
60
+ kernel (ndarrray): un-normalized kernel.
61
+ """
62
+ inverse_sigma = np.linalg.inv(sigma_matrix)
63
+ kernel = np.exp(-0.5 * np.sum(np.dot(grid, inverse_sigma) * grid, 2))
64
+ return kernel
65
+
66
+
67
+ def cdf2(d_matrix, grid):
68
+ """Calculate the CDF of the standard bivariate Gaussian distribution.
69
+ Used in skewed Gaussian distribution.
70
+
71
+ Args:
72
+ d_matrix (ndarrasy): skew matrix.
73
+ grid (ndarray): generated by :func:`mesh_grid`,
74
+ with the shape (K, K, 2), K is the kernel size.
75
+
76
+ Returns:
77
+ cdf (ndarray): skewed cdf.
78
+ """
79
+ rv = multivariate_normal([0, 0], [[1, 0], [0, 1]])
80
+ grid = np.dot(grid, d_matrix)
81
+ cdf = rv.cdf(grid)
82
+ return cdf
83
+
84
+
85
+ def bivariate_Gaussian(kernel_size, sig_x, sig_y, theta, grid=None, isotropic=True):
86
+ """Generate a bivariate isotropic or anisotropic Gaussian kernel.
87
+
88
+ In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.
89
+
90
+ Args:
91
+ kernel_size (int):
92
+ sig_x (float):
93
+ sig_y (float):
94
+ theta (float): Radian measurement.
95
+ grid (ndarray, optional): generated by :func:`mesh_grid`,
96
+ with the shape (K, K, 2), K is the kernel size. Default: None
97
+ isotropic (bool):
98
+
99
+ Returns:
100
+ kernel (ndarray): normalized kernel.
101
+ """
102
+ if grid is None:
103
+ grid, _, _ = mesh_grid(kernel_size)
104
+ if isotropic:
105
+ sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])
106
+ else:
107
+ sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
108
+ kernel = pdf2(sigma_matrix, grid)
109
+ kernel = kernel / np.sum(kernel)
110
+ return kernel
111
+
112
+
113
+ def bivariate_generalized_Gaussian(kernel_size, sig_x, sig_y, theta, beta, grid=None, isotropic=True):
114
+ """Generate a bivariate generalized Gaussian kernel.
115
+
116
+ ``Paper: Parameter Estimation For Multivariate Generalized Gaussian Distributions``
117
+
118
+ In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.
119
+
120
+ Args:
121
+ kernel_size (int):
122
+ sig_x (float):
123
+ sig_y (float):
124
+ theta (float): Radian measurement.
125
+ beta (float): shape parameter, beta = 1 is the normal distribution.
126
+ grid (ndarray, optional): generated by :func:`mesh_grid`,
127
+ with the shape (K, K, 2), K is the kernel size. Default: None
128
+
129
+ Returns:
130
+ kernel (ndarray): normalized kernel.
131
+ """
132
+ if grid is None:
133
+ grid, _, _ = mesh_grid(kernel_size)
134
+ if isotropic:
135
+ sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])
136
+ else:
137
+ sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
138
+ inverse_sigma = np.linalg.inv(sigma_matrix)
139
+ kernel = np.exp(-0.5 * np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta))
140
+ kernel = kernel / np.sum(kernel)
141
+ return kernel
142
+
143
+
144
+ def bivariate_plateau(kernel_size, sig_x, sig_y, theta, beta, grid=None, isotropic=True):
145
+ """Generate a plateau-like anisotropic kernel.
146
+
147
+ 1 / (1+x^(beta))
148
+
149
+ Reference: https://stats.stackexchange.com/questions/203629/is-there-a-plateau-shaped-distribution
150
+
151
+ In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored.
152
+
153
+ Args:
154
+ kernel_size (int):
155
+ sig_x (float):
156
+ sig_y (float):
157
+ theta (float): Radian measurement.
158
+ beta (float): shape parameter, beta = 1 is the normal distribution.
159
+ grid (ndarray, optional): generated by :func:`mesh_grid`,
160
+ with the shape (K, K, 2), K is the kernel size. Default: None
161
+
162
+ Returns:
163
+ kernel (ndarray): normalized kernel.
164
+ """
165
+ if grid is None:
166
+ grid, _, _ = mesh_grid(kernel_size)
167
+ if isotropic:
168
+ sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]])
169
+ else:
170
+ sigma_matrix = sigma_matrix2(sig_x, sig_y, theta)
171
+ inverse_sigma = np.linalg.inv(sigma_matrix)
172
+ kernel = np.reciprocal(np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta) + 1)
173
+ kernel = kernel / np.sum(kernel)
174
+ return kernel
175
+
176
+
177
+ def random_bivariate_Gaussian(kernel_size,
178
+ sigma_x_range,
179
+ sigma_y_range,
180
+ rotation_range,
181
+ noise_range=None,
182
+ isotropic=True):
183
+ """Randomly generate bivariate isotropic or anisotropic Gaussian kernels.
184
+
185
+ In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.
186
+
187
+ Args:
188
+ kernel_size (int):
189
+ sigma_x_range (tuple): [0.6, 5]
190
+ sigma_y_range (tuple): [0.6, 5]
191
+ rotation range (tuple): [-math.pi, math.pi]
192
+ noise_range(tuple, optional): multiplicative kernel noise,
193
+ [0.75, 1.25]. Default: None
194
+
195
+ Returns:
196
+ kernel (ndarray):
197
+ """
198
+ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
199
+ assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
200
+ sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
201
+ if isotropic is False:
202
+ assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
203
+ assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
204
+ sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
205
+ rotation = np.random.uniform(rotation_range[0], rotation_range[1])
206
+ else:
207
+ sigma_y = sigma_x
208
+ rotation = 0
209
+
210
+ kernel = bivariate_Gaussian(kernel_size, sigma_x, sigma_y, rotation, isotropic=isotropic)
211
+
212
+ # add multiplicative noise
213
+ if noise_range is not None:
214
+ assert noise_range[0] < noise_range[1], 'Wrong noise range.'
215
+ noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)
216
+ kernel = kernel * noise
217
+ kernel = kernel / np.sum(kernel)
218
+ return kernel
219
+
220
+
221
+ def random_bivariate_generalized_Gaussian(kernel_size,
222
+ sigma_x_range,
223
+ sigma_y_range,
224
+ rotation_range,
225
+ beta_range,
226
+ noise_range=None,
227
+ isotropic=True):
228
+ """Randomly generate bivariate generalized Gaussian kernels.
229
+
230
+ In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.
231
+
232
+ Args:
233
+ kernel_size (int):
234
+ sigma_x_range (tuple): [0.6, 5]
235
+ sigma_y_range (tuple): [0.6, 5]
236
+ rotation range (tuple): [-math.pi, math.pi]
237
+ beta_range (tuple): [0.5, 8]
238
+ noise_range(tuple, optional): multiplicative kernel noise,
239
+ [0.75, 1.25]. Default: None
240
+
241
+ Returns:
242
+ kernel (ndarray):
243
+ """
244
+ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
245
+ assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
246
+ sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
247
+ if isotropic is False:
248
+ assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
249
+ assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
250
+ sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
251
+ rotation = np.random.uniform(rotation_range[0], rotation_range[1])
252
+ else:
253
+ sigma_y = sigma_x
254
+ rotation = 0
255
+
256
+ # assume beta_range[0] < 1 < beta_range[1]
257
+ if np.random.uniform() < 0.5:
258
+ beta = np.random.uniform(beta_range[0], 1)
259
+ else:
260
+ beta = np.random.uniform(1, beta_range[1])
261
+
262
+ kernel = bivariate_generalized_Gaussian(kernel_size, sigma_x, sigma_y, rotation, beta, isotropic=isotropic)
263
+
264
+ # add multiplicative noise
265
+ if noise_range is not None:
266
+ assert noise_range[0] < noise_range[1], 'Wrong noise range.'
267
+ noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)
268
+ kernel = kernel * noise
269
+ kernel = kernel / np.sum(kernel)
270
+ return kernel
271
+
272
+
273
+ def random_bivariate_plateau(kernel_size,
274
+ sigma_x_range,
275
+ sigma_y_range,
276
+ rotation_range,
277
+ beta_range,
278
+ noise_range=None,
279
+ isotropic=True):
280
+ """Randomly generate bivariate plateau kernels.
281
+
282
+ In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored.
283
+
284
+ Args:
285
+ kernel_size (int):
286
+ sigma_x_range (tuple): [0.6, 5]
287
+ sigma_y_range (tuple): [0.6, 5]
288
+ rotation range (tuple): [-math.pi/2, math.pi/2]
289
+ beta_range (tuple): [1, 4]
290
+ noise_range(tuple, optional): multiplicative kernel noise,
291
+ [0.75, 1.25]. Default: None
292
+
293
+ Returns:
294
+ kernel (ndarray):
295
+ """
296
+ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
297
+ assert sigma_x_range[0] < sigma_x_range[1], 'Wrong sigma_x_range.'
298
+ sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1])
299
+ if isotropic is False:
300
+ assert sigma_y_range[0] < sigma_y_range[1], 'Wrong sigma_y_range.'
301
+ assert rotation_range[0] < rotation_range[1], 'Wrong rotation_range.'
302
+ sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1])
303
+ rotation = np.random.uniform(rotation_range[0], rotation_range[1])
304
+ else:
305
+ sigma_y = sigma_x
306
+ rotation = 0
307
+
308
+ # TODO: this may be not proper
309
+ if np.random.uniform() < 0.5:
310
+ beta = np.random.uniform(beta_range[0], 1)
311
+ else:
312
+ beta = np.random.uniform(1, beta_range[1])
313
+
314
+ kernel = bivariate_plateau(kernel_size, sigma_x, sigma_y, rotation, beta, isotropic=isotropic)
315
+ # add multiplicative noise
316
+ if noise_range is not None:
317
+ assert noise_range[0] < noise_range[1], 'Wrong noise range.'
318
+ noise = np.random.uniform(noise_range[0], noise_range[1], size=kernel.shape)
319
+ kernel = kernel * noise
320
+ kernel = kernel / np.sum(kernel)
321
+
322
+ return kernel
323
+
324
+
325
+ def random_mixed_kernels(kernel_list,
326
+ kernel_prob,
327
+ kernel_size=21,
328
+ sigma_x_range=(0.6, 5),
329
+ sigma_y_range=(0.6, 5),
330
+ rotation_range=(-math.pi, math.pi),
331
+ betag_range=(0.5, 8),
332
+ betap_range=(0.5, 8),
333
+ noise_range=None):
334
+ """Randomly generate mixed kernels.
335
+
336
+ Args:
337
+ kernel_list (tuple): a list name of kernel types,
338
+ support ['iso', 'aniso', 'skew', 'generalized', 'plateau_iso',
339
+ 'plateau_aniso']
340
+ kernel_prob (tuple): corresponding kernel probability for each
341
+ kernel type
342
+ kernel_size (int):
343
+ sigma_x_range (tuple): [0.6, 5]
344
+ sigma_y_range (tuple): [0.6, 5]
345
+ rotation range (tuple): [-math.pi, math.pi]
346
+ beta_range (tuple): [0.5, 8]
347
+ noise_range(tuple, optional): multiplicative kernel noise,
348
+ [0.75, 1.25]. Default: None
349
+
350
+ Returns:
351
+ kernel (ndarray):
352
+ """
353
+ kernel_type = random.choices(kernel_list, kernel_prob)[0]
354
+ if kernel_type == 'iso':
355
+ kernel = random_bivariate_Gaussian(
356
+ kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, isotropic=True)
357
+ elif kernel_type == 'aniso':
358
+ kernel = random_bivariate_Gaussian(
359
+ kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, isotropic=False)
360
+ elif kernel_type == 'generalized_iso':
361
+ kernel = random_bivariate_generalized_Gaussian(
362
+ kernel_size,
363
+ sigma_x_range,
364
+ sigma_y_range,
365
+ rotation_range,
366
+ betag_range,
367
+ noise_range=noise_range,
368
+ isotropic=True)
369
+ elif kernel_type == 'generalized_aniso':
370
+ kernel = random_bivariate_generalized_Gaussian(
371
+ kernel_size,
372
+ sigma_x_range,
373
+ sigma_y_range,
374
+ rotation_range,
375
+ betag_range,
376
+ noise_range=noise_range,
377
+ isotropic=False)
378
+ elif kernel_type == 'plateau_iso':
379
+ kernel = random_bivariate_plateau(
380
+ kernel_size, sigma_x_range, sigma_y_range, rotation_range, betap_range, noise_range=None, isotropic=True)
381
+ elif kernel_type == 'plateau_aniso':
382
+ kernel = random_bivariate_plateau(
383
+ kernel_size, sigma_x_range, sigma_y_range, rotation_range, betap_range, noise_range=None, isotropic=False)
384
+ return kernel
385
+
386
+
387
+ np.seterr(divide='ignore', invalid='ignore')
388
+
389
+
390
+ def circular_lowpass_kernel(cutoff, kernel_size, pad_to=0):
391
+ """2D sinc filter
392
+
393
+ Reference: https://dsp.stackexchange.com/questions/58301/2-d-circularly-symmetric-low-pass-filter
394
+
395
+ Args:
396
+ cutoff (float): cutoff frequency in radians (pi is max)
397
+ kernel_size (int): horizontal and vertical size, must be odd.
398
+ pad_to (int): pad kernel size to desired size, must be odd or zero.
399
+ """
400
+ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.'
401
+ kernel = np.fromfunction(
402
+ lambda x, y: cutoff * special.j1(cutoff * np.sqrt(
403
+ (x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)) / (2 * np.pi * np.sqrt(
404
+ (x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)), [kernel_size, kernel_size])
405
+ kernel[(kernel_size - 1) // 2, (kernel_size - 1) // 2] = cutoff**2 / (4 * np.pi)
406
+ kernel = kernel / np.sum(kernel)
407
+ if pad_to > kernel_size:
408
+ pad_size = (pad_to - kernel_size) // 2
409
+ kernel = np.pad(kernel, ((pad_size, pad_size), (pad_size, pad_size)))
410
+ return kernel
411
+
412
+
413
+ # ------------------------------------------------------------- #
414
+ # --------------------------- noise --------------------------- #
415
+ # ------------------------------------------------------------- #
416
+
417
+ # ----------------------- Gaussian Noise ----------------------- #
418
+
419
+
420
+ def generate_gaussian_noise(img, sigma=10, gray_noise=False):
421
+ """Generate Gaussian noise.
422
+
423
+ Args:
424
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
425
+ sigma (float): Noise scale (measured in range 255). Default: 10.
426
+
427
+ Returns:
428
+ (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],
429
+ float32.
430
+ """
431
+ if gray_noise:
432
+ noise = np.float32(np.random.randn(*(img.shape[0:2]))) * sigma / 255.
433
+ noise = np.expand_dims(noise, axis=2).repeat(3, axis=2)
434
+ else:
435
+ noise = np.float32(np.random.randn(*(img.shape))) * sigma / 255.
436
+ return noise
437
+
438
+
439
+ def add_gaussian_noise(img, sigma=10, clip=True, rounds=False, gray_noise=False):
440
+ """Add Gaussian noise.
441
+
442
+ Args:
443
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
444
+ sigma (float): Noise scale (measured in range 255). Default: 10.
445
+
446
+ Returns:
447
+ (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],
448
+ float32.
449
+ """
450
+ noise = generate_gaussian_noise(img, sigma, gray_noise)
451
+ out = img + noise
452
+ if clip and rounds:
453
+ out = np.clip((out * 255.0).round(), 0, 255) / 255.
454
+ elif clip:
455
+ out = np.clip(out, 0, 1)
456
+ elif rounds:
457
+ out = (out * 255.0).round() / 255.
458
+ return out
459
+
460
+
461
+ def generate_gaussian_noise_pt(img, sigma=10, gray_noise=0):
462
+ """Add Gaussian noise (PyTorch version).
463
+
464
+ Args:
465
+ img (Tensor): Shape (b, c, h, w), range[0, 1], float32.
466
+ scale (float | Tensor): Noise scale. Default: 1.0.
467
+
468
+ Returns:
469
+ (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],
470
+ float32.
471
+ """
472
+ b, _, h, w = img.size()
473
+ if not isinstance(sigma, (float, int)):
474
+ sigma = sigma.view(img.size(0), 1, 1, 1)
475
+ if isinstance(gray_noise, (float, int)):
476
+ cal_gray_noise = gray_noise > 0
477
+ else:
478
+ gray_noise = gray_noise.view(b, 1, 1, 1)
479
+ cal_gray_noise = torch.sum(gray_noise) > 0
480
+
481
+ if cal_gray_noise:
482
+ noise_gray = torch.randn(*img.size()[2:4], dtype=img.dtype, device=img.device) * sigma / 255.
483
+ noise_gray = noise_gray.view(b, 1, h, w)
484
+
485
+ # always calculate color noise
486
+ noise = torch.randn(*img.size(), dtype=img.dtype, device=img.device) * sigma / 255.
487
+
488
+ if cal_gray_noise:
489
+ noise = noise * (1 - gray_noise) + noise_gray * gray_noise
490
+ return noise
491
+
492
+
493
+ def add_gaussian_noise_pt(img, sigma=10, gray_noise=0, clip=True, rounds=False):
494
+ """Add Gaussian noise (PyTorch version).
495
+
496
+ Args:
497
+ img (Tensor): Shape (b, c, h, w), range[0, 1], float32.
498
+ scale (float | Tensor): Noise scale. Default: 1.0.
499
+
500
+ Returns:
501
+ (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],
502
+ float32.
503
+ """
504
+ noise = generate_gaussian_noise_pt(img, sigma, gray_noise)
505
+ out = img + noise
506
+ if clip and rounds:
507
+ out = torch.clamp((out * 255.0).round(), 0, 255) / 255.
508
+ elif clip:
509
+ out = torch.clamp(out, 0, 1)
510
+ elif rounds:
511
+ out = (out * 255.0).round() / 255.
512
+ return out
513
+
514
+
515
+ # ----------------------- Random Gaussian Noise ----------------------- #
516
+ def random_generate_gaussian_noise(img, sigma_range=(0, 10), gray_prob=0):
517
+ sigma = np.random.uniform(sigma_range[0], sigma_range[1])
518
+ if np.random.uniform() < gray_prob:
519
+ gray_noise = True
520
+ else:
521
+ gray_noise = False
522
+ return generate_gaussian_noise(img, sigma, gray_noise)
523
+
524
+
525
+ def random_add_gaussian_noise(img, sigma_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):
526
+ noise = random_generate_gaussian_noise(img, sigma_range, gray_prob)
527
+ out = img + noise
528
+ if clip and rounds:
529
+ out = np.clip((out * 255.0).round(), 0, 255) / 255.
530
+ elif clip:
531
+ out = np.clip(out, 0, 1)
532
+ elif rounds:
533
+ out = (out * 255.0).round() / 255.
534
+ return out
535
+
536
+
537
+ def random_generate_gaussian_noise_pt(img, sigma_range=(0, 10), gray_prob=0):
538
+ sigma = torch.rand(
539
+ img.size(0), dtype=img.dtype, device=img.device) * (sigma_range[1] - sigma_range[0]) + sigma_range[0]
540
+ gray_noise = torch.rand(img.size(0), dtype=img.dtype, device=img.device)
541
+ gray_noise = (gray_noise < gray_prob).float()
542
+ return generate_gaussian_noise_pt(img, sigma, gray_noise)
543
+
544
+
545
+ def random_add_gaussian_noise_pt(img, sigma_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):
546
+ noise = random_generate_gaussian_noise_pt(img, sigma_range, gray_prob)
547
+ out = img + noise
548
+ if clip and rounds:
549
+ out = torch.clamp((out * 255.0).round(), 0, 255) / 255.
550
+ elif clip:
551
+ out = torch.clamp(out, 0, 1)
552
+ elif rounds:
553
+ out = (out * 255.0).round() / 255.
554
+ return out
555
+
556
+
557
+ # ----------------------- Poisson (Shot) Noise ----------------------- #
558
+
559
+
560
+ def generate_poisson_noise(img, scale=1.0, gray_noise=False):
561
+ """Generate poisson noise.
562
+
563
+ Reference: https://github.com/scikit-image/scikit-image/blob/main/skimage/util/noise.py#L37-L219
564
+
565
+ Args:
566
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
567
+ scale (float): Noise scale. Default: 1.0.
568
+ gray_noise (bool): Whether generate gray noise. Default: False.
569
+
570
+ Returns:
571
+ (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],
572
+ float32.
573
+ """
574
+ if gray_noise:
575
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
576
+ # round and clip image for counting vals correctly
577
+ img = np.clip((img * 255.0).round(), 0, 255) / 255.
578
+ vals = len(np.unique(img))
579
+ vals = 2**np.ceil(np.log2(vals))
580
+ out = np.float32(np.random.poisson(img * vals) / float(vals))
581
+ noise = out - img
582
+ if gray_noise:
583
+ noise = np.repeat(noise[:, :, np.newaxis], 3, axis=2)
584
+ return noise * scale
585
+
586
+
587
+ def add_poisson_noise(img, scale=1.0, clip=True, rounds=False, gray_noise=False):
588
+ """Add poisson noise.
589
+
590
+ Args:
591
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
592
+ scale (float): Noise scale. Default: 1.0.
593
+ gray_noise (bool): Whether generate gray noise. Default: False.
594
+
595
+ Returns:
596
+ (Numpy array): Returned noisy image, shape (h, w, c), range[0, 1],
597
+ float32.
598
+ """
599
+ noise = generate_poisson_noise(img, scale, gray_noise)
600
+ out = img + noise
601
+ if clip and rounds:
602
+ out = np.clip((out * 255.0).round(), 0, 255) / 255.
603
+ elif clip:
604
+ out = np.clip(out, 0, 1)
605
+ elif rounds:
606
+ out = (out * 255.0).round() / 255.
607
+ return out
608
+
609
+
610
+ def generate_poisson_noise_pt(img, scale=1.0, gray_noise=0):
611
+ """Generate a batch of poisson noise (PyTorch version)
612
+
613
+ Args:
614
+ img (Tensor): Input image, shape (b, c, h, w), range [0, 1], float32.
615
+ scale (float | Tensor): Noise scale. Number or Tensor with shape (b).
616
+ Default: 1.0.
617
+ gray_noise (float | Tensor): 0-1 number or Tensor with shape (b).
618
+ 0 for False, 1 for True. Default: 0.
619
+
620
+ Returns:
621
+ (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],
622
+ float32.
623
+ """
624
+ b, _, h, w = img.size()
625
+ if isinstance(gray_noise, (float, int)):
626
+ cal_gray_noise = gray_noise > 0
627
+ else:
628
+ gray_noise = gray_noise.view(b, 1, 1, 1)
629
+ cal_gray_noise = torch.sum(gray_noise) > 0
630
+ if cal_gray_noise:
631
+ img_gray = rgb_to_grayscale(img, num_output_channels=1)
632
+ # round and clip image for counting vals correctly
633
+ img_gray = torch.clamp((img_gray * 255.0).round(), 0, 255) / 255.
634
+ # use for-loop to get the unique values for each sample
635
+ vals_list = [len(torch.unique(img_gray[i, :, :, :])) for i in range(b)]
636
+ vals_list = [2**np.ceil(np.log2(vals)) for vals in vals_list]
637
+ vals = img_gray.new_tensor(vals_list).view(b, 1, 1, 1)
638
+ out = torch.poisson(img_gray * vals) / vals
639
+ noise_gray = out - img_gray
640
+ noise_gray = noise_gray.expand(b, 3, h, w)
641
+
642
+ # always calculate color noise
643
+ # round and clip image for counting vals correctly
644
+ img = torch.clamp((img * 255.0).round(), 0, 255) / 255.
645
+ # use for-loop to get the unique values for each sample
646
+ vals_list = [len(torch.unique(img[i, :, :, :])) for i in range(b)]
647
+ vals_list = [2**np.ceil(np.log2(vals)) for vals in vals_list]
648
+ vals = img.new_tensor(vals_list).view(b, 1, 1, 1)
649
+ out = torch.poisson(img * vals) / vals
650
+ noise = out - img
651
+ if cal_gray_noise:
652
+ noise = noise * (1 - gray_noise) + noise_gray * gray_noise
653
+ if not isinstance(scale, (float, int)):
654
+ scale = scale.view(b, 1, 1, 1)
655
+ return noise * scale
656
+
657
+
658
+ def add_poisson_noise_pt(img, scale=1.0, clip=True, rounds=False, gray_noise=0):
659
+ """Add poisson noise to a batch of images (PyTorch version).
660
+
661
+ Args:
662
+ img (Tensor): Input image, shape (b, c, h, w), range [0, 1], float32.
663
+ scale (float | Tensor): Noise scale. Number or Tensor with shape (b).
664
+ Default: 1.0.
665
+ gray_noise (float | Tensor): 0-1 number or Tensor with shape (b).
666
+ 0 for False, 1 for True. Default: 0.
667
+
668
+ Returns:
669
+ (Tensor): Returned noisy image, shape (b, c, h, w), range[0, 1],
670
+ float32.
671
+ """
672
+ noise = generate_poisson_noise_pt(img, scale, gray_noise)
673
+ out = img + noise
674
+ if clip and rounds:
675
+ out = torch.clamp((out * 255.0).round(), 0, 255) / 255.
676
+ elif clip:
677
+ out = torch.clamp(out, 0, 1)
678
+ elif rounds:
679
+ out = (out * 255.0).round() / 255.
680
+ return out
681
+
682
+
683
+ # ----------------------- Random Poisson (Shot) Noise ----------------------- #
684
+
685
+
686
+ def random_generate_poisson_noise(img, scale_range=(0, 1.0), gray_prob=0):
687
+ scale = np.random.uniform(scale_range[0], scale_range[1])
688
+ if np.random.uniform() < gray_prob:
689
+ gray_noise = True
690
+ else:
691
+ gray_noise = False
692
+ return generate_poisson_noise(img, scale, gray_noise)
693
+
694
+
695
+ def random_add_poisson_noise(img, scale_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):
696
+ noise = random_generate_poisson_noise(img, scale_range, gray_prob)
697
+ out = img + noise
698
+ if clip and rounds:
699
+ out = np.clip((out * 255.0).round(), 0, 255) / 255.
700
+ elif clip:
701
+ out = np.clip(out, 0, 1)
702
+ elif rounds:
703
+ out = (out * 255.0).round() / 255.
704
+ return out
705
+
706
+
707
+ def random_generate_poisson_noise_pt(img, scale_range=(0, 1.0), gray_prob=0):
708
+ scale = torch.rand(
709
+ img.size(0), dtype=img.dtype, device=img.device) * (scale_range[1] - scale_range[0]) + scale_range[0]
710
+ gray_noise = torch.rand(img.size(0), dtype=img.dtype, device=img.device)
711
+ gray_noise = (gray_noise < gray_prob).float()
712
+ return generate_poisson_noise_pt(img, scale, gray_noise)
713
+
714
+
715
+ def random_add_poisson_noise_pt(img, scale_range=(0, 1.0), gray_prob=0, clip=True, rounds=False):
716
+ noise = random_generate_poisson_noise_pt(img, scale_range, gray_prob)
717
+ out = img + noise
718
+ if clip and rounds:
719
+ out = torch.clamp((out * 255.0).round(), 0, 255) / 255.
720
+ elif clip:
721
+ out = torch.clamp(out, 0, 1)
722
+ elif rounds:
723
+ out = (out * 255.0).round() / 255.
724
+ return out
725
+
726
+
727
+ # ------------------------------------------------------------------------ #
728
+ # --------------------------- JPEG compression --------------------------- #
729
+ # ------------------------------------------------------------------------ #
730
+
731
+
732
+ def add_jpg_compression(img, quality=90):
733
+ """Add JPG compression artifacts.
734
+
735
+ Args:
736
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
737
+ quality (float): JPG compression quality. 0 for lowest quality, 100 for
738
+ best quality. Default: 90.
739
+
740
+ Returns:
741
+ (Numpy array): Returned image after JPG, shape (h, w, c), range[0, 1],
742
+ float32.
743
+ """
744
+ img = np.clip(img, 0, 1)
745
+ encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
746
+ _, encimg = cv2.imencode('.jpg', img * 255., encode_param)
747
+ img = np.float32(cv2.imdecode(encimg, 1)) / 255.
748
+ return img
749
+
750
+
751
+ def random_add_jpg_compression(img, quality_range=(90, 100)):
752
+ """Randomly add JPG compression artifacts.
753
+
754
+ Args:
755
+ img (Numpy array): Input image, shape (h, w, c), range [0, 1], float32.
756
+ quality_range (tuple[float] | list[float]): JPG compression quality
757
+ range. 0 for lowest quality, 100 for best quality.
758
+ Default: (90, 100).
759
+
760
+ Returns:
761
+ (Numpy array): Returned image after JPG, shape (h, w, c), range[0, 1],
762
+ float32.
763
+ """
764
+ quality = np.random.uniform(quality_range[0], quality_range[1])
765
+ return add_jpg_compression(img, quality)