web-dev-qa-db-fra.com

Convolve2d juste en utilisant Numpy

J'étudie le traitement d'image avec Numpy et je fais face à un problème de filtrage avec convolution.

Je voudrais convoluer une image en niveaux de gris. (convolution d'un tableau 2D avec un tableau 2D plus petit)

Quelqu'un a-t-il une idée pour affiner ma méthode?

Je sais que scipy supporte convolve2d mais je veux faire un convolve2d uniquement en utilisant Numpy.

Ce que j'ai fait

Tout d'abord, j'ai fait un tableau 2D des sous-matrices.

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:]]])

les sous-matrices semblent compliquées mais ce que je fais est montré dans le dessin suivant.

submatrices

Ensuite, j'ai multiplié chaque sous-matrices avec un filtre.

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

multiplied_subs

et les a résumés.

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

Ainsi, cette procédure peut être appelée mon 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)

Cependant, je trouve cela my_convolve2d gênant pour 3 raisons.

  1. La génération des sous-matrices est trop maladroite, difficile à lire et ne peut être utilisée que lorsque le filtre est de 3 * 3
  2. La taille des différentes sous-matrices semble être trop grande, car elle est environ 9 fois plus grande que la matrice d'origine.
  3. La sommation semble un peu non intuitive. Dit simplement, moche.

Merci d'avoir lu jusqu'ici.

Type de mise à jour. J'ai écrit un conv3d pour moi. Je laisserai cela comme un domaine public.

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
12
Allosteric

Vous pouvez générer les sous-réseaux à l'aide de as_strided[1] :

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)

Pour vous débarrasser de votre deuxième somme "laide", modifiez votre einsum pour que le tableau de sortie ne contienne que j et k. Cela implique votre deuxième sommation.

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]]
11
Crispin

Vous pouvez également utiliser fft (l'une des méthodes les plus rapides pour effectuer des circonvolutions)

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

cheers, Dan

6
Dan Erez

Nettoyé à l'aide de as_strided et l'astuce _Crispin einsum d'en haut. Applique la taille du filtre à la forme développée. Devrait même autoriser des entrées non carrées si les indices sont compatibles.

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)
3
Daniel F