web-dev-qa-db-fra.com

Noyau de filtre Sobel de grande taille

J'utilise un filtre sobel de taille 3x3 pour calculer la dérivée d'image. En regardant certains articles sur Internet, il semble que les noyaux pour le filtre sobel pour les tailles 5x5 et 7x7 soient également courants, mais je ne suis pas en mesure de trouver leurs valeurs de noyau.

Quelqu'un pourrait-il me faire savoir les valeurs du noyau pour le filtre sobel de taille 5x5 et 7x7? De plus, si quelqu'un pouvait partager une méthode pour générer les valeurs du noyau, ce serait très utile.

Merci d'avance.

39
Aarkan

MISE À JOUR 23-avr.-2018: il semble que les noyaux définis dans le lien ci-dessous ne sont pas de véritables noyaux Sobel (pour 5x5 et plus) - ils peuvent faire un travail raisonnable de détection Edge, mais ils ne devrait pas être appelé noyaux Sobel. Voir réponse de Daniel pour un résumé plus précis et complet. (Je laisserai cette réponse ici car (a) elle est liée à divers endroits et (b) les réponses acceptées ne peuvent pas être facilement supprimées.)

Google semble produire de nombreux résultats, par exemple http://rsbweb.nih.gov/nih-image/download/user-macros/slowsobel.macro suggère les noyaux suivants pour 3x3, 5x5, 7x7 et 9x9:

3x3:

1   0   -1
2   0   -2
1   0   -1

5x5:

2   1   0   -1  -2
3   2   0   -2  -3
4   3   0   -3  -4
3   2   0   -2  -3
2   1   0   -1  -2

7x7:

3   2   1   0   -1  -2  -3
4   3   2   0   -2  -3  -4
5   4   3   0   -3  -4  -5
6   5   4   0   -4  -5  -6
5   4   3   0   -3  -4  -5
4   3   2   0   -2  -3  -4
3   2   1   0   -1  -2  -3

9x9:

4   3   2   1   0   -1  -2  -3  -4
5   4   3   2   0   -2  -3  -4  -5
6   5   4   3   0   -3  -4  -5  -6
7   6   5   4   0   -4  -5  -6  -7
8   7   6   5   0   -5  -6  -7  -8
7   6   5   4   0   -4  -5  -6  -7
6   5   4   3   0   -3  -4  -5  -6
5   4   3   2   0   -2  -3  -4  -5
4   3   2   1   0   -1  -2  -3  -4
15
Paul R

D'autres sources semblent donner des définitions différentes des noyaux plus gros. La bibliothèque Intel IPP , par exemple, donne le noyau 5x5 comme

1  2 0  -2 -1
4  8 0  -8 -4
6 12 0 -12 -6
4  8 0  -8 -4
1  2 0  -2 -1

Intuitivement, cela a plus de sens pour moi parce que vous prêtez plus d'attention aux éléments plus proches du centre. Il a également une définition naturelle en termes de noyau 3x3 qui est facile à étendre pour générer des noyaux plus gros. Cela dit, dans ma brève recherche, j'ai trouvé 3 définitions différentes du noyau 5x5 - donc je soupçonne que (comme Paul le dit) les noyaux plus gros sont ad hoc, et ce n'est donc en aucun cas la réponse définitive.

Le noyau 3x3 est le produit externe d'un noyau de lissage et d'un noyau de gradient, dans Matlab c'est quelque chose comme

sob3x3 = [ 1 2 1 ]' * [1 0 -1]

les noyaux plus gros peuvent être définis en convoluant le noyau 3x3 avec un autre noyau de lissage

sob5x5 = conv2( [ 1 2 1 ]' * [1 2 1], sob3x3 )

vous pouvez répéter le processus pour obtenir des noyaux progressivement plus gros

sob7x7 = conv2( [ 1 2 1 ]' * [1 2 1], sob5x5 )
sob9x9 = conv2( [ 1 2 1 ]' * [1 2 1], sob7x7 )
...

il y a beaucoup d'autres façons de l'écrire, mais je pense que cela explique exactement ce qui se passe le mieux. Fondamentalement, vous commencez avec un noyau de lissage dans une direction et une estimation des différences finies du dérivé dans l'autre, puis appliquez simplement le lissage jusqu'à ce que vous obteniez la taille de noyau souhaitée.

Parce qu'il ne s'agit que d'une série de convolutions, toutes les propriétés Nice sont valables (commutativité, associativité, etc.), ce qui pourrait être utile pour votre implémentation. Par exemple, vous pouvez séparer trivialement le noyau 5x5 en ses composants de lissage et dérivés:

sob5x5 = conv([1 2 1],[1 2 1])' * conv([1 2 1],[-1 0 1])

Notez que pour être un estimateur dérivé "correct", le Sobel 3x3 doit être mis à l'échelle par un facteur de 1/8:

sob3x3 = 1/8 * [ 1 2 1 ]' * [1 0 -1]

et chaque noyau plus gros doit être mis à l'échelle par un facteur supplémentaire de 1/16 (car les noyaux de lissage ne sont pas normalisés):

sob5x5 = 1/16 * conv2( [ 1 2 1 ]' * [1 2 1], sob3x3 )
sob7x7 = 1/16 * conv2( [ 1 2 1 ]' * [1 2 1], sob5x5 )
...
36
Adam Bowen

Solution complète pour les tailles et angles arbitraires du noyau Sobel

tl; dr: passez à la section 'Exemples'

Pour ajouter une autre solution, développez ce document (ce n'est pas particulièrement de haute qualité, mais il montre des graphiques et des matrices utilisables à partir du bas de la page 2).

Objectif

Ce que nous essayons de faire est d'estimer le gradient local de l'image à la position (x, y). Le gradient est un vecteur composé des composants dans les directions x et y, gx et gy.

Maintenant, imaginez que nous voulons approximer le gradient en fonction de notre pixel (x, y) et de ses voisins en tant qu'opération de noyau (3x3, 5x5, ou n'importe quelle taille).

Idée de solution

Nous pouvons approximer le gradient en additionnant les projections de toutes les paires voisin-centre sur la direction du gradient. (Le noyau de Sobel n'est qu'une méthode particulière de pondération des différentes contributions, tout comme Prewitt, essentiellement).

Étapes intermédiaires explicites pour 3x3

Ceci est l'image locale, pixel central (x, y) marqué comme "o" (centre)

a b c
d o f
g h i

Disons que nous voulons le gradient dans la direction x positive. Le vecteur unitaire dans la direction x positive est (1,0) [J'utiliserai plus tard la convention selon laquelle la direction y positive est BAS, c'est-à-dire (0,1), et que (0,0) est en haut à gauche de l'image) .]

Le vecteur de o à f ('of' pour faire court) est (1,0). Le gradient dans la direction "de" est (f - o)/1 (la valeur de l'image au pixel est ici notée f moins la valeur au centre o, divisée par la distance entre ces pixels). Si nous projetons le vecteur unitaire de ce gradient voisin particulier sur notre direction de gradient souhaitée (1,0) via un produit scalaire, nous obtenons 1. Voici un petit tableau avec les contributions de tous les voisins, en commençant par les cas les plus faciles. Notez que pour les diagonales, leur distance est sqrt2, et les vecteurs unitaires dans les directions diagonales sont 1/sqrt2 * (+/- 1, +/- 1)

f:   (f-o)/1     * 1
d:   (d-o)/1     * -1       because (-1, 0) dot (1, 0) = -1
b:   (b-o)/1     * 0        because (0, -1) dot (1, 0) = 0
h:   (h-o)/1     * 0        (as per b)
a:   (a-o)/sqrt2 * -1/sqrt2 distance is sqrt2, and 1/sqrt2*(-1,-1) dot (1,0) = -1/sqrt2
c:   (c-o)/sqrt2 * +1/sqrt2   ...
g:   (g-o)/sqrt2 * -1/sqrt2   ...
i:   (i-o)/sqrt2 * +1/sqrt2   ...

modifier pour clarification: Il existe deux facteurs de 1/sqrt (2) pour la raison suivante:

  1. Nous sommes intéressés par la contribution au gradient dans une direction spécifique (ici x), nous devons donc projeter le gradient directionnel du pixel central au pixel voisin sur la direction qui nous intéresse Ceci est accompli en prenant le produit scalaire des vecteurs unitaires dans les directions respectives, ce qui introduit le premier facteur 1/L (ici 1/sqrt (2) pour les diagonales).

  2. Le gradient mesure le changement infinitésimal en un point, que nous approchons par différences finies. En termes d'équation linéaire, m = (y2-y1)/(x2-x1). Pour cette raison, la différence de valeur entre le pixel central et le pixel voisin (y2-y1) doit être répartie sur leur distance (correspond à x2-x1) afin d'obtenir les unités de remontée par unité de distance. Cela donne un deuxième facteur de 1/L (ici 1/sqrt (2) pour les diagonales)

Ok, maintenant nous connaissons les contributions. Simplifions cette expression en combinant des paires de contributions de pixels opposées. Je vais commencer par d et f:

{(f-o)/1 * 1} + {(d-o)/1 * -1}
= f - o - (d - o)
= f - d

Maintenant, la première diagonale:

{(c-o)/sqrt2 * 1/sqrt2} + {(g-o)/sqrt2 * -1/sqrt2}
= (c - o)/2 - (g - o)/2
= (c - g)/2

La deuxième diagonale contribue (i - a)/2. La direction perpendiculaire contribue à zéro. Notez que toutes les contributions du pixel central "o" disparaissent.

Nous avons maintenant calculé les contributions de tous les voisins les plus proches au gradient dans la direction x positive au pixel (x, y), donc notre approximation totale du gradient dans la direction x est simplement leur somme:

gx(x,y) = f - d + (c - g)/2 + (i - a)/2

On peut obtenir le même résultat en utilisant un noyau de convolution où les coefficients sont écrits à la place du pixel voisin correspondant:

-1/2  0  1/2
 -1   0   1
-1/2  0  1/2

Si vous ne voulez pas traiter les fractions, vous multipliez cela par 2 et obtenez le fameux noyau Sobel 3x3.

      -1 0 1
G_x = -2 0 2
      -1 0 1

La multiplication par deux ne sert qu'à obtenir des entiers pratiques. La mise à l'échelle de votre image de sortie est essentiellement arbitraire, la plupart du temps vous la normalisez à votre plage d'image, de toute façon (pour obtenir des résultats clairement visibles).

Par le même raisonnement que ci-dessus, vous obtenez le noyau du gradient vertical gy en projetant les contributions voisines sur le vecteur unitaire dans la direction y positive (0,1)

      -1 -2 -1
G_y =  0  0  0
       1  2  1

Formule pour les noyaux de taille arbitraire

Si vous voulez des noyaux 5x5 ou plus, il vous suffit de faire attention aux distances, par ex.

A B 2 B A
B C 1 C B
2 1 - 1 2
B C 1 C B
A B 2 B A

A = 2 * sqrt2
B = sqrt5
C = sqrt2.

Si la longueur du vecteur reliant deux pixels quelconques est L, le vecteur unitaire dans cette direction a un préfacteur de 1/L. Pour cette raison, les contributions de tout pixel "k" au (par exemple) le gradient x (1,0) peuvent être simplifiées à "(différence de valeur sur une distance au carré) fois (DotProduct du vecteur de direction non normalisé" ok "avec le vecteur de gradient , par exemple (1,0)) "

gx_k = (k - o)/(pixel distance^2) ['ok' dot (1,0)].

Étant donné que le produit scalaire du vecteur de connexion avec le vecteur d'unité x sélectionne l'entrée de vecteur correspondante, l'entrée de noyau G_x correspondante à la position k est juste

i / (i*i + j*j)

où i et j sont le nombre de pas du pixel central au pixel k dans les directions x et y. Dans le calcul 3x3 ci-dessus, le pixel "a" aurait i = -1 (1 à gauche), j = -1 (1 en haut) et donc l'entrée de noyau "a" est -1/(1 + 1 ) = -1/2.

Les entrées pour le noyau G_y sont

j/(i*i + j*j). 

Si je veux des valeurs entières pour mon noyau, je procède comme suit:

  • vérifier la plage disponible de l'image de sortie
  • calculer le résultat le plus élevé possible en appliquant le noyau à virgule flottante (c'est-à-dire supposer la valeur d'entrée maximale sous toutes les entrées de noyau positives, donc la valeur de sortie est (somme de toutes les valeurs de noyau positives) * (valeur d'image d'entrée maximale possible). Si vous avez signé l'entrée, vous devez pour prendre également en compte les valeurs négatives. Le pire des cas est alors la somme de toutes les valeurs positives + la somme de toutes les valeurs abs des entrées négatives (si entrée max sous positifs, -max entrée sous négatifs). edit: la somme de toutes les valeurs abs a également appelé à juste titre le weight du noyau
  • calculer la mise à l'échelle maximale autorisée pour le noyau (sans plage de débordement d'image de sortie)
  • pour tous les multiples entiers (de 2 au maximum au-dessus) du noyau à virgule flottante: vérifiez qui a la somme la plus faible d'erreurs d'arrondi absolu et utilisez ce noyau

Donc en résumé:

Gx_ij = i / (i*i + j*j)
Gy_ij = j / (i*i + j*j)

où i, j est la position dans le noyau comptée à partir du centre. Mettez à l'échelle les entrées du noyau selon vos besoins pour obtenir des nombres entiers (ou au moins des approximations proches).

Ces formules sont valables pour toutes les tailles de noyau.

Exemples

          -2/8 -1/5  0  1/5  2/8           -5  -4  0   4   5
          -2/5 -1/2  0  1/2  2/5           -8 -10  0  10   8
G_x (5x5) -2/4 -1/1  0  1/1  2/4  (*20) = -10 -20  0  20  10
          -2/5 -1/2  0  1/2  2/5           -8 -10  0  10   8
          -2/8 -1/5  0  1/5  2/8           -5  -4  0   4   5

Notez que les pixels 3x3 centraux du noyau 5x5 en notation flottante ne sont que le noyau 3x3, c'est-à-dire que les noyaux plus grands représentent une approximation continue avec des données supplémentaires mais moins pondérées. Cela continue sur des tailles de noyau plus grandes:

           -3/18 -2/13 -1/10 0  1/10 2/13 3/18
           -3/13 -2/8  -1/5  0  1/5  2/8  3/13
           -3/10 -2/5  -1/2  0  1/2  2/5  3/10
G_x (7x7)  -3/9  -2/4  -1/1  0  1/1  2/4  3/9 
           -3/10 -2/5  -1/2  0  1/2  2/5  3/10
           -3/13 -2/8  -1/5  0  1/5  2/8  3/13
           -3/18 -2/13 -1/10 0  1/10 2/13 3/18

Les représentations entières exactes deviennent impraticables à ce stade.

Pour autant que je sache (je n'ai pas accès à l'article original), la partie "Sobel" de ceci pondère correctement les contributions. La solution de Prewitt peut être obtenue en omettant la pondération de distance et en entrant simplement i et j dans le noyau selon le cas.

Bonus: Sobel Kernels pour des directions arbitraires

Nous pouvons donc approximer les composantes x et y du gradient d'image (qui est en fait un vecteur, comme indiqué au tout début). Le gradient dans n'importe quelle direction arbitraire alpha (mesuré mathématiquement positif, dans ce cas dans le sens horaire puisque le y positif est vers le bas) peut être obtenu en projetant le vecteur de gradient sur le vecteur d'unité de gradient alpha.

Le vecteur alpha est (cos alpha, sin alpha). Pour alpha = 0 ° vous pouvez obtenir le résultat pour gx, pour alpha = 90 ° vous obtenez gy.

g_alpha = (alpha-unit vector) dot (gx, gy)
        = (cos a, sin a) dot (gx, gy)
        = cos a * gx + sin a * gy

Si vous prenez la peine d'écrire gx et gy en tant que sommes de contributions de voisins, vous vous rendez compte que vous pouvez grouper l'expression longue résultante par des termes qui s'appliquent au même pixel voisin, puis réécrire ceci en un seul noyau de convolution avec des entrées

G_alpha_ij = (i * cos a + j * sin a)/(i*i + j*j)

Si vous souhaitez l'approximation de l'entier le plus proche, suivez les étapes décrites ci-dessus.

26
Daniel

Générateur de filtres à gradient Sobel

(Cette réponse fait référence à analyse donnée par @Daniel, ci-dessus.)

Gx[i,j] = i / (i*i + j*j)

Gy[i,j] = j / (i*i + j*j)

Il s'agit d'un résultat important et d'une meilleure explication que celle que l'on trouve dans le document original . Il devrait être écrit dans Wikipedia , ou quelque part, car il semble également supérieur à toute autre discussion du problème disponible sur Internet.

Cependant, il n'est pas vrai que les représentations à valeur entière ne soient pas pratiques pour les filtres de taille supérieure à 5 * 5, comme revendiqué. En utilisant des entiers 64 bits, les tailles de filtre Sobel jusqu'à 15 * 15 peuvent être exprimées exactement.

Voici les quatre premiers; le résultat doit être divisé par le "poids", de sorte que le gradient d'une région d'image telle que la suivante, soit normalisé à une valeur de 1.

1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5

Gx (3):

-1/2  0/1  1/2           -1  0  1
-1/1    0  1/1   * 2 =   -2  0  2
-1/2  0/1  1/2           -1  0  1

weight = 4               weight = 8

Gx (5):

-2/8 -1/5  0/4  1/5  2/8             -5  -4   0   4   5
-2/5 -1/2  0/1  1/2  2/5             -8 -10   0  10   8
-2/4 -1/1    0  1/1  2/4   * 20 =   -10 -20   0  20  10
-2/5 -1/2  0/1  1/2  2/5             -8 -10   0  10   8
-2/8 -1/5  0/4  1/5  2/8             -5  -4   0   4   5

weight = 12                          weight = 240

Gx (7):

-3/18 -2/13 -1/10   0/9  1/10  2/13  3/18             -130 -120  -78    0   78  120  130
-3/13  -2/8  -1/5   0/4   1/5   2/8  3/13             -180 -195 -156    0  156  195  180
-3/10  -2/5  -1/2   0/1   1/2   2/5  3/10             -234 -312 -390    0  390  312  234
 -3/9  -2/4  -1/1     0   1/1   2/4   3/9   * 780 =   -260 -390 -780    0  780  390  260
-3/10  -2/5  -1/2   0/1   1/2   2/5  3/10             -234 -312 -390    0  390  312  234
-3/13  -2/8  -1/5   0/4   1/5   2/8  3/13             -180 -195 -156    0  156  195  180
-3/18 -2/13 -1/10   0/9  1/10  2/13  3/18             -130 -120  -78    0   78  120  130

weight = 24                                           weight = 18720

Gx (9):

-4/32 -3/25 -2/20 -1/17  0/16  1/17  2/20  3/25  4/32                -16575  -15912  -13260   -7800       0    7800   13260   15912   16575
-4/25 -3/18 -2/13 -1/10   0/9  1/10  2/13  3/18  4/25                -21216  -22100  -20400  -13260       0   13260   20400   22100   21216
-4/20 -3/13  -2/8  -1/5   0/4   1/5   2/8  3/13  4/20                -26520  -30600  -33150  -26520       0   26520   33150   30600   26520
-4/17 -3/10  -2/5  -1/2   0/1   1/2   2/5  3/10  4/17                -31200  -39780  -53040  -66300       0   66300   53040   39780   31200
-4/16  -3/9  -2/4  -1/1     0   1/1   2/4   3/9  4/16   * 132600 =   -33150  -44200  -66300 -132600       0  132600   66300   44200   33150
-4/17 -3/10  -2/5  -1/2   0/1   1/2   2/5  3/10  4/17                -31200  -39780  -53040  -66300       0   66300   53040   39780   31200
-4/20 -3/13  -2/8  -1/5   0/4   1/5   2/8  3/13  4/20                -26520  -30600  -33150  -26520       0   26520   33150   30600   26520
-4/25 -3/18 -2/13 -1/10   0/9  1/10  2/13  3/18  4/25                -21216  -22100  -20400  -13260       0   13260   20400   22100   21216
-4/32 -3/25 -2/20 -1/17  0/16  1/17  2/20  3/25  4/32                -16575  -15912  -13260   -7800       0    7800   13260   15912   16575

weight = 40                                                          weight = 5304000

Le programme Ruby en annexe ci-dessous, calculera les filtres Sobel et les poids correspondants de toute taille, bien que les filtres à valeur entière ne soient pas susceptibles d'être utiles pour des tailles supérieures à 15 * 15.

#!/usr/bin/Ruby

# Sobel image gradient filter generator
# by <[email protected]> -- Sept 2017
# reference:
# https://stackoverflow.com/questions/9567882/sobel-filter-kernel-of-large-size


if (s = ARGV[0].to_i) < 3 || (s % 2) == 0
    $stderr.puts "invalid size"
    exit false
end

s /= 2


n = 1

# find least-common-multiple of all fractional denominators
(0..s).each { |j|
    (1..s).each { |i|
        d = i*i + j*j
        n = n.lcm(d / d.gcd(i))
    }
}


fw1 = format("%d/%d", s, 2*s*s).size + 2
fw2 = format("%d", n).size + 2


weight = 0
s1 = ""
s2 = ""

(-s..s).each { |y|
    (-s..s).each { |x|
        i, j = x, y   # "i, j = y, x" for transpose
        d = i*i + j*j
        if (i != 0)
            if (n * i % d) != 0   # this should never happen
                $stderr.puts "inexact division: #{n} * #{i} / ((#{i})^2 + (#{j})^2)"
                exit false
            end
            w = n * i / d
            weight += i * w
        else
            w = 0
        end
        s1 += "%*s" % [fw1, d > 0 ? "%d/%d" % [i, d] : "0"]
        s2 += "%*d" % [fw2, w]
    }
    s1 += "\n" ; s2 += "\n"
}


f = n.gcd(weight)

puts s1

puts "\nweight = %d%s" % [weight/f, f < n ? "/%d" % (n/f) : ""]

puts "\n* #{n} =\n\n"

puts s2

puts "\nweight = #{weight}"
3
Ian Bruce

J'ai rapidement piraté un algorithme pour générer un noyau Sobel de toute taille impaire> 1, basé sur les exemples donnés par @Paul R:

    public static void CreateSobelKernel(int n, ref float[][] Kx, ref float[][] Ky)
    {
        int side = n * 2 + 3;
        int halfSide = side / 2;
        for (int i = 0; i < side; i++)
        {
            int k = (i <= halfSide) ? (halfSide + i) : (side + halfSide - i - 1);
            for (int j = 0; j < side; j++)
            {
                if (j < halfSide)
                    Kx[i][j] = Ky[j][i] = j - k;
                else if (j > halfSide)
                    Kx[i][j] = Ky[j][i] = k - (side - j - 1);
                else
                    Kx[i][j] = Ky[j][i] = 0;
            }
        }
    }

J'espère que cela aide.

2
Pedro Boechat

Voici une solution simple faite avec python 3 en utilisant numpy et la réponse @Daniel.

def custom_sobel(shape, axis):
    """
    shape must be odd: eg. (5,5)
    axis is the direction, with 0 to positive x and 1 to positive y
    """
    k = np.zeros(shape)
    p = [(j,i) for j in range(shape[0]) 
           for i in range(shape[1]) 
           if not (i == (shape[1] -1)/2. and j == (shape[0] -1)/2.)]

    for j, i in p:
        j_ = int(j - (shape[0] -1)/2.)
        i_ = int(i - (shape[1] -1)/2.)
        k[j,i] = (i_ if axis==0 else j_)/float(i_*i_ + j_*j_)
    return k

Il retourne le noyau (5,5) comme ceci:

Sobel x:
   [[-0.25 -0.2   0.    0.2   0.25]
    [-0.4  -0.5   0.    0.5   0.4 ]
    [-0.5  -1.    0.    1.    0.5 ]
    [-0.4  -0.5   0.    0.5   0.4 ]
    [-0.25 -0.2   0.    0.2   0.25]]


Sobel y:
   [[-0.25 -0.4  -0.5  -0.4  -0.25]
    [-0.2  -0.5  -1.   -0.5  -0.2 ]
    [ 0.    0.    0.    0.    0.  ]
    [ 0.2   0.5   1.    0.5   0.2 ]
    [ 0.25  0.4   0.5   0.4   0.25]]

Si quelqu'un connaît une meilleure façon de le faire en python, faites-le moi savoir. Je suis encore débutant;)

2
Joao Ponte

Comme Adam Bowen a expliqué dans sa réponse , le noyau Sobel est une combinaison d'un lissage le long d'un axe et d'une dérivée de différence centrale le long de l'autre axe:

sob3x3 = [1 2 1]' * [1 0 -1]

Le lissage ajoute de la régularisation (réduit la sensibilité au bruit).

(Je laisse de côté tous les facteurs 1/8 dans cet article, comme l'a fait Sobel lui-même , ce qui signifie que l'opérateur détermine la dérivée jusqu'à l'échelle. Aussi, * signifie toujours convolution dans ce post.)

Généralisons ceci:

deriv_kernel = smoothing_kernel * d/dx

L'une des propriétés de la convolution est que

d/dx f = d/dx * f

C'est-à-dire que la convolution d'une image avec l'opérateur de dérivée élémentaire donne la dérivée de l'image. Notant également que la convolution est commutative,

deriv_kernel = d/dx * smoothing_kernel = d/dx smoothing_kernel

Autrement dit, le noyau dérivé est le dérivé d'un noyau de lissage.

Notez que l'application d'un tel noyau à une image par convolution:

image * deriv_kernel = image * smoothing_kernel * d/dx = d/dx (image * smoothing_kernel)

Autrement dit, avec ce noyau dérivé généralisé et idéalisé, nous pouvons calculer le véritable dérivé de l'image lissée. Ce n'est bien sûr pas le cas avec le noyau Sobel, car il utilise une approximation de différence centrale à la dérivée. Mais choisir un meilleur smoothing_kernel, cela peut être réalisé. Le noyau gaussien est ici l'option idéale, car il offre le meilleur compromis entre compacité dans le domaine spatial (faible encombrement du noyau) et compacité dans le domaine fréquentiel (bon lissage). De plus, le gaussien est parfaitement isotrope et séparable. L'utilisation d'un noyau dérivé gaussien donne le meilleur opérateur dérivé régularisé possible.

Ainsi, si vous recherchez un opérateur Sobel plus grand, parce que vous avez besoin de plus de régularisation, utilisez plutôt un opérateur dérivé gaussien.


Analysons un peu plus le noyau Sobel.

Le noyau de lissage est triangulaire, avec des échantillons [1 2 1]. Il s'agit d'une fonction triangulaire qui, échantillonnée, conduit à ces trois valeurs:

      2 + x ,   if -2 < x < 0
h = { 2     ,   if x = 0
      2 - x ,   if 0 < x < 2

Sa dérivée est:

            1 ,   if -2 < x < 0
d/dx h = {  0 ,   if x = 0        (not really, but it's the sensible solution)
           -1 ,   if 0 < x < 2

Ainsi, nous pouvons voir que l'approximation de la dérivée de différence centrale peut être considérée comme un échantillonnage de la dérivée analytique de la même fonction triangulaire utilisée pour le lissage. Nous avons donc:

sob3x3 = [1 2 1]' * d/dx [1 2 1] = d/dx ( [1 2 1]' * [1 2 1] )

Donc, si vous voulez agrandir ce noyau, agrandissez simplement le noyau de lissage:

sob5x5 = d/dx ( [1 2 3 2 1]' * [1 2 3 2 1] ) = [1 2 3 2 1]' * [1 1 0 -1 -1]

sob7x7 = d/dx ( [1 2 3 4 3 2 1]' * [1 2 3 4 3 2 1] ) = [1 2 3 4 3 2 1]' * [1 1 1 0 -1 -1 -1]

Ceci est assez différent du conseil donné par Adam Bowen , qui suggère de convoluer le noyau avec le noyau triangulaire à 3 onglets le long de chaque dimension: [1 2 1] * [1 2 1] = [1 4 6 4 1], et [1 2 1] * [1 0 -1] = [1 2 0 -2 -1]. Notez qu'en raison du théorème de la limite centrale, la convolution de ce noyau triangulaire avec lui-même conduit à un filtre qui se rapproche un peu plus du gaussien. Plus le noyau que nous créons par convolutions répétées avec lui-même est grand, plus nous approchons de ce gaussien. Ainsi, au lieu d'utiliser cette méthode, vous pourriez aussi bien échantillonner directement la fonction gaussienne.

Daniel a un long post dans lequel il suggère d'étendre le noyau Sobel d'une autre manière encore. La forme du noyau de lissage diverge ici de l'approximation gaussienne, je n'ai pas essayé d'étudier ses propriétés.

Notez qu'aucune de ces trois extensions possibles du noyau Sobel n'est en fait un noyau Sobel, car le noyau Sobel est explicitement un noyau 3x3 ( voir une note historique de Sobel sur son opérateur , qu'il n'a jamais réellement publié) .

Notez également que je ne préconise pas le noyau Sobel étendu dérivé ici. Utilisez des dérivés gaussiens!

1
Cris Luengo

Merci pour tous, je vais essayer la deuxième variante de @Adam Bowen, prendre le code C # pour Sobel5x5, 7x7, 9x9 ... génération de matrice pour cette variante (peut-être avec des bugs, si vous trouvez un bug ou pouvez optimiser le code - écrivez-le ici):

    static void Main(string[] args)
    {
        float[,] Sobel3x3 = new float[,] {
            {-1, 0, 1},
            {-2, 0, 2},
            {-1, 0, 1}};

        float[,] Sobel5x5 = Conv2DforSobelOperator(Sobel3x3);
        float[,] Sobel7x7 = Conv2DforSobelOperator(Sobel5x5);
        Console.ReadKey();
    }

    public static float[,] Conv2DforSobelOperator(float[,] Kernel)
    {
        if (Kernel == null)
            throw new Exception("Kernel = null");

        if (Kernel.GetLength(0) != Kernel.GetLength(1))
            throw new Exception("Kernel matrix must be Square matrix!");

        float[,] BaseMatrix = new float[,] {
            {1, 2, 1},
            {2, 4, 2},
            {1, 2, 1}};

        int KernelSize = Kernel.GetLength(0);
        int HalfKernelSize = KernelSize / 2;
        int OutSize = KernelSize + 2;

        if ((KernelSize & 1) == 0) // Kernel_Size must be: 3, 5, 7, 9 ...
            throw new Exception("Kernel size must be odd (3x3, 5x5, 7x7...)");

        float[,] Out = new float[OutSize, OutSize];
        float[,] InMatrix = new float[OutSize, OutSize];

        for (int x = 0; x < BaseMatrix.GetLength(0); x++)
            for (int y = 0; y < BaseMatrix.GetLength(1); y++)
                InMatrix[HalfKernelSize + x, HalfKernelSize + y] = BaseMatrix[x, y];

        for (int x = 0; x < OutSize; x++)
            for (int y = 0; y < OutSize; y++)
                for (int Kx = 0; Kx < KernelSize; Kx++)
                    for (int Ky = 0; Ky < KernelSize; Ky++)
                    {
                        int X = x + Kx - HalfKernelSize;
                        int Y = y + Ky - HalfKernelSize;

                        if (X >= 0 && Y >= 0 && X < OutSize && Y < OutSize)
                            Out[x, y] += InMatrix[X, Y] * Kernel[KernelSize - 1 - Kx, KernelSize - 1 - Ky];
                    }
        return Out;
    }

Résultats (NormalMap) ou il y copie , où ce métod - №2, @Paul R metod - №1. Maintenant, j'utilise le dernier, car il donne un résultat plus fluide et il est facile de générer des noyaux avec le code this .

0
Alex Green

Implémentation Matlab de réponse de Daniel :

kernel_width = 9;

halfway = floor(kernel_width/2);
step = -halfway : halfway;

i_matrix = repmat(step,[kernel_width 1]);
j_matrix = i_matrix';

gx = i_matrix ./ ( i_matrix.*i_matrix + j_matrix.*j_matrix );
gx(halfway+1,halfway+1) = 0; % deals with NaN in middle

gy = gx';
0
Jolene_F