title: Convolve2d just by using Numpy
body: <p>I am studying image-processing using NumPy and facing a problem with filtering with convolution.</p>
  <p><strong>I would like to convolve a gray-scale image. (convolve a 2d Array with a smaller 2d Array)</strong></p>
  <p>Does anyone have an idea to <strong>refine</strong> my method?</p>
  <p>I know that <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html" rel="nofollow noreferrer">SciPy</a> supports convolve2d but I want to make a convolve2d only by using NumPy.</p>
  <h1>What I have done</h1>
  <p>First, I made a 2d array the submatrices.</p>
  <pre><code>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:]]])
  </code></pre>
  <p>the submatrices seems complicated but what I am doing is shown in the following drawing.</p>
  <p><a href="https://i.stack.imgur.com/VLRnQ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/VLRnQ.png" alt="submatrices" /></a></p>
  <p>Next, I multiplied each submatrices with a filter.</p>
  <pre><code>conv_filter = np.array([[0,-1,0],[-1,4,-1],[0,-1,0]])
  multiplied_subs = np.einsum('ij,ijkl-&gt;ijkl',conv_filter,submatrices)
  </code></pre>
  <p><a href="https://i.stack.imgur.com/lh8Ym.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/lh8Ym.png" alt="multiplied_subs" /></a></p>
  <p>and summed them.</p>
  <pre><code>np.sum(np.sum(multiplied_subs, axis = -3), axis = -3)
  #array([[ 6,  7,  8],
  #       [11, 12, 13],
  #       [16, 17, 18]])
  </code></pre>
  <p>Thus this procedure can be called my convolve2d.</p>
  <pre><code>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-&gt;ijkl',conv_filter,submatrices)
      return np.sum(np.sum(multiplied_subs, axis = -3), axis = -3)
  </code></pre>
  <p>However, I find this my_convolve2d troublesome for 3 reasons.</p>
  <ol>
  <li>Generation of the submatrices is too awkward that is difficult to read and can only be used when the filter is 3*3</li>
  <li>The size of the variant submatrices seems to be too big, since it is approximately 9 folds bigger than the original matrix.</li>
  <li>The summing seems a little non intuitive. Simply said, ugly.</li>
  </ol>
  <p>Thank you for reading this far.</p>
  <p>Kind of update. I wrote a conv3d for myself. I will leave this as a public domain.</p>
  <pre><code>def convolve3d(img, kernel):
      # calc the size of the array of submatrices
      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 submatrices
      submatrices = strd(img,kernel.shape + sub_shape,img.strides * 2)
  
      # sum the submatrices and kernel
      convolved_matrix = np.einsum('hij,hijklm-&gt;klm', kernel, submatrices)
  
      return convolved_matrix
  </code></pre>