在 pytorch 中使用可分离的 2D 卷积实现 3D 高斯模糊

问题描述

我正在尝试在 pytorch 中实现 3D 体积的类似高斯模糊。我可以很容易地通过与 2D 高斯核进行卷积来对 2D 图像进行 2D 模糊,并且相同的方法似乎适用于具有 3D 高斯核的 3D。但是,它在 3D 中非常慢(尤其是在 sigmas/kernel 尺寸较大的情况下)。我知道这也可以通过与 2D 内核卷积 3 次来完成,这应该更快,但我无法让它工作。我的测试用例如下。

import torch
import torch.nn.functional as F

VOL_SIZE = 21


def make_gaussian_kernel(sigma):
    ks = int(sigma * 5)
    if ks % 2 == 0:
        ks += 1
    ts = torch.linspace(-ks // 2,ks // 2 + 1,ks)
    gauss = torch.exp((-(ts / sigma)**2 / 2))
    kernel = gauss / gauss.sum()

    return kernel


def test_3d_gaussian_blur(blur_sigma=2):
    # Make a test volume
    vol = torch.zeros([VOL_SIZE] * 3)
    vol[VOL_SIZE // 2,VOL_SIZE // 2,VOL_SIZE // 2] = 1

    # 3D convolution
    vol_in = vol.reshape(1,1,*vol.shape)
    k = make_gaussian_kernel(blur_sigma)
    k3d = torch.einsum('i,j,k->ijk',k,k)
    k3d = k3d / k3d.sum()
    vol_3d = F.conv3d(vol_in,k3d.reshape(1,*k3d.shape),stride=1,padding=len(k) // 2)

    # Separable 2D convolution
    vol_in = vol.reshape(1,*vol.shape)
    k2d = torch.einsum('i,j->ij',k)
    k2d = k2d / k2d.sum()
    k2d = k2d.expand(VOL_SIZE,*k2d.shape)
    for i in range(3):
        vol_in = vol_in.permute(0,3,2)
        vol_in = F.conv2d(vol_in,k2d,padding=len(k) // 2,groups=VOL_SIZE)
    vol_3d_sep = vol_in

    torch.allclose(vol_3d,vol_3d_sep)  # --> False

非常感谢任何帮助!

解决方法

理论上您可以使用三个 2d 卷积计算 3d 高斯卷积,但这意味着您必须减小 2d 内核的大小,因为您在每个方向上都有效地进行了卷积两次.

但在计算上更高效(以及您通常想要的)是分离成一维内核。我更改了函数的第二部分来实现这一点。 (我必须说我真的很喜欢你基于排列的方法!)因为你使用的是 3d 卷,所以你不能很好地使用 useEffect(() => { async callAxios() => { const result = await axios.get(`${url_one}`) const user_orders = await axios.get(`${url_two}`) const user_details = await axios.get(`${url_three}`) } callAxios() },[]) conv2d 函数,所以最好的事情是真的即使您只是在计算一维卷积,也只需使用 conv1d

请注意,conv3d 使用的阈值 allclose 我们无法通过此方法达到,可能是由于取消错误。

1e-8

附录:如果您真的想滥用 def test_3d_gaussian_blur(blur_sigma=2): # Make a test volume vol = torch.randn([VOL_SIZE] * 3) # using something other than zeros vol[VOL_SIZE // 2,VOL_SIZE // 2,VOL_SIZE // 2] = 1 # 3D convolution vol_in = vol.reshape(1,1,*vol.shape) k = make_gaussian_kernel(blur_sigma) k3d = torch.einsum('i,j,k->ijk',k,k) k3d = k3d / k3d.sum() vol_3d = F.conv3d(vol_in,k3d.reshape(1,*k3d.shape),stride=1,padding=len(k) // 2) # Separable 1D convolution vol_in = vol[None,None,...] # k2d = torch.einsum('i,j->ij',k) # k2d = k2d / k2d.sum() # not necessary if kernel already sums to zero,check: # print(f'{k2d.sum()=}') k1d = k[None,:,None] for i in range(3): vol_in = vol_in.permute(0,4,2,3) vol_in = F.conv3d(vol_in,k1d,padding=(len(k) // 2,0)) vol_3d_sep = vol_in print((vol_3d- vol_3d_sep).abs().max()) # something ~1e-7 print(torch.allclose(vol_3d,vol_3d_sep)) # allclose checks if it is around 1e-8 来处理卷,您可以尝试

conv2d

或者单独使用 # separate 3d kernel into 1d + 2d vol_in = vol[None,...] k2d = torch.einsum('i,k) k2d = k2d.expand(VOL_SIZE,len(k),len(k)) # k2d = k2d / k2d.sum() # not necessary if kernel already sums to zero,check: # print(f'{k2d.sum()=}') k1d = k[None,None] vol_in = F.conv3d(vol_in,0)) vol_in = vol_in[0,...] # abuse conv2d-groups argument for volume dimension,works only for 1 channel volumes vol_in = F.conv2d(vol_in,k2d,len(k) // 2),groups=VOL_SIZE) vol_3d_sep = vol_in 你可以这样做:

conv2d

这些应该仍然比三个连续的二维卷积更快。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...