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