比较scipy、torch和fourier周期卷积时的不一致性

2024-05-29 01:48:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我用三种不同的方式在合成图像上实现2d周期卷积:使用scipy,使用torch和{}变换(同样在torch框架下)。在

然而,我得到了不同的结果。通过手工操作,我可以看到scipy的卷积得到正确的结果。^另一方面,{}的空间版本产生了预期的反转结果。最后,Fourier版本返回了意外的结果。在

代码如下:

import torch
import numpy as np
import scipy.signal as sig
import torch.nn.functional as F
import matplotlib.pyplot as plt


def numpy_periodic_conv(f, k):
    H, W = f.shape
    periodic_f = np.hstack([f, f])
    periodic_f = np.vstack([periodic_f, periodic_f])
    conv = sig.convolve2d(periodic_f, k, mode='same')
    conv = conv[H // 2:-H // 2, W // 2:-W // 2]
    return periodic_f, conv

def torch_periodic_conv(f, k):
    H, W = f.shape[-2:]
    periodic_f = f.repeat(1, 1, 2, 2)
    conv = F.conv2d(periodic_f, k, padding=1)
    conv = conv[:, :, H // 2:-H // 2, W // 2:-W // 2]
    return periodic_f.squeeze().numpy(), conv.squeeze().numpy()

def torch_fourier_conv(f, k):
    pad_x = f.shape[-2] - k.shape[-2]
    pad_y = f.shape[-1] - k.shape[-1]
    expanded_kernel = F.pad(k, [0, pad_x, 0, pad_y])
    fft_x = torch.rfft(f, 2, onesided=False)
    fft_kernel = torch.rfft(expanded_kernel, 2, onesided=False)
    real = fft_x[:, :, :, :, 0] * fft_kernel[:, :, :, :, 0] - \
           fft_x[:, :, :, :, 1] * fft_kernel[:, :, :, :, 1]
    im = fft_x[:, :, :, :, 0] * fft_kernel[:, :, :, :, 1] + \
         fft_x[:, :, :, :, 1] * fft_kernel[:, :, :, :, 0]
    fft_conv = torch.stack([real, im], -1) # (a+bj)*(c+dj) = (ac-bd)+(ad+bc)j
    ifft_conv = torch.irfft(fft_conv, 2, onesided=False)
    return expanded_kernel.squeeze().numpy(), ifft_conv.squeeze().numpy()

if __name__ == '__main__':
    f = np.concatenate([np.ones((10, 5)), np.zeros((10, 5))], 1)
    k = np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]])

    f_tensor = torch.from_numpy(f).unsqueeze(0).unsqueeze(0).float()
    k_tensor = torch.from_numpy(k).unsqueeze(0).unsqueeze(0).float()

    np_periodic_f, np_periodic_conv = numpy_periodic_conv(f, k)
    tc_periodic_f, tc_periodic_conv = torch_periodic_conv(f_tensor, k_tensor)
    tc_fourier_k, tc_fourier_conv = torch_fourier_conv(f_tensor, k_tensor)

    print('Spatial numpy conv shape= ', np_periodic_conv.shape)
    print('Spatial torch conv shape= ', tc_periodic_conv.shape)
    print('Fourier torch conv shape= ', tc_fourier_conv.shape)

    r_np = dict(name='numpy', im=np_periodic_f, k=k, conv=np_periodic_conv)
    r_torch = dict(name='torch', im=tc_periodic_f, k=k, conv=tc_periodic_conv)
    r_fourier = dict(name='fourier', im=f, k=tc_fourier_k, conv=tc_fourier_conv)
    titles = ['{} im', '{} kernel', '{} conv']
    results = [r_np, r_torch, r_fourier]
    fig, axs = plt.subplots(3, 3)
    for i, r_dict in enumerate(results):
        axs[i, 0].imshow(r_dict['im'], cmap='gray')
        axs[i, 0].set_title(titles[0].format(r_dict['name']))
        axs[i, 1].imshow(r_dict['k'], cmap='gray')
        axs[i, 1].set_title(titles[1].format(r_dict['name']))
        axs[i, 2].imshow(r_dict['conv'], cmap='gray')
        axs[i, 2].set_title(titles[2].format(r_dict['name']))
    plt.show()

我得到的结果是:

注意:numpytorch版本的图像都显示了执行周期卷积所需的周期图像。Fourier版本的内核显示了原始内核的0填充到图像大小,这是在频域中计算元素乘法所必需的。

-Edit1:在Fourier版本的乘法中,我在做(ac-bd)+(ad-bc)j而不是{}时出错。但是现在,我把卷积移动了一列。在

results

-Edit2:torch的空间卷积结果被反转,因为该操作实际上是一个互相关。这在pytorch的官方论坛here中得到了证实。此外,在将核填充固定为Cris Luengo的答案后,频率法得到的结果与关联式相同。这对我来说很奇怪,因为据我所知,频率特性适用于卷积,而不是相关性。在

修复内核后的新结果:

new_results


Tags: namefftnumpynptorchkernel卷积dict
1条回答
网友
1楼 · 发布于 2024-05-29 01:48:15
  1. FFT结果错误,因为填充错误。填充时,需要将原点(内核的中心)放在图像的左上角。有关详细信息,请参见this other answer

  2. 另外两种方法的区别就是卷积和相关的区别。看起来“numpy”的结果是一个卷积,“torch”结果是一个相关性。

相关问题 更多 >

    热门问题