Shortcuts

mmagic.datasets.transforms.blur_kernels 源代码

# This code is referenced from BasicSR with modifications.
# Reference: https://github.com/xinntao/BasicSR/blob/master/basicsr/data/degradations.py  # noqa
# Original license: Copyright (c) 2020 xinntao, under the Apache 2.0 license.

import numpy as np
from scipy import special


[文档]def get_rotated_sigma_matrix(sig_x, sig_y, theta): """Calculate the rotated sigma matrix (two dimensional matrix). Args: sig_x (float): Standard deviation along the horizontal direction. sig_y (float): Standard deviation along the vertical direction. theta (float): Rotation in radian. Returns: np.ndarray: Rotated sigma matrix. """ diag = np.array([[sig_x**2, 0], [0, sig_y**2]]).astype(np.float32) rot = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]).astype(np.float32) return np.matmul(rot, np.matmul(diag, rot.T))
[文档]def _mesh_grid(kernel_size): """Generate the mesh grid, centering at zero. Args: kernel_size (int): The size of the kernel. Returns: x_grid (np.ndarray): x-coordinates with shape (kernel_size, kernel_size). y_grid (np.ndarray): y-coordinates with shape (kernel_size, kernel_size). xy_grid (np.ndarray): stacked coordinates with shape (kernel_size, kernel_size, 2). """ range_ = np.arange(-kernel_size // 2 + 1., kernel_size // 2 + 1.) x_grid, y_grid = np.meshgrid(range_, range_) xy_grid = np.hstack((x_grid.reshape((kernel_size * kernel_size, 1)), y_grid.reshape(kernel_size * kernel_size, 1))).reshape(kernel_size, kernel_size, 2) return xy_grid, x_grid, y_grid
[文档]def calculate_gaussian_pdf(sigma_matrix, grid): """Calculate PDF of the bivariate Gaussian distribution. Args: sigma_matrix (np.ndarray): The variance matrix with shape (2, 2). grid (np.ndarray): Coordinates generated by :func:`_mesh_grid`, with shape (K, K, 2), where K is the kernel size. Returns: kernel (np.ndarray): Un-normalized kernel. """ inverse_sigma = np.linalg.inv(sigma_matrix) kernel = np.exp(-0.5 * np.sum(np.matmul(grid, inverse_sigma) * grid, 2)) return kernel
[文档]def bivariate_gaussian(kernel_size, sig_x, sig_y=None, theta=None, grid=None, is_isotropic=True): """Generate a bivariate isotropic or anisotropic Gaussian kernel. In isotropic mode, only `sig_x` is used. `sig_y` and `theta` are ignored. Args: kernel_size (int): The size of the kernel sig_x (float): Standard deviation along horizontal direction. sig_y (float | None, optional): Standard deviation along the vertical direction. If it is None, 'is_isotropic' must be set to True. Default: None. theta (float | None, optional): Rotation in radian. If it is None, 'is_isotropic' must be set to True. Default: None. grid (ndarray, optional): Coordinates generated by :func:`_mesh_grid`, with shape (K, K, 2), where K is the kernel size. Default: None is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): normalized kernel (i.e. sum to 1). """ if grid is None: grid, _, _ = _mesh_grid(kernel_size) if is_isotropic: sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]]).astype(np.float32) else: if sig_y is None: raise ValueError('"sig_y" cannot be None if "is_isotropic" is ' 'False.') sigma_matrix = get_rotated_sigma_matrix(sig_x, sig_y, theta) kernel = calculate_gaussian_pdf(sigma_matrix, grid) kernel = kernel / np.sum(kernel) return kernel
[文档]def bivariate_generalized_gaussian(kernel_size, sig_x, sig_y=None, theta=None, beta=1, grid=None, is_isotropic=True): """Generate a bivariate generalized Gaussian kernel. Described in `Parameter Estimation For Multivariate Generalized Gaussian Distributions` by Pascal et. al (2013). In isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored. Args: kernel_size (int): The size of the kernel sig_x (float): Standard deviation along horizontal direction sig_y (float | None, optional): Standard deviation along the vertical direction. If it is None, 'is_isotropic' must be set to True. Default: None. theta (float | None, optional): Rotation in radian. If it is None, 'is_isotropic' must be set to True. Default: None. beta (float, optional): Shape parameter, beta = 1 is the normal distribution. Default: 1. grid (ndarray, optional): Coordinates generated by :func:`_mesh_grid`, with shape (K, K, 2), where K is the kernel size. Default: None is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): normalized kernel. """ if grid is None: grid, _, _ = _mesh_grid(kernel_size) if is_isotropic: sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]]).astype(np.float32) else: sigma_matrix = get_rotated_sigma_matrix(sig_x, sig_y, theta) inverse_sigma = np.linalg.inv(sigma_matrix) kernel = np.exp( -0.5 * np.power(np.sum(np.matmul(grid, inverse_sigma) * grid, 2), beta)) kernel = kernel / np.sum(kernel) return kernel
[文档]def bivariate_plateau(kernel_size, sig_x, sig_y, theta, beta, grid=None, is_isotropic=True): """Generate a plateau-like anisotropic kernel. This kernel has a form of 1 / (1+x^(beta)). Ref: https://stats.stackexchange.com/questions/203629/is-there-a-plateau-shaped-distribution # noqa In the isotropic mode, only `sig_x` is used. `sig_y` and `theta` is ignored. Args: kernel_size (int): The size of the kernel sig_x (float): Standard deviation along horizontal direction sig_y (float): Standard deviation along the vertical direction. theta (float): Rotation in radian. beta (float): Shape parameter, beta = 1 is the normal distribution. grid (np.ndarray, optional): Coordinates generated by :func:`_mesh_grid`, with shape (K, K, 2), where K is the kernel size. Default: None is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): normalized kernel (i.e. sum to 1). """ if grid is None: grid, _, _ = _mesh_grid(kernel_size) if is_isotropic: sigma_matrix = np.array([[sig_x**2, 0], [0, sig_x**2]]).astype(np.float32) else: sigma_matrix = get_rotated_sigma_matrix(sig_x, sig_y, theta) inverse_sigma = np.linalg.inv(sigma_matrix) kernel = np.reciprocal( np.power(np.sum(np.matmul(grid, inverse_sigma) * grid, 2), beta) + 1) kernel = kernel / np.sum(kernel) return kernel
[文档]def random_bivariate_gaussian_kernel(kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=None, is_isotropic=True): """Randomly generate bivariate isotropic or anisotropic Gaussian kernels. In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored. Args: kernel_size (int): The size of the kernel. sigma_x_range (tuple): The range of the standard deviation along the horizontal direction. Default: [0.6, 5] sigma_y_range (tuple): The range of the standard deviation along the vertical direction. Default: [0.6, 5] rotation_range (tuple): Range of rotation in radian. noise_range (tuple, optional): Multiplicative kernel noise. Default: None. is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): The kernel whose parameters are sampled from the specified range. """ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.' assert sigma_x_range[0] <= sigma_x_range[1], 'Wrong sigma_x_range.' sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1]) if is_isotropic is False: assert sigma_y_range[0] <= sigma_y_range[1], 'Wrong sigma_y_range.' assert rotation_range[0] <= rotation_range[1], 'Wrong rotation_range.' sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1]) rotation = np.random.uniform(rotation_range[0], rotation_range[1]) else: sigma_y = sigma_x rotation = 0 kernel = bivariate_gaussian( kernel_size, sigma_x, sigma_y, rotation, is_isotropic=is_isotropic) # add multiplicative noise if noise_range is not None: assert noise_range[0] <= noise_range[1], 'Wrong noise range.' noise = np.random.uniform( noise_range[0], noise_range[1], size=kernel.shape) kernel = kernel * noise kernel = kernel / np.sum(kernel) return kernel
[文档]def random_bivariate_generalized_gaussian_kernel(kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_range, noise_range=None, is_isotropic=True): """Randomly generate bivariate generalized Gaussian kernels. In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored. Args: kernel_size (int): The size of the kernel. sigma_x_range (tuple): The range of the standard deviation along the horizontal direction. Default: [0.6, 5] sigma_y_range (tuple): The range of the standard deviation along the vertical direction. Default: [0.6, 5] rotation_range (tuple): Range of rotation in radian. beta_range (float): The range of the shape parameter, beta = 1 is the normal distribution. noise_range (tuple, optional): Multiplicative kernel noise. Default: None. is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): Normalized kernel. """ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.' assert sigma_x_range[0] <= sigma_x_range[1], 'Wrong sigma_x_range.' sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1]) if is_isotropic is False: assert sigma_y_range[0] <= sigma_y_range[1], 'Wrong sigma_y_range.' assert rotation_range[0] <= rotation_range[1], 'Wrong rotation_range.' sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1]) rotation = np.random.uniform(rotation_range[0], rotation_range[1]) else: sigma_y = sigma_x rotation = 0 # assume beta_range[0] <= 1 <= beta_range[1] if np.random.uniform() <= 0.5: beta = np.random.uniform(beta_range[0], 1) else: beta = np.random.uniform(1, beta_range[1]) kernel = bivariate_generalized_gaussian( kernel_size, sigma_x, sigma_y, rotation, beta, is_isotropic=is_isotropic) # add multiplicative noise if noise_range is not None: assert noise_range[0] <= noise_range[1], 'Wrong noise range.' noise = np.random.uniform( noise_range[0], noise_range[1], size=kernel.shape) kernel = kernel * noise kernel = kernel / np.sum(kernel) return kernel
[文档]def random_bivariate_plateau_kernel(kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_range, noise_range=None, is_isotropic=True): """Randomly generate bivariate plateau kernels. In the isotropic mode, only `sigma_x_range` is used. `sigma_y_range` and `rotation_range` is ignored. Args: kernel_size (int): The size of the kernel. sigma_x_range (tuple): The range of the standard deviation along the horizontal direction. Default: [0.6, 5] sigma_y_range (tuple): The range of the standard deviation along the vertical direction. Default: [0.6, 5] rotation_range (tuple): Range of rotation in radian. beta_range (float): The range of the shape parameter, beta = 1 is the normal distribution. noise_range (tuple, optional): Multiplicative kernel noise. Default: None. is_isotropic (bool, optional): Whether to use an isotropic kernel. Default: True. Returns: kernel (np.ndarray): Plateau kernel. """ assert kernel_size % 2 == 1, 'Kernel size must be an odd number.' assert sigma_x_range[0] <= sigma_x_range[1], 'Wrong sigma_x_range.' sigma_x = np.random.uniform(sigma_x_range[0], sigma_x_range[1]) if is_isotropic is False: assert sigma_y_range[0] <= sigma_y_range[1], 'Wrong sigma_y_range.' assert rotation_range[0] <= rotation_range[1], 'Wrong rotation_range.' sigma_y = np.random.uniform(sigma_y_range[0], sigma_y_range[1]) rotation = np.random.uniform(rotation_range[0], rotation_range[1]) else: sigma_y = sigma_x rotation = 0 # TODO: this may be not proper if np.random.uniform() <= 0.5: beta = np.random.uniform(beta_range[0], 1) else: beta = np.random.uniform(1, beta_range[1]) kernel = bivariate_plateau( kernel_size, sigma_x, sigma_y, rotation, beta, is_isotropic=is_isotropic) # add multiplicative noise if noise_range is not None: assert noise_range[0] <= noise_range[1], 'Wrong noise range.' noise = np.random.uniform( noise_range[0], noise_range[1], size=kernel.shape) kernel = kernel * noise kernel = kernel / np.sum(kernel) return kernel
[文档]def random_circular_lowpass_kernel(omega_range, kernel_size, pad_to=0): """Generate a 2D Sinc filter. Reference: https://dsp.stackexchange.com/questions/58301/2-d-circularly-symmetric-low-pass-filter # noqa Args: omega_range (tuple): The cutoff frequency in radian (pi is max). kernel_size (int): The size of the kernel. It must be an odd number. pad_to (int, optional): The size of the padded kernel. It must be odd or zero. Default: 0. Returns: kernel (np.ndarray): The Sinc kernel with specified parameters. """ err = np.geterr() np.seterr(divide='ignore', invalid='ignore') assert kernel_size % 2 == 1, 'Kernel size must be an odd number.' omega = np.random.uniform(omega_range[0], omega_range[-1]) kernel = np.fromfunction( lambda x, y: omega * special.j1(omega * np.sqrt( (x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)) / (2 * np.pi * np.sqrt((x - (kernel_size - 1) / 2)**2 + (y - (kernel_size - 1) / 2)**2)), [kernel_size, kernel_size]) kernel[(kernel_size - 1) // 2, (kernel_size - 1) // 2] = omega**2 / (4 * np.pi) kernel = kernel / np.sum(kernel) if pad_to > kernel_size: pad_size = (pad_to - kernel_size) // 2 kernel = np.pad(kernel, ((pad_size, pad_size), (pad_size, pad_size))) np.seterr(**err) return kernel
[文档]def random_mixed_kernels(kernel_list, kernel_prob, kernel_size, sigma_x_range=[0.6, 5], sigma_y_range=[0.6, 5], rotation_range=[-np.pi, np.pi], beta_gaussian_range=[0.5, 8], beta_plateau_range=[1, 2], omega_range=[0, np.pi], noise_range=None): """Randomly generate a kernel. Args: kernel_list (list): A list of kernel types. Choices are 'iso', 'aniso', 'skew', 'generalized_iso', 'generalized_aniso', 'plateau_iso', 'plateau_aniso', 'sinc'. kernel_prob (list): The probability of choosing of the corresponding kernel. kernel_size (int): The size of the kernel. sigma_x_range (list, optional): The range of the standard deviation along the horizontal direction. Default: (0.6, 5). sigma_y_range (list, optional): The range of the standard deviation along the vertical direction. Default: (0.6, 5). rotation_range (list, optional): Range of rotation in radian. Default: (-np.pi, np.pi). beta_gaussian_range (list, optional): The range of the shape parameter for generalized Gaussian. Default: (0.5, 8). beta_plateau_range (list, optional): The range of the shape parameter for plateau kernel. Default: (1, 2). omega_range (list, optional): The range of omega used in Sinc kernel. Default: (0, np.pi). noise_range (list, optional): Multiplicative kernel noise. Default: None. Returns: kernel (np.ndarray): The kernel whose parameters are sampled from the specified range. """ kernel_type = np.random.choice(kernel_list, p=kernel_prob) if kernel_type == 'iso': kernel = random_bivariate_gaussian_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, is_isotropic=True) elif kernel_type == 'aniso': kernel = random_bivariate_gaussian_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, noise_range=noise_range, is_isotropic=False) elif kernel_type == 'generalized_iso': kernel = random_bivariate_generalized_gaussian_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_gaussian_range, noise_range=noise_range, is_isotropic=True) elif kernel_type == 'generalized_aniso': kernel = random_bivariate_generalized_gaussian_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_gaussian_range, noise_range=noise_range, is_isotropic=False) elif kernel_type == 'plateau_iso': kernel = random_bivariate_plateau_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_plateau_range, noise_range=None, is_isotropic=True) elif kernel_type == 'plateau_aniso': kernel = random_bivariate_plateau_kernel( kernel_size, sigma_x_range, sigma_y_range, rotation_range, beta_plateau_range, noise_range=None, is_isotropic=False) elif kernel_type == 'sinc': kernel = random_circular_lowpass_kernel(omega_range, kernel_size) return kernel