| |
| |
| |
|
|
| import sys |
| import math |
| import torch |
| import numpy as np |
| import scipy.ndimage as snd |
| from scipy.special import softmax |
| from scipy.interpolate import interp2d |
|
|
| import torch.nn.functional as F |
|
|
|
|
| from . import util_image |
| from ResizeRight.resize_right import resize |
|
|
| def modcrop(im, sf): |
| h, w = im.shape[:2] |
| h -= (h % sf) |
| w -= (w % sf) |
| return im[:h, :w,] |
|
|
| |
| def sigma2kernel(sigma, k_size=21, sf=3, shift=False): |
| ''' |
| Generate Gaussian kernel according to cholesky decomposion. |
| Input: |
| sigma: N x 1 x 2 x 2 torch tensor, covariance matrix |
| k_size: integer, kernel size |
| sf: scale factor |
| Output: |
| kernel: N x 1 x k x k torch tensor |
| ''' |
| try: |
| sigma_inv = torch.inverse(sigma) |
| except: |
| sigma_disturb = sigma + torch.eye(2, dtype=sigma.dtype, device=sigma.device).unsqueeze(0).unsqueeze(0) * 1e-5 |
| sigma_inv = torch.inverse(sigma_disturb) |
|
|
| |
| if shift: |
| center = k_size // 2 + 0.5 * (sf - k_size % 2) |
| else: |
| center = k_size // 2 |
|
|
| |
| X, Y = torch.meshgrid(torch.arange(k_size), torch.arange(k_size)) |
| Z = torch.stack((X, Y), dim=2).to(device=sigma.device, dtype=sigma.dtype).view(1, -1, 2, 1) |
|
|
| |
| ZZ = Z - center |
| ZZ_t = ZZ.permute(0, 1, 3, 2) |
| ZZZ = -0.5 * ZZ_t.matmul(sigma_inv).matmul(ZZ).squeeze(-1).squeeze(-1) |
| kernel = F.softmax(ZZZ, dim=1) |
|
|
| return kernel.view(-1, 1, k_size, k_size) |
|
|
| def shifted_anisotropic_Gaussian(k_size=21, sf=4, lambda_1=1.2, lambda_2=5., theta=0, shift=True): |
| ''' |
| # modified version of https://github.com/cszn/USRNet/blob/master/utils/utils_sisr.py |
| ''' |
| |
| Lam = np.diag([lambda_1, lambda_2]) |
| U = np.array([[np.cos(theta), -np.sin(theta)], |
| [np.sin(theta), np.cos(theta)]]) |
| sigma = U @ Lam @ U.T |
| inv_sigma = np.linalg.inv(sigma)[None, None, :, :] |
|
|
| |
| if shift: |
| center = k_size // 2 + 0.5*(sf - k_size % 2) |
| else: |
| center = k_size // 2 |
|
|
| |
| X, Y = np.meshgrid(range(k_size), range(k_size)) |
| Z = np.stack([X, Y], 2).astype(np.float32)[:, :, :, None] |
|
|
| |
| ZZ = Z - center |
| ZZ_t = ZZ.transpose(0,1,3,2) |
| ZZZ = -0.5 * np.squeeze(ZZ_t @ inv_sigma @ ZZ).reshape([1, -1]) |
| kernel = softmax(ZZZ, axis=1).reshape([k_size, k_size]) |
|
|
| |
| s1, s2 = sigma[0, 0], sigma[1, 1] |
| |
| rho = sigma[0, 1] / (math.sqrt(s1) * math.sqrt(s2)) |
| kernel_infos = np.array([s1, s2, rho]) |
|
|
| return kernel, kernel_infos |
|
|
| |
| def imconv_np(im, kernel, padding_mode='reflect', correlate=False): |
| ''' |
| Image convolution or correlation. |
| Input: |
| im: h x w x c numpy array |
| kernel: k x k numpy array |
| padding_mode: 'reflect', 'constant' or 'wrap' |
| ''' |
| if kernel.ndim != im.ndim: kernel = kernel[:, :, np.newaxis] |
|
|
| if correlate: |
| out = snd.correlate(im, kernel, mode=padding_mode) |
| else: |
| out = snd.convolve(im, kernel, mode=padding_mode) |
|
|
| return out |
|
|
| def conv_multi_kernel_tensor(im_hr, kernel, sf, downsampler): |
| ''' |
| Degradation model by Pytorch. |
| Input: |
| im_hr: N x c x h x w |
| kernel: N x 1 x k x k |
| sf: scale factor |
| ''' |
| im_hr_pad = F.pad(im_hr, (kernel.shape[-1] // 2,)*4, mode='reflect') |
| im_blur = F.conv3d(im_hr_pad.unsqueeze(0), kernel.unsqueeze(1), groups=im_hr.shape[0]) |
| if downsampler.lower() == 'direct': |
| im_blur = im_blur[0, :, :, ::sf, ::sf] |
| elif downsampler.lower() == 'bicubic': |
| im_blur = resize(im_blur, scale_factors=1/sf) |
| else: |
| sys.exit('Please input the corrected downsampler: Direct or Bicubic!') |
|
|
| return im_blur |
|
|
| def tidy_kernel(kernel, expect_size=21): |
| ''' |
| Input: |
| kernel: p x p numpy array |
| ''' |
| k_size = kernel.shape[-1] |
| kernel_new = np.zeros([expect_size, expect_size], dtype=kernel.dtype) |
| if expect_size >= k_size: |
| start_ind = expect_size // 2 - k_size // 2 |
| end_ind = start_ind + k_size |
| kernel_new[start_ind:end_ind, start_ind:end_ind] = kernel |
| elif expect_size < k_size: |
| start_ind = k_size // 2 - expect_size // 2 |
| end_ind = start_ind + expect_size |
| kernel_new = kernel[start_ind:end_ind, start_ind:end_ind] |
| kernel_new /= kernel_new.sum() |
|
|
| return kernel_new |
|
|
| def shift_pixel(x, sf, upper_left=True): |
| """shift pixel for super-resolution with different scale factors |
| Args: |
| x: WxHxC or WxH |
| sf: scale factor |
| upper_left: shift direction |
| """ |
| h, w = x.shape[:2] |
| shift = (sf-1)*0.5 |
| xv, yv = np.arange(0, w, 1.0), np.arange(0, h, 1.0) |
| if upper_left: |
| x1 = xv + shift |
| y1 = yv + shift |
| else: |
| x1 = xv - shift |
| y1 = yv - shift |
|
|
| x1 = np.clip(x1, 0, w-1) |
| y1 = np.clip(y1, 0, h-1) |
|
|
| if x.ndim == 2: |
| x = interp2d(xv, yv, x)(x1, y1) |
| if x.ndim == 3: |
| for i in range(x.shape[-1]): |
| x[:, :, i] = interp2d(xv, yv, x[:, :, i])(x1, y1) |
|
|
| return x |
|
|
| |
| class Bicubic: |
| def __init__(self, scale=0.25): |
| self.scale = scale |
|
|
| def __call__(self, im, scale=None, out_shape=None): |
| scale = self.scale if scale is None else scale |
| out = resize(im, scale_factors=scale, out_shape=None) |
| return out |
|
|