Convolve2d 只需使用 Numpy

Posted

技术标签:

【中文标题】Convolve2d 只需使用 Numpy【英文标题】:Convolve2d just by using Numpy 【发布时间】:2017-08-22 12:44:09 【问题描述】:

我正在研究使用 Numpy 进行图像处理,但遇到了卷积过滤的问题。

我想对灰度图像进行卷积。 (将二维数组与较小的二维数组卷积)

有人想改进我的方法吗?

我知道scipy 支持 convolve2d,但我只想使用 Numpy 制作 convolve2d。

我做了什么

首先,我为子矩阵制作了一个二维数组。

a = np.arange(25).reshape(5,5) # original matrix

submatrices = np.array([
     [a[:-2,:-2], a[:-2,1:-1], a[:-2,2:]],
     [a[1:-1,:-2], a[1:-1,1:-1], a[1:-1,2:]],
     [a[2:,:-2], a[2:,1:-1], a[2:,2:]]])

子矩阵看起来很复杂,但我正在做的事情如下图所示。

接下来,我将每个子矩阵与一个过滤器相乘。

conv_filter = np.array([[0,-1,0],[-1,4,-1],[0,-1,0]])
multiplied_subs = np.einsum('ij,ijkl->ijkl',conv_filter,submatrices)

并将它们相加。

np.sum(np.sum(multiplied_subs, axis = -3), axis = -3)
#array([[ 6,  7,  8],
#       [11, 12, 13],
#       [16, 17, 18]])

因此这个过程可以称为我的 convolve2d。

def my_convolve2d(a, conv_filter):
    submatrices = np.array([
         [a[:-2,:-2], a[:-2,1:-1], a[:-2,2:]],
         [a[1:-1,:-2], a[1:-1,1:-1], a[1:-1,2:]],
         [a[2:,:-2], a[2:,1:-1], a[2:,2:]]])
    multiplied_subs = np.einsum('ij,ijkl->ijkl',conv_filter,submatrices)
    return np.sum(np.sum(multiplied_subs, axis = -3), axis = -3)

但是,我发现这个 my_convolve2d 很麻烦,原因有 3 个。

    子矩阵的生成过于笨拙,难以阅读,只能在过滤器为3*3时使用 各种子矩阵的大小似乎太大了,因为它比原始矩阵大了大约 9 倍。 求和似乎有点不直观。简单地说,丑。

感谢您阅读本文。

某种更新。我为自己写了一个conv3d。我会将其保留为公共领域。

def convolve3d(img, kernel):
    # calc the size of the array of submatracies
    sub_shape = tuple(np.subtract(img.shape, kernel.shape) + 1)

    # alias for the function
    strd = np.lib.stride_tricks.as_strided

    # make an array of submatracies
    submatrices = strd(img,kernel.shape + sub_shape,img.strides * 2)

    # sum the submatraces and kernel
    convolved_matrix = np.einsum('hij,hijklm->klm', kernel, submatrices)

    return convolved_matrix

【问题讨论】:

感谢您提供矩阵图 :) 如果我理解正确,您需要有关如何使您的解决方案更优雅的提示吗? 很高兴它有帮助!是的。如果您能提供一些技巧来克服最后几行中写的 3 个问题,我将不胜感激。 我应该补充一点,这 3 个点是按优先顺序排列的。第一个对我来说很重要,最后一个似乎有点微不足道。如果还有其他问题和改进,我也会很高兴。 第二张图(等号后)不是错了吗?不应该将每个子矩阵与过滤器相乘(按元素),然后将每个结果子矩阵的元素相加吗? @AndyK 他们会产生同样的结果。 【参考方案1】:

您可以使用as_strided 生成子数组:

import numpy as np

a = np.array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

sub_shape = (3,3)
view_shape = tuple(np.subtract(a.shape, sub_shape) + 1) + sub_shape
strides = a.strides + a.strides

sub_matrices = np.lib.stride_tricks.as_strided(a,view_shape,strides)

要摆脱第二个“丑陋”的总和,请更改 einsum,使输出数组只有 jk。这意味着您的第二次总结。

conv_filter = np.array([[0,-1,0],[-1,5,-1],[0,-1,0]])
m = np.einsum('ij,ijkl->kl',conv_filter,sub_matrices)

# [[ 6  7  8]
#  [11 12 13]
#  [16 17 18]]

【讨论】:

如果 a_s 是跨步数组并且 filter 是您的类似拉普拉斯算子的过滤器,那么如果您的答案确实是 array( [[ 6, 7, 8], [11, 12, 13], [16, 17, 18]]) 感谢您的提示。我现在正在自己尝试这个。也许微不足道,但我认为名称过滤器不合适,因为它是python的内置函数。 可以直接在爱因斯坦求和中进行求和。查看答案 这个问题,写sub_shape = conv_filter.shape不是更好吗? 看来不一定是这样。 conv_filter.shape 应该是 a.shape - sub_shape + 1。在您的示例a.shape = [5, 5]sub_shape = (3, 3) 中就是这样,所以conv_filter.shape == sub_shape 是正确的,但没有严格执行。如果你想要它被强制执行,那么你应该设置sub_shape = a.shape - conv_filter.shape + 1【参考方案2】:

使用as_strided 和@Crispin 的einsum 技巧从上方清理。将过滤器尺寸强制为扩展形状。如果索引兼容,甚至应该允许非方形输入。

def conv2d(a, f):
    s = f.shape + tuple(np.subtract(a.shape, f.shape) + 1)
    strd = numpy.lib.stride_tricks.as_strided
    subM = strd(a, shape = s, strides = a.strides * 2)
    return np.einsum('ij,ijkl->kl', f, subM)

【讨论】:

进一步简化...请参阅下面的评论... np.sum(a_s * filter, axis=(2,3)) 如果您的答案确实是 array([[ 6, 7, 8 ], [11, 12, 13], [16, 17, 18]]) ... 其中 a_s 是跨步数组,filter 是 3x3 过滤器 不确定为什么会这样@NaN,因为它肯定没有按照问题的要求做 - 但它确实做到了,即使对于任意的a 矩阵 至少在 numpy v12 中,a.shape 和 f.shape 是一个元组,所以我认为 s 应该是 tuple(np.subtract(a.shape, f.shape)+1) 真的!仍然习惯于shape 给出ndarray。解决了这个问题。 嗨@DanielF,是否有适用于RGB的概括?对 einsum 表示法不太熟悉,所以知道如何概括会很棒。【参考方案3】:

您还可以使用 fft(执行卷积的更快方法之一)

from numpy.fft import fft2, ifft2
import numpy as np

def fft_convolve2d(x,y):
    """ 2D convolution, using FFT"""
    fr = fft2(x)
    fr2 = fft2(np.flipud(np.fliplr(y)))
    m,n = fr.shape
    cc = np.real(ifft2(fr*fr2))
    cc = np.roll(cc, -m/2+1,axis=0)
    cc = np.roll(cc, -n/2+1,axis=1)
    return cc
https://gist.github.com/thearn/5424195 您必须将滤镜填充为与图像相同的大小(将其放在 zeros_like 垫子的中间。)

干杯, 丹

【讨论】:

如果内核只有 9 个元素,使用 FFT 进行卷积肯定效率不高。这仅对大内核有用。 是的。它取决于内核和图像大小,但 fft 表现出色的阈值非常低。它还取决于您的计算机架构,但对于大多数通用用例来说,fft 是高性能库的首选。 下面的文章比较了各种图像卷积方法的性能,由此我们可以得出结论,Numpy的FFT方法是执行卷积最快的方法。 laurentperrinet.github.io/sciblog/posts/…【参考方案4】:

https://laurentperrinet.github.io/sciblog/posts/2017-09-20-the-fastest-2d-convolution-in-the-world.html

在此处查看所有卷积方法及其各自的性能。 另外,我发现下面的代码 sn-p 更简单。

from numpy.fft  import fft2, ifft2
def np_fftconvolve(A, B):
    return np.real(ifft2(fft2(A)*fft2(B, s=A.shape)))

【讨论】:

此代码不正确。它将改变输出图像。内核越大,偏移越大。 你能详细说明它将如何改变输出图像吗? ***.com/questions/54877892/…

以上是关于Convolve2d 只需使用 Numpy的主要内容,如果未能解决你的问题,请参考以下文章

如何在Anaconda下使用非mkl NumPy?

numpy

使用 carma(犰狳矩阵和 numpy 数组)用 pybind11 包装 c++ 类时出错

即使无法使用numpy进行均分,也要按列拆分数组

按元素比较两个 NumPy 数组的相等性

在 Numpy Python 中将一维数组附加到二维数组