Vectorizing 2D Convolutions in NumPy

Question:

I know there are various optimized off-the-shelf functions available for performing 2D convolutions, but just for the sake of understanding, I am trying to implement my own 2D convolution function.

The following is what I done as of now:

    convoluted = []
    # TODO: Vectorize
    for i in range(0, M - m + 1):
        for j in range(0, N - n + 1):
            submatrix = x[i:i+m, j:j+n]
            convoluted.append(np.sum([submatrix*kernel]))
    convoluted = np.array(convoluted).reshape(M - m + 1, N - n + 1)

Note that x is the input image array, (M, N) is the shape of the input image, and (m, n) is the shape of the kernel used.

The 2 explicit for-loops are quite slow.
Is there any way to vectorize this? Any help is appreciated
(I know that python list comprehension can be used, at least for the inner loop, but I’m looking for a faster method, if any)

Asked By: user13492565

||

Answers:

I think you are looking for this:

from skimage.util.shape import view_as_windows
sub_matrices = view_as_windows(x, (m,n), 1)
convoluted = np.einsum('ij,klij->kl',kernel,sub_matrices)

First line creates windows of kernel size from your original array. Second line is simple multiplication and addition (similar to the line in your for loop, more elegant to remove loops for it). The output of this and your code should be the same. If you are set to not use any of them, let us know your limitations to edit the post.

Another similar approach without using skimage package (using only numpy but slightly longer code) is here.

Answered By: Ehsan

I build on Ehsan’s answer to get the shortest answer that only uses Numpy:

windows = np.lib.stride_tricks.sliding_window_view(img, kernel.shape)
convolved_img = np.einsum('ij,klij->kl',kernel,windows)
Answered By: Quan Nguyen