使用 python 和 numpy 进行二维卷积

发布于 2024-08-25 08:48:08 字数 453 浏览 11 评论 0原文

我正在尝试使用 numpy 在 python 中执行 2d 卷积

我有一个 2d 数组,如下所示,内核 H_r 为行,H_c 为列

data = np.zeros((nr, nc), dtype=np.float32)

#fill array with some data here then convolve

for r in range(nr):
    data[r,:] = np.convolve(data[r,:], H_r, 'same')

for c in range(nc):
    data[:,c] = np.convolve(data[:,c], H_c, 'same')

data = data.astype(np.uint8);

它不会产生我期望的输出,这段代码看起来不错吗,我认为问题在于从 float32 到 8bit 的转换。最好的方法是什么

谢谢

I am trying to perform a 2d convolution in python using numpy

I have a 2d array as follows with kernel H_r for the rows and H_c for the columns

data = np.zeros((nr, nc), dtype=np.float32)

#fill array with some data here then convolve

for r in range(nr):
    data[r,:] = np.convolve(data[r,:], H_r, 'same')

for c in range(nc):
    data[:,c] = np.convolve(data[:,c], H_c, 'same')

data = data.astype(np.uint8);

It does not produce the output that I was expecting, does this code look OK, I think the problem is with the casting from float32 to 8bit. Whats the best way to do this

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

晒暮凉 2024-09-01 08:48:08

也许这不是最优化的解决方案,但这是我之前在 Python 的 numpy 库中使用的实现:

def convolution2d(image, kernel, bias):
    m, n = kernel.shape
    if (m == n):
        y, x = image.shape
        y = y - m + 1
        x = x - m + 1
        new_image = np.zeros((y,x))
        for i in range(y):
            for j in range(x):
                new_image[i][j] = np.sum(image[i:i+m, j:j+m]*kernel) + bias
    return new_image

我希望这段代码可以帮助其他有同样疑问的人。

问候。

Maybe it is not the most optimized solution, but this is an implementation I used before with numpy library for Python:

def convolution2d(image, kernel, bias):
    m, n = kernel.shape
    if (m == n):
        y, x = image.shape
        y = y - m + 1
        x = x - m + 1
        new_image = np.zeros((y,x))
        for i in range(y):
            for j in range(x):
                new_image[i][j] = np.sum(image[i:i+m, j:j+m]*kernel) + bias
    return new_image

I hope this code helps other guys with the same doubt.

Regards.

遥远的绿洲 2024-09-01 08:48:08

编辑 [2019 年 1 月]

@Tashus 评论如下是正确的,因此 @dudemeister 的答案可能更切中要害。他建议的函数也更有效,因为避免了直接的 2D 卷积和所需的操作数量。

可能的问题

我相信您正在执行两个一维卷积,第一个每列,第二个每行,并将第一个的结果替换为第二个的结果。

请注意 numpy.convolve'same' 参数的 a> 返回一个与所提供的最大形状相同的数组,因此当您进行第一次卷积时,您已经填充了整个 data 数组。

在这些步骤中可视化数组的一种好方法是使用 Hinton图表,以便您可以检查哪些元素已经具有值。

可能的解决方案

您可以尝试将两个卷积的结果相加(使用 data[:,c] += .. 而不是 data[:,c] =第二个 for 循环),如果您的卷积矩阵是使用一维 H_rH_c 矩阵的结果,如下所示:

卷积核添加

另一种方法是使用 < code>scipy.signal.convolve2d 与二维卷积数组,这可能是您首先想要做的。

Edit [Jan 2019]

@Tashus comment bellow is correct, and @dudemeister's answer is thus probably more on the mark. The function he suggested is also more efficient, by avoiding a direct 2D convolution and the number of operations that would entail.

Possible Problem

I believe you are doing two 1d convolutions, the first per columns and the second per rows, and replacing the results from the first with the results of the second.

Notice that numpy.convolve with the 'same' argument returns an array of equal shape to the largest one provided, so when you make the first convolution you already populated the entire data array.

One good way to visualize your arrays during these steps is to use Hinton diagrams, so you can check which elements already have a value.

Possible Solution

You can try to add the results of the two convolutions (use data[:,c] += .. instead of data[:,c] = on the second for loop), if your convolution matrix is the result of using the one dimensional H_r and H_c matrices like so:

convolution core addition

Another way to do that would be to use scipy.signal.convolve2d with a 2d convolution array, which is probably what you wanted to do in the first place.

奈何桥上唱咆哮 2024-09-01 08:48:08

由于您已经将内核分开,因此您应该简单地使用 scipy 中的 sepfir2d 函数:

from scipy.signal import sepfir2d
convolved = sepfir2d(data, H_r, H_c)

另一方面,您那里的代码看起来没问题......

Since you already have your kernel separated you should simply use the sepfir2d function from scipy:

from scipy.signal import sepfir2d
convolved = sepfir2d(data, H_r, H_c)

On the other hand, the code you have there looks all right ...

甜点 2024-09-01 08:48:08

我检查了很多实现,但没有找到符合我目的的实现,这应该非常简单。所以这是一个非常简单的 for 循环实现

def convolution2d(image, kernel, stride, padding):
    image = np.pad(image, [(padding, padding), (padding, padding)], mode='constant', constant_values=0)

    kernel_height, kernel_width = kernel.shape
    padded_height, padded_width = image.shape

    output_height = (padded_height - kernel_height) // stride + 1
    output_width = (padded_width - kernel_width) // stride + 1

    new_image = np.zeros((output_height, output_width)).astype(np.float32)

    for y in range(0, output_height):
        for x in range(0, output_width):
            new_image[y][x] = np.sum(image[y * stride:y * stride + kernel_height, x * stride:x * stride + kernel_width] * kernel).astype(np.float32)
    return new_image

I checked out many implementations and found none for my purpose, which should be really simple. So here is a dead-simple implementation with for loop

def convolution2d(image, kernel, stride, padding):
    image = np.pad(image, [(padding, padding), (padding, padding)], mode='constant', constant_values=0)

    kernel_height, kernel_width = kernel.shape
    padded_height, padded_width = image.shape

    output_height = (padded_height - kernel_height) // stride + 1
    output_width = (padded_width - kernel_width) // stride + 1

    new_image = np.zeros((output_height, output_width)).astype(np.float32)

    for y in range(0, output_height):
        for x in range(0, output_width):
            new_image[y][x] = np.sum(image[y * stride:y * stride + kernel_height, x * stride:x * stride + kernel_width] * kernel).astype(np.float32)
    return new_image
梦归所梦 2024-09-01 08:48:08

它也可能不是最优化的解决方案,但它比 @omotto 提出的解决方案快大约十倍,并且它只使用基本的 numpy 函数(如 reshape、expand_dims、tile...)并且没有“for”循环:

def gen_idx_conv1d(in_size, ker_size):
    """
    Generates a list of indices. This indices correspond to the indices
    of a 1D input tensor on which we would like to apply a 1D convolution.

    For instance, with a 1D input array of size 5 and a kernel of size 3, the
    1D convolution product will successively looks at elements of indices [0,1,2],
    [1,2,3] and [2,3,4] in the input array. In this case, the function idx_conv1d(5,3) 
    outputs the following array: array([0,1,2,1,2,3,2,3,4]).

    args:
        in_size: (type: int) size of the input 1d array.
        ker_size: (type: int) kernel size.

    return:
        idx_list: (type: np.array) list of the successive indices of the 1D input array
        access to the 1D convolution algorithm.

    example:
        >>> gen_idx_conv1d(in_size=5, ker_size=3)
        array([0, 1, 2, 1, 2, 3, 2, 3, 4])
    """
    f = lambda dim1, dim2, axis: np.reshape(np.tile(np.expand_dims(np.arange(dim1),axis),dim2),-1)
    out_size = in_size-ker_size+1
    return f(ker_size, out_size, 0)+f(out_size, ker_size, 1)

def repeat_idx_2d(idx_list, nbof_rep, axis):
    """
    Repeats an array of indices (idx_list) a number of time (nbof_rep) "along" an axis
    (axis). This function helps to browse through a 2d array of size
    (len(idx_list),nbof_rep).

    args:
        idx_list: (type: np.array or list) a 1D array of indices.
        nbof_rep: (type: int) number of repetition.
        axis: (type: int) axis "along" which the repetition will be applied.

    return
        idx_list: (type: np.array) a 1D array of indices of size len(idx_list)*nbof_rep.

    example:
        >>> a = np.array([0, 1, 2])
        >>> repeat_idx_2d(a, 3, 0) # repeats array 'a' 3 times along 'axis' 0
        array([0, 0, 0, 1, 1, 1, 2, 2, 2])

        >>> repeat_idx_2d(a, 3, 1) # repeats array 'a' 3 times along 'axis' 1
        array([0, 1, 2, 0, 1, 2, 0, 1, 2])

        >>> b = np.reshape(np.arange(3*4), (3,4))
        >>> b[repeat_idx_2d(np.arange(3), 4, 0), repeat_idx_2d(np.arange(4), 3, 1)]
        array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
    """
    assert axis in [0,1], "Axis should be equal to 0 or 1."
    tile_axis = (nbof_rep,1) if axis else (1,nbof_rep)
    return np.reshape(np.tile(np.expand_dims(idx_list, 1),tile_axis),-1)

def conv2d(im, ker):
    """
    Performs a 'valid' 2D convolution on an image. The input image may be
    a 2D or a 3D array.

    The output image first two dimensions will be reduced depending on the 
    convolution size. 

    The kernel may be a 2D or 3D array. If 2D, it will be applied on every
    channel of the input image. If 3D, its last dimension must match the
    image one.

    args:
        im: (type: np.array) image (2D or 3D).
        ker: (type: np.array) convolution kernel (2D or 3D).

    returns:
        im: (type: np.array) convolved image.

    example:
        >>> im = np.reshape(np.arange(10*10*3),(10,10,3))/(10*10*3) # 3D image
        >>> ker = np.array([[0,1,0],[-1,0,1],[0,-1,0]]) # 2D kernel
        >>> conv2d(im, ker) # 3D array of shape (8,8,3)
    """
    if len(im.shape)==2: # if the image is a 2D array, it is reshaped by expanding the last dimension
        im = np.expand_dims(im,-1)

    im_x, im_y, im_w = im.shape

    if len(ker.shape)==2: # if the kernel is a 2D array, it is reshaped so it will be applied to all of the image channels
        ker = np.tile(np.expand_dims(ker,-1),[1,1,im_w]) # the same kernel will be applied to all of the channels 

    assert ker.shape[-1]==im.shape[-1], "Kernel and image last dimension must match."

    ker_x = ker.shape[0]
    ker_y = ker.shape[1]

    # shape of the output image
    out_x = im_x - ker_x + 1 
    out_y = im_y - ker_y + 1

    # reshapes the image to (out_x, ker_x, out_y, ker_y, im_w)
    idx_list_x = gen_idx_conv1d(im_x, ker_x) # computes the indices of a 1D conv (cf. idx_conv1d doc)
    idx_list_y = gen_idx_conv1d(im_y, ker_y)

    idx_reshaped_x = repeat_idx_2d(idx_list_x, len(idx_list_y), 0) # repeats the previous indices to be used in 2D (cf. repeat_idx_2d doc)
    idx_reshaped_y = repeat_idx_2d(idx_list_y, len(idx_list_x), 1)

    im_reshaped = np.reshape(im[idx_reshaped_x, idx_reshaped_y, :], [out_x, ker_x, out_y, ker_y, im_w]) # reshapes

    # reshapes the 2D kernel
    ker = np.reshape(ker,[1, ker_x, 1, ker_y, im_w])

    # applies the kernel to the image and reduces the dimension back to the one of original input image
    return np.squeeze(np.sum(im_reshaped*ker, axis=(1,3)))

我尝试添加很多注释来解释该方法,但总体思路是将 3D 输入图像重塑为 5D 形状之一(output_image_height、kernel_height、output_image_width、kernel_width、output_image_channel),然后直接使用基本数组应用内核乘法。当然,此方法会使用更多内存(在执行期间,图像的大小因此乘以 kernel_height*kernel_width),但速度更快。

为了完成这个重塑步骤,我“过度使用”了 numpy 数组的索引方法,特别是将 numpy 数组作为 numpy 数组的索引的可能性。

这种方法也可以用于使用基本数学函数在 Pytorch 或 Tensorflow 中重新编码 2D 卷积积,但我毫不怀疑地说它会比现有的 nn.conv2d 运算符慢......

我真的很喜欢编码这个仅使用 numpy 基本工具的方法。

It might not be the most optimized solution either, but it is approximately ten times faster than the one proposed by @omotto and it only uses basic numpy function (as reshape, expand_dims, tile...) and no 'for' loops:

def gen_idx_conv1d(in_size, ker_size):
    """
    Generates a list of indices. This indices correspond to the indices
    of a 1D input tensor on which we would like to apply a 1D convolution.

    For instance, with a 1D input array of size 5 and a kernel of size 3, the
    1D convolution product will successively looks at elements of indices [0,1,2],
    [1,2,3] and [2,3,4] in the input array. In this case, the function idx_conv1d(5,3) 
    outputs the following array: array([0,1,2,1,2,3,2,3,4]).

    args:
        in_size: (type: int) size of the input 1d array.
        ker_size: (type: int) kernel size.

    return:
        idx_list: (type: np.array) list of the successive indices of the 1D input array
        access to the 1D convolution algorithm.

    example:
        >>> gen_idx_conv1d(in_size=5, ker_size=3)
        array([0, 1, 2, 1, 2, 3, 2, 3, 4])
    """
    f = lambda dim1, dim2, axis: np.reshape(np.tile(np.expand_dims(np.arange(dim1),axis),dim2),-1)
    out_size = in_size-ker_size+1
    return f(ker_size, out_size, 0)+f(out_size, ker_size, 1)

def repeat_idx_2d(idx_list, nbof_rep, axis):
    """
    Repeats an array of indices (idx_list) a number of time (nbof_rep) "along" an axis
    (axis). This function helps to browse through a 2d array of size
    (len(idx_list),nbof_rep).

    args:
        idx_list: (type: np.array or list) a 1D array of indices.
        nbof_rep: (type: int) number of repetition.
        axis: (type: int) axis "along" which the repetition will be applied.

    return
        idx_list: (type: np.array) a 1D array of indices of size len(idx_list)*nbof_rep.

    example:
        >>> a = np.array([0, 1, 2])
        >>> repeat_idx_2d(a, 3, 0) # repeats array 'a' 3 times along 'axis' 0
        array([0, 0, 0, 1, 1, 1, 2, 2, 2])

        >>> repeat_idx_2d(a, 3, 1) # repeats array 'a' 3 times along 'axis' 1
        array([0, 1, 2, 0, 1, 2, 0, 1, 2])

        >>> b = np.reshape(np.arange(3*4), (3,4))
        >>> b[repeat_idx_2d(np.arange(3), 4, 0), repeat_idx_2d(np.arange(4), 3, 1)]
        array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
    """
    assert axis in [0,1], "Axis should be equal to 0 or 1."
    tile_axis = (nbof_rep,1) if axis else (1,nbof_rep)
    return np.reshape(np.tile(np.expand_dims(idx_list, 1),tile_axis),-1)

def conv2d(im, ker):
    """
    Performs a 'valid' 2D convolution on an image. The input image may be
    a 2D or a 3D array.

    The output image first two dimensions will be reduced depending on the 
    convolution size. 

    The kernel may be a 2D or 3D array. If 2D, it will be applied on every
    channel of the input image. If 3D, its last dimension must match the
    image one.

    args:
        im: (type: np.array) image (2D or 3D).
        ker: (type: np.array) convolution kernel (2D or 3D).

    returns:
        im: (type: np.array) convolved image.

    example:
        >>> im = np.reshape(np.arange(10*10*3),(10,10,3))/(10*10*3) # 3D image
        >>> ker = np.array([[0,1,0],[-1,0,1],[0,-1,0]]) # 2D kernel
        >>> conv2d(im, ker) # 3D array of shape (8,8,3)
    """
    if len(im.shape)==2: # if the image is a 2D array, it is reshaped by expanding the last dimension
        im = np.expand_dims(im,-1)

    im_x, im_y, im_w = im.shape

    if len(ker.shape)==2: # if the kernel is a 2D array, it is reshaped so it will be applied to all of the image channels
        ker = np.tile(np.expand_dims(ker,-1),[1,1,im_w]) # the same kernel will be applied to all of the channels 

    assert ker.shape[-1]==im.shape[-1], "Kernel and image last dimension must match."

    ker_x = ker.shape[0]
    ker_y = ker.shape[1]

    # shape of the output image
    out_x = im_x - ker_x + 1 
    out_y = im_y - ker_y + 1

    # reshapes the image to (out_x, ker_x, out_y, ker_y, im_w)
    idx_list_x = gen_idx_conv1d(im_x, ker_x) # computes the indices of a 1D conv (cf. idx_conv1d doc)
    idx_list_y = gen_idx_conv1d(im_y, ker_y)

    idx_reshaped_x = repeat_idx_2d(idx_list_x, len(idx_list_y), 0) # repeats the previous indices to be used in 2D (cf. repeat_idx_2d doc)
    idx_reshaped_y = repeat_idx_2d(idx_list_y, len(idx_list_x), 1)

    im_reshaped = np.reshape(im[idx_reshaped_x, idx_reshaped_y, :], [out_x, ker_x, out_y, ker_y, im_w]) # reshapes

    # reshapes the 2D kernel
    ker = np.reshape(ker,[1, ker_x, 1, ker_y, im_w])

    # applies the kernel to the image and reduces the dimension back to the one of original input image
    return np.squeeze(np.sum(im_reshaped*ker, axis=(1,3)))

I tried to add a lot of comments to explain the method but the global idea is to reshape the 3D input image to a 5D one of shape (output_image_height, kernel_height, output_image_width, kernel_width, output_image_channel) and then to apply the kernel directly using the basic array multiplication. Of course, this methods is then using more memory (during the execution the size of the image is thus multiply by kernel_height*kernel_width) but it is faster.

To do this reshape step, I 'over-used' the indexing methods of numpy arrays, especially, the possibility of giving a numpy array as indices into a numpy array.

This methods could also be used to re-code the 2D convolution product in Pytorch or Tensorflow using the base math functions but I have no doubt in saying that it will be slower than the existing nn.conv2d operator...

I really enjoyed coding this method by only using the numpy basic tools.

风和你 2024-09-01 08:48:08

仅使用基本 numpy 的超级简单且快速的卷积:

import numpy as np

def conv2d(image, kernel):
    # apply kernel to image, return image of the same shape
    # assume both image and kernel are 2D arrays
    # kernel = np.flipud(np.fliplr(kernel))  # optionally flip the kernel
    k = kernel.shape[0]
    width = k//2
    # place the image inside a frame to compensate for the kernel overlap
    a = framed(image, width)
    b = np.zeros(image.shape)  # fill the output array with zeros; do not use np.empty()
    # shift the image around each pixel, multiply by the corresponding kernel value and accumulate the results
    for p, dp, r, dr in [(i, i + image.shape[0], j, j + image.shape[1]) for i in range(k) for j in range(k)]:
        b += a[p:dp, r:dr] * kernel[p, r]
    # or just write two nested for loops if you prefer
    # np.clip(b, 0, 255, out=b)  # optionally clip values exceeding the limits
    return b

def framed(image, width):
    a = np.zeros((image.shape[0]+2*width, image.shape[1]+2*width))
    a[width:-width, width:-width] = image
    # alternatively fill the frame with ones or copy border pixels
    return a

运行它:

Image.fromarray(conv2d(image, kernel).astype('uint8'))

不是沿着图像滑动内核并逐像素计算变换,而是创建与内核中每个元素对应的一系列图像的移位版本并应用相应的内核每个移动图像版本的值。

这可能是仅使用基本 numpy 可以获得的最快速度;速度已经与 scipy convolve2d 的 C 实现相当,并且优于 fftconvolve。这个想法类似于@Tata​​rize。此示例仅适用于一种颜色分量;对于 RGB,只需对每个重复(或相应地修改算法)。

Super simple and fast convolution using only basic numpy:

import numpy as np

def conv2d(image, kernel):
    # apply kernel to image, return image of the same shape
    # assume both image and kernel are 2D arrays
    # kernel = np.flipud(np.fliplr(kernel))  # optionally flip the kernel
    k = kernel.shape[0]
    width = k//2
    # place the image inside a frame to compensate for the kernel overlap
    a = framed(image, width)
    b = np.zeros(image.shape)  # fill the output array with zeros; do not use np.empty()
    # shift the image around each pixel, multiply by the corresponding kernel value and accumulate the results
    for p, dp, r, dr in [(i, i + image.shape[0], j, j + image.shape[1]) for i in range(k) for j in range(k)]:
        b += a[p:dp, r:dr] * kernel[p, r]
    # or just write two nested for loops if you prefer
    # np.clip(b, 0, 255, out=b)  # optionally clip values exceeding the limits
    return b

def framed(image, width):
    a = np.zeros((image.shape[0]+2*width, image.shape[1]+2*width))
    a[width:-width, width:-width] = image
    # alternatively fill the frame with ones or copy border pixels
    return a

Run it:

Image.fromarray(conv2d(image, kernel).astype('uint8'))

Instead of sliding the kernel along the image and computing the transformation pixel by pixel, create a series of shifted versions of the image corresponding to each element in the kernel and apply the corresponding kernel value to each of the shifted image versions.

This is probably the fastest you can get using just basic numpy; the speed is already comparable to C implementation of scipy convolve2d and better than fftconvolve. The idea is similar to @Tatarize. This example works only for one color component; for RGB just repeat for each (or modify the algorithm accordingly).

亽野灬性zι浪 2024-09-01 08:48:08

最明显的方法之一是对内核进行硬编码。

img = img.convert('L')
a = np.array(img)
out = np.zeros([a.shape[0]-2, a.shape[1]-2], dtype='float')
out += a[:-2, :-2]
out += a[1:-1, :-2]
out += a[2:, :-2]
out += a[:-2, 1:-1]
out += a[1:-1,1:-1]
out += a[2:, 1:-1]
out += a[:-2, 2:]
out += a[1:-1, 2:]
out += a[2:, 2:]
out /= 9.0
out = out.astype('uint8')
img = Image.fromarray(out)

此示例执行完全展开的 3x3 框模糊。您可以将具有不同值的值相乘,然后除以不同的金额。但是,如果您真的想要最快且最肮脏的方法,这就是它。我认为它比 Guillaume Mougeot 的方法好 5 倍。他的方法比其他方法好 10 倍。

如果您正在做高斯模糊之类的事情,它可能会丢失几个步骤。并且需要增加一些东西。

One of the most obvious is to hard code the kernel.

img = img.convert('L')
a = np.array(img)
out = np.zeros([a.shape[0]-2, a.shape[1]-2], dtype='float')
out += a[:-2, :-2]
out += a[1:-1, :-2]
out += a[2:, :-2]
out += a[:-2, 1:-1]
out += a[1:-1,1:-1]
out += a[2:, 1:-1]
out += a[:-2, 2:]
out += a[1:-1, 2:]
out += a[2:, 2:]
out /= 9.0
out = out.astype('uint8')
img = Image.fromarray(out)

This example does a box blur 3x3 completely unrolled. You can multiply the values where you have a different value and divide them by a different amount. But, if you honestly want the quickest and dirtiest method this is it. I think it beats Guillaume Mougeot's method by a factor of like 5. His method beating the others by a factor of 10.

It may lose a few steps if you're doing something like a gaussian blur. and need to multiply some stuff.

鹤舞 2024-09-01 08:48:08

我编写了这个使用 numpy.lib.stride_tricks.as_stridedconvolve_stride 。此外,它支持跨步和扩张。它还兼容阶数 > 的张量。 2.

import numpy as np
from numpy.lib.stride_tricks import as_strided
from im2col import im2col

def conv_view(X, F_s, dr, std):
    X_s = np.array(X.shape)
    F_s = np.array(F_s)
    dr = np.array(dr)
    Fd_s = (F_s - 1) * dr + 1
    if np.any(Fd_s > X_s):
        raise ValueError('(Dilated) filter size must be smaller than X')
    std = np.array(std)
    X_ss = np.array(X.strides)
    Xn_s = (X_s - Fd_s) // std + 1
    Xv_s = np.append(Xn_s, F_s)
    Xv_ss = np.tile(X_ss, 2) * np.append(std, dr)
    return as_strided(X, Xv_s, Xv_ss, writeable=False)

def convolve_stride(X, F, dr=None, std=None):
    if dr is None:
        dr = np.ones(X.ndim, dtype=int)
    if std is None:
        std = np.ones(X.ndim, dtype=int)
    if not (X.ndim == F.ndim == len(dr) == len(std)):
        raise ValueError('X.ndim, F.ndim, len(dr), len(std) must be the same')
    Xv = conv_view(X, F.shape, dr, std)
    return np.tensordot(Xv, F, axes=X.ndim)

%timeit -n 100 -r 10 convolve_stride(A, F)
#31.2 ms ± 1.31 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)

I wrote this convolve_stride which uses numpy.lib.stride_tricks.as_strided. Moreover it supports both strides and dilation. It is also compatible to tensor with order > 2.

import numpy as np
from numpy.lib.stride_tricks import as_strided
from im2col import im2col

def conv_view(X, F_s, dr, std):
    X_s = np.array(X.shape)
    F_s = np.array(F_s)
    dr = np.array(dr)
    Fd_s = (F_s - 1) * dr + 1
    if np.any(Fd_s > X_s):
        raise ValueError('(Dilated) filter size must be smaller than X')
    std = np.array(std)
    X_ss = np.array(X.strides)
    Xn_s = (X_s - Fd_s) // std + 1
    Xv_s = np.append(Xn_s, F_s)
    Xv_ss = np.tile(X_ss, 2) * np.append(std, dr)
    return as_strided(X, Xv_s, Xv_ss, writeable=False)

def convolve_stride(X, F, dr=None, std=None):
    if dr is None:
        dr = np.ones(X.ndim, dtype=int)
    if std is None:
        std = np.ones(X.ndim, dtype=int)
    if not (X.ndim == F.ndim == len(dr) == len(std)):
        raise ValueError('X.ndim, F.ndim, len(dr), len(std) must be the same')
    Xv = conv_view(X, F.shape, dr, std)
    return np.tensordot(Xv, F, axes=X.ndim)

%timeit -n 100 -r 10 convolve_stride(A, F)
#31.2 ms ± 1.31 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
年华零落成诗 2024-09-01 08:48:08

尝试第一轮然后转换为 uint8:

data = data.round().astype(np.uint8);

Try to first round and then cast to uint8:

data = data.round().astype(np.uint8);
电影里的梦 2024-09-01 08:48:08

通常,二维卷积是一个误称。理想情况下,在引擎盖下,
所做的是两个矩阵的相关

垫==相同
返回与输入维度相同的输出

它也可以获取不对称图像。为了对一批二维矩阵执行相关(深度学习术语中的卷积),可以迭代所有通道,计算每个通道切片与相应滤波器切片的相关性。

例如:如果图像为 (28,28,3) 并且滤波器大小为 (5,5,3),则从图像通道中取出 3 个切片中的每一个,并使用上面的自定义函数执行互相关,然后堆叠结果矩阵在输出的相应维度中。

def get_cross_corr_2d(W, X, pad = 'valid'):

   if(pad == 'same'):
       pr = int((W.shape[0] - 1)/2)
       pc = int((W.shape[1] - 1)/2)
       conv_2d = np.zeros((X.shape[0], X.shape[1]))
       X_pad = np.zeros((X.shape[0] + 2*pr, X.shape[1] + 2*pc))
       X_pad[pr:pr+X.shape[0], pc:pc+X.shape[1]] = X
       for r in range(conv_2d.shape[0]):
           for c in range(conv_2d.shape[1]):
               conv_2d[r,c] = np.sum(np.inner(W, X_pad[r:r+W.shape[0], c:c+W.shape[1]]))
       return conv_2d
    
   else:    
       pr = W.shape[0] - 1
       pc = W.shape[1] - 1
       conv_2d = np.zeros((X.shape[0] - W.shape[0] + 2*pr + 1,
                           X.shape[1] - W.shape[1] + 2*pc + 1))
       X_pad = np.zeros((X.shape[0] + 2*pr, X.shape[1] + 2*pc))
       X_pad[pr:pr+X.shape[0], pc:pc+X.shape[1]] = X
       for r in range(conv_2d.shape[0]):
           for c in range(conv_2d.shape[1]):
               conv_2d[r,c] = np.sum(np.multiply(W, X_pad[r:r+W.shape[0], c:c+W.shape[1]]))
       return conv_2d

Typically, Convolution 2D is a misnomer. Ideally, under the hood,
whats being done is a correlation of 2 matrices.

pad == same
returns the output as the same as input dimension

It can also take asymmetric images. In order to perform correlation(convolution in deep learning lingo) on a batch of 2d matrices, one can iterate over all the channels, calculate the correlation for each of the channel slices with the respective filter slice.

For example: If image is (28,28,3) and filter size is (5,5,3) then take each of the 3 slices from the image channel and perform the cross correlation using the custom function above and stack the resulting matrix in the respective dimension of the output.

def get_cross_corr_2d(W, X, pad = 'valid'):

   if(pad == 'same'):
       pr = int((W.shape[0] - 1)/2)
       pc = int((W.shape[1] - 1)/2)
       conv_2d = np.zeros((X.shape[0], X.shape[1]))
       X_pad = np.zeros((X.shape[0] + 2*pr, X.shape[1] + 2*pc))
       X_pad[pr:pr+X.shape[0], pc:pc+X.shape[1]] = X
       for r in range(conv_2d.shape[0]):
           for c in range(conv_2d.shape[1]):
               conv_2d[r,c] = np.sum(np.inner(W, X_pad[r:r+W.shape[0], c:c+W.shape[1]]))
       return conv_2d
    
   else:    
       pr = W.shape[0] - 1
       pc = W.shape[1] - 1
       conv_2d = np.zeros((X.shape[0] - W.shape[0] + 2*pr + 1,
                           X.shape[1] - W.shape[1] + 2*pc + 1))
       X_pad = np.zeros((X.shape[0] + 2*pr, X.shape[1] + 2*pc))
       X_pad[pr:pr+X.shape[0], pc:pc+X.shape[1]] = X
       for r in range(conv_2d.shape[0]):
           for c in range(conv_2d.shape[1]):
               conv_2d[r,c] = np.sum(np.multiply(W, X_pad[r:r+W.shape[0], c:c+W.shape[1]]))
       return conv_2d
伪心 2024-09-01 08:48:08

此代码不正确:

for r in range(nr):
    data[r,:] = np.convolve(data[r,:], H_r, 'same')

for c in range(nc):
    data[:,c] = np.convolve(data[:,c], H_c, 'same')

请参阅从多维卷积到一维的 Nussbaumer 变换。

This code incorrect:

for r in range(nr):
    data[r,:] = np.convolve(data[r,:], H_r, 'same')

for c in range(nc):
    data[:,c] = np.convolve(data[:,c], H_c, 'same')

See Nussbaumer transformation from multidimentional convolution to one dimentional.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文