比较scipy,火炬和傅立叶周期卷积时的不一致性

问题描述 投票:2回答:1

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

但是,我得到了不同的结果。手动执行操作我可以看到scipy的卷积产生了正确的结果。另一方面,torch的空间版本反转产生预期结果。最后,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()

我得到的结果:

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

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

results

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

修复内核后的新结果:

new_results

python scipy fft pytorch convolution
1个回答
3
投票
  1. FFT结果错误,因为填充错误。填充时,需要将原点(内核的中心)放在图像的左上角。有关详细信息,请参阅this other answer
  2. 其他两个之间的差异是卷积和相关之间的差异。看起来“numpy”结果是一个卷积,“火炬”一个相关。
© www.soinside.com 2019 - 2024. All rights reserved.