image-processing - filtro - sobel image processing
Kernel de filtro Sobel de gran tamaño (8)
Estoy usando un filtro Sobel de tamaño 3x3 para calcular la derivada de la imagen. Al mirar algunos artículos en Internet, parece que los kernels para filtro sobel para tamaños 5x5 y 7x7 también son comunes, pero no puedo encontrar sus valores de kernel.
¿Podría alguien decirme los valores del kernel para el filtro sobel de tamaño 5x5 y 7x7? Además, si alguien puede compartir un método para generar los valores del kernel, será muy útil.
Gracias por adelantado.
Generador de filtro de gradiente Sobel
(Esta respuesta se refiere al analysis dado por @Daniel, arriba).
Gx[i,j] = i / (i*i + j*j)
Gy[i,j] = j / (i*i + j*j)
Este es un resultado importante y una mejor explicación que la que se puede encontrar en el documento original . Debe escribirse en Wikipedia , o en alguna parte, porque también parece superior a cualquier otra discusión sobre el tema disponible en Internet.
Sin embargo, no es realmente cierto que las representaciones de valores enteros sean poco prácticas para filtros de tamaño superior a 5 * 5, como se reivindica. Usando enteros de 64 bits, los tamaños de filtro Sobel de hasta 15 * 15 se pueden expresar exactamente.
Aquí están los primeros cuatro; el resultado debe dividirse por el "peso", de modo que el gradiente de una región de imagen como el siguiente, se normalice a un valor 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
El programa Ruby que se incluye a continuación, calculará los filtros Sobel y los pesos correspondientes de cualquier tamaño, aunque los filtros de valores enteros probablemente no sean útiles para tamaños superiores a 15 * 15.
#!/usr/bin/ruby
# Sobel image gradient filter generator
# by <[email protected]> -- Sept 2017
# reference:
# https://.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}"
Solución completa para tamaños y ángulos de kernel Sobel arbitrarios
tl; dr: saltar a la sección ''Ejemplos''
Para agregar otra solución, amplíe este documento (no es particularmente de alta calidad, pero muestra algunos gráficos y matrices utilizables que comienzan en la parte inferior de la página 2).
Gol
Lo que estamos tratando de hacer es calcular el gradiente local de la imagen en la posición (x, y). El gradiente es un vector compuesto por los componentes en la dirección xey, gx y gy.
Ahora imagine que queremos aproximar el gradiente en función de nuestro píxel (x, y) y sus vecinos como operación del núcleo (3x3, 5x5 o cualquier tamaño).
Idea de solución
Podemos aproximar el gradiente sumando las proyecciones de todos los pares de vecinos y centros en la dirección del gradiente. (El núcleo de Sobel es solo un método particular para ponderar las diferentes contribuciones, y también lo es Prewitt, básicamente).
Pasos intermedios explícitos para 3x3
Esta es la imagen local, píxel central (x, y) marcado como ''o'' (centro)
a b c
d o f
g h i
Digamos que queremos el gradiente en dirección x positiva. El vector unitario en la dirección x positiva es (1,0) [Más tarde utilizaré la convención de que la dirección y positiva es ABAJO, es decir, (0,1), y que (0,0) está arriba a la izquierda de la imagen) .]
El vector de o a f (''de'' para abreviar) es (1,0). El gradiente en la dirección ''de'' es (f - o) / 1 (el valor de la imagen en el píxel aquí denota f valor menos en el centro o, dividido por la distancia entre esos píxeles). Si proyectamos el vector unitario de ese gradiente vecino particular en la dirección de gradiente deseada (1,0) mediante un producto escalar, obtenemos 1. Aquí hay una pequeña tabla con las contribuciones de todos los vecinos, empezando por los casos más fáciles. Tenga en cuenta que para las diagonales, su distancia es sqrt2, y los vectores unitarios en las direcciones diagonales son 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 ...
editar para aclarar: hay dos factores de 1 / sqrt (2) por la siguiente razón:
Estamos interesados en la contribución al gradiente en una dirección específica (aquí x), así que tenemos que proyectar el gradiente direccional desde el píxel central al píxel vecino en la dirección que nos interesa. Esto se logra tomando el producto escalar de los vectores unitarios en las direcciones respectivas, que introduce el primer factor 1 / L (aquí 1 / sqrt (2) para las diagonales).
El gradiente mide el cambio infinitesimal en un punto, que aproximamos por diferencias finitas. En términos de una ecuación lineal, m = (y2-y1) / (x2-x1). Por esta razón, la diferencia de valor entre el píxel central y el píxel vecino (y2-y1) debe distribuirse a lo largo de su distancia (corresponde a x2-x1) para obtener las unidades de ascenso por unidad de distancia. Esto produce un segundo factor de 1 / L (aquí 1 / sqrt (2) para las diagonales)
Ok, ahora sabemos las contribuciones. Simplifiquemos esta expresión combinando pares opuestos de contribuciones de píxeles. Comenzaré con d y f:
{(f-o)/1 * 1} + {(d-o)/1 * -1}
= f - o - (d - o)
= f - d
Ahora la primera diagonal:
{(c-o)/sqrt2 * 1/sqrt2} + {(g-o)/sqrt2 * -1/sqrt2}
= (c - o)/2 - (g - o)/2
= (c - g)/2
La segunda diagonal contribuye (i - a) / 2. La dirección perpendicular aporta cero. Tenga en cuenta que todas las contribuciones del píxel central ''o'' desaparecen.
Ahora hemos calculado las contribuciones de todos los vecinos más cercanos al gradiente en la dirección x positiva en píxeles (x, y), por lo que nuestra aproximación total del gradiente en dirección x es simplemente su suma:
gx(x,y) = f - d + (c - g)/2 + (i - a)/2
Podemos obtener el mismo resultado utilizando un kernel de convolución donde los coeficientes se escriben en el lugar del píxel vecino correspondiente:
-1/2 0 1/2
-1 0 1
-1/2 0 1/2
Si no quiere lidiar con fracciones, multiplique esto por 2 y obtenga el bien conocido kernel Sobel 3x3.
-1 0 1
G_x = -2 0 2
-1 0 1
La multiplicación por dos solo sirve para obtener enteros convenientes. La escala de su imagen de salida es básicamente arbitraria, la mayoría de las veces la normaliza en su rango de imagen, de todos modos (para obtener resultados claramente visibles).
Por el mismo razonamiento que el anterior, se obtiene el núcleo para el gradiente vertical al proyectar las contribuciones vecinas sobre el vector unitario en la dirección y positiva (0,1)
-1 -2 -1
G_y = 0 0 0
1 2 1
Fórmula para granos de tamaño arbitrario
Si quiere granos de 5x5 o más, solo necesita prestar atención a las distancias, por ej.
A B 2 B A
B C 1 C B
2 1 - 1 2
B C 1 C B
A B 2 B A
dónde
A = 2 * sqrt2
B = sqrt5
C = sqrt2.
Si la longitud del vector que conecta dos píxeles es L, el vector unitario en esa dirección tiene un prefactor de 1 / L. Por esta razón, las contribuciones de cualquier píxel ''k'' a (digamos) el gradiente x (1,0) se pueden simplificar a "(diferencia de valor sobre la distancia cuadrada) veces (DotProduct del vector de dirección no normalizado ''ok'' con vector de gradiente , por ejemplo, (1,0)) "
gx_k = (k - o)/(pixel distance^2) [''ok'' dot (1,0)].
Como el producto escalar del vector de conexión con el vector x unidad selecciona la entrada de vector correspondiente, la entrada del kernel G_x correspondiente en la posición k es
i / (i*i + j*j)
donde i y j son la cantidad de pasos desde el píxel central hasta el píxel k en la dirección xey. En el cálculo de 3x3 anterior, el pixel ''a'' tendría i = -1 (1 a la izquierda), j = -1 (1 a la parte superior) y, por lo tanto, la entrada del kernel ''a'' es -1 / (1 + 1 ) = -1/2.
Las entradas para el kernel G_y son
j/(i*i + j*j).
Si quiero valores enteros para mi kernel, sigo estos pasos:
- compruebe el rango disponible de la imagen de salida
- calcule el mayor resultado posible al aplicar kernel de coma flotante (es decir, suponga el valor de entrada máximo bajo todas las entradas positivas del kernel, entonces el valor de salida es (suma sobre todos los valores positivos del kernel) * (máximo valor de imagen de entrada posible). para considerar también los valores negativos. El peor caso es la suma de todos los valores positivos + suma de todos los valores abs de entradas negativas (si la entrada máxima es positiva, -max entrada bajo negativos). edición: la suma de todos los valores abs tiene también se ha llamado acertadamente el peso del núcleo
- calcular la escala máxima permitida para kernel (sin desbordamiento del rango de la imagen de salida)
- para todos los múltiplos enteros (de 2 a más) del kernel de coma flotante: verifique cuál tiene la suma más baja de errores de redondeo absolutos y use este kernel
Entonces en resumen:
Gx_ij = i / (i*i + j*j)
Gy_ij = j / (i*i + j*j)
donde i, j es la posición en el kernel contada desde el centro. Escale las entradas del kernel según sea necesario para obtener números enteros (o al menos aproximaciones cercanas).
Estas fórmulas son válidas para todos los tamaños de kernel.
Ejemplos
-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
Tenga en cuenta que los píxeles centrales de 3x3 del núcleo de 5x5 en la notación de flotación son solo el núcleo de 3x3, es decir, los núcleos más grandes representan una aproximación continua con datos adicionales pero de menor ponderación. Esto continúa con tamaños de kernel más 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
Las representaciones enteras exactas se vuelven imprácticas en este punto.
Por lo que puedo decir (no tengo acceso al documento original), la parte "Sobel" de esto está ponderando correctamente las contribuciones. La solución de Prewitt puede obtenerse omitiendo la ponderación de la distancia y simplemente ingresando i y j en el kernel, según corresponda.
Bonus: Sobel Kernels para direcciones arbitrarias
Entonces podemos aproximar los componentes xey del gradiente de la imagen (que en realidad es un vector, como se dijo al principio). El gradiente en cualquier dirección arbitraria alfa (medida matemáticamente positiva, en este caso en el sentido de las agujas del reloj, ya que positivo y es hacia abajo) puede obtenerse proyectando el vector de gradiente sobre el vector de unidad de gradiente alfa.
El vector de unidad alfa es (cos alfa, sin alfa). Para alpha = 0 ° puedes obtener el resultado para gx, para alpha = 90 ° obtienes gy.
g_alpha = (alpha-unit vector) dot (gx, gy)
= (cos a, sin a) dot (gx, gy)
= cos a * gx + sin a * gy
Si se toma la molestia de escribir gx y gy como sumas de contribuciones vecinas, se da cuenta de que puede agrupar la expresión larga resultante por términos que se aplican al mismo píxel vecino, y luego reescribir esto como un núcleo de convolución única con entradas
G_alpha_ij = (i * cos a + j * sin a)/(i*i + j*j)
Si desea la aproximación entera más cercana, siga los pasos descritos anteriormente.
Aquí hay una solución simple hecha con Python 3 usando numpy y la respuesta @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
Devuelve el núcleo (5,5) de esta manera:
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 alguien conoce una mejor manera de hacerlo en Python, házmelo saber. Soy un novato todavía;)
Como explicó Adam Bowen en su respuesta , el kernel de Sobel es una combinación de un suavizado a lo largo de un eje y una derivada de diferencia central a lo largo del otro eje:
sob3x3 = [1 2 1]'' * [1 0 -1]
El suavizado agrega regularización (reduce la sensibilidad al ruido).
(Dejo fuera todos los factores 1/8
en esta publicación, como lo hizo el propio Sobel , lo que significa que el operador determina la derivada hasta la escala. Además, *
siempre significa convolución en esta publicación.)
Vamos a generalizar esto:
deriv_kernel = smoothing_kernel * d/dx
Una de las propiedades de la convolución es que
d/dx f = d/dx * f
Es decir, al convolucionar una imagen con el operador de derivada elemental se obtiene la derivada de la imagen. Observando también que la convolución es conmutativa,
deriv_kernel = d/dx * smoothing_kernel = d/dx smoothing_kernel
Es decir, el kernel derivado es la derivada de un núcleo suavizado.
Tenga en cuenta que al aplicar dicho kernel a una imagen por convolución:
image * deriv_kernel = image * smoothing_kernel * d/dx = d/dx (image * smoothing_kernel)
Es decir, con este núcleo derivativo idealizado generalizado, podemos calcular la derivada verdadera de la imagen suavizada. Por supuesto, este no es el caso con el kernel de Sobel, ya que utiliza una aproximación de diferencia central a la derivada. Pero al elegir un mejor smoothing_kernel
, esto se puede lograr. El kernel de Gauss es la opción ideal aquí, ya que ofrece el mejor compromiso entre compacidad en el dominio espacial (huella pequeña del núcleo) con compacidad en el dominio de la frecuencia (buen alisamiento). Además, el gaussiano es perfectamente isótropo y separable. El uso de un kernel derivado Gaussiano produce el mejor operador derivativo regularizado posible.
Por lo tanto, si está buscando un operador de Sobel más grande, porque necesita más regularización, use un operador de derivados gaussianos.
Analicemos el kernel de Sobel un poco más.
El núcleo suavizado es triangular, con muestras [1 2 1]
. Esta es una función triangular que, de la muestra, conduce a esos tres valores:
2 + x , if -2 < x < 0
h = { 2 , if x = 0
2 - x , if 0 < x < 2
Su derivado es:
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
Entonces, podemos ver que la aproximación de derivada de diferencia central se puede ver como un muestreo de la derivada analítica de la misma función triangular utilizada para suavizar. Así tenemos:
sob3x3 = [1 2 1]'' * d/dx [1 2 1] = d/dx ( [1 2 1]'' * [1 2 1] )
Por lo tanto, si desea agrandar este kernel, simplemente agrande el kernel de suavizado:
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]
Esto es bastante diferente del consejo dado por Adam Bowen , quien sugiere que se convierta el kernel con el núcleo triangular de 3 pestañas a lo largo de cada dimensión: [1 2 1] * [1 2 1] = [1 4 6 4 1]
, y [1 2 1] * [1 0 -1] = [1 2 0 -2 -1]
. Tenga en cuenta que, debido al teorema del límite central, la convolución de este núcleo triangular consigo mismo conduce a un filtro que se aproxima al de Gauss un poco más. Cuanto más grande es el núcleo que creamos mediante convoluciones repetidas consigo mismo, más nos aproximamos a este gaussiano. Entonces, en lugar de usar este método, también podría muestrear directamente la función gaussiana.
Daniel tiene una larga publicación en la que sugiere extender el kernel de Sobel de otra manera. La forma del núcleo suavizante aquí diverge de la aproximación Gaussiana, no he intentado estudiar sus propiedades.
Tenga en cuenta que ninguna de estas tres extensiones posibles del kernel de Sobel son en realidad núcleos de Sobel, ya que el núcleo de Sobel es explícitamente un núcleo de 3x3 ( consulte una nota histórica de Sobel sobre su operador , que en realidad nunca publicó).
Tenga en cuenta también que no estoy defendiendo el kernel extendido de Sobel derivado aquí. ¡Usa derivados Gaussianos!
Gracias a todos, probaré la segunda variante de @Adam Bowen, tomaré el código C # para Sobel5x5, 7x7, 9x9 ... Generación de matrices para esta variante (quizás con errores, si encuentras errores o puedes optimizar el código, escríbelo allí):
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;
}
Resultados (NormalMap) o copia allí , donde este método - №2, @Paul R metodológico - №1. Ahora estoy utilizando el último, porque da un resultado más suave y es fácil generar kernels con this código.
Otras fuentes parecen dar diferentes definiciones de los núcleos más grandes. La biblioteca Intel IPP, por ejemplo, le da al kernel de 5x5
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
Intuitivamente, esto tiene más sentido para mí porque prestas más atención a los elementos más cerca del centro. También tiene una definición natural en términos del núcleo 3x3 que es fácil de ampliar para generar núcleos más grandes. Dicho esto, en mi breve búsqueda encontré 3 definiciones diferentes del kernel 5x5, por lo que sospecho que (como dice Paul) los núcleos más grandes son ad hoc, por lo que esta no es la respuesta definitiva.
El kernel de 3x3 es el producto externo de un kernel de suavizado y un kernel de gradiente, en Matlab esto es algo así como
sob3x3 = [ 1 2 1 ]'' * [1 0 -1]
los núcleos más grandes se pueden definir al convolucionar el kernel 3x3 con otro núcleo suavizado
sob5x5 = conv2( [ 1 2 1 ]'' * [1 2 1], sob3x3 )
puedes repetir el proceso para obtener núcleos progresivamente más grandes
sob7x7 = conv2( [ 1 2 1 ]'' * [1 2 1], sob5x5 )
sob9x9 = conv2( [ 1 2 1 ]'' * [1 2 1], sob7x7 )
...
hay muchas otras maneras de escribirlo, pero creo que esto explica exactamente qué está sucediendo mejor. Básicamente, comienzas con un kernel suavizado en una dirección y una estimación de diferencias finitas de la derivada en la otra y luego solo aplicas el suavizado hasta que obtienes el tamaño de kernel que deseas.
Debido a que es solo una serie de convoluciones, todas las propiedades agradables tienen, (conmutatividad, asociatividad, etc.) que podrían ser útiles para su implementación. Por ejemplo, puede separar trivialmente el kernel 5x5 en sus componentes suavizado y derivado:
sob5x5 = conv ([1 2 1], [1 2 1]) ''* conv ([1 2 1], [- 1 0 1])
Tenga en cuenta que para ser un estimador derivado "adecuado", el 3x3 Sobel se debe escalar por un factor de 1/8:
sob3x3 = 1/8 * [ 1 2 1 ]'' * [1 0 -1]
y cada grano más grande necesita ser escalado por un factor adicional de 1/16 (porque los granos suavizantes no están normalizados):
sob5x5 = 1/16 * conv2( [ 1 2 1 ]'' * [1 2 1], sob3x3 )
sob7x7 = 1/16 * conv2( [ 1 2 1 ]'' * [1 2 1], sob5x5 )
...
Rápidamente pirateé un algoritmo para generar un kernel de Sobel de cualquier tamaño impar> 1, basado en los ejemplos dados por @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;
}
}
}
Espero eso ayude.
ACTUALIZACIÓN 23-Abr-2018: parece que los núcleos definidos en el siguiente enlace no son verdaderos núcleos Sobel (para 5x5 y superiores): pueden hacer un trabajo razonable de detección de bordes, pero no deben llamarse núcleos Sobel. Vea la respuesta de Daniel para un resumen más preciso y completo. (Dejaré esta respuesta aquí ya que (a) está vinculada desde varios lugares y (b) las respuestas aceptadas no pueden eliminarse fácilmente).
Google parece generar muchos resultados, por ejemplo, http://rsbweb.nih.gov/nih-image/download/user-macros/slowsobel.macro sugiere los siguientes kernels para 3x3, 5x5, 7x7 y 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