Detector de bordes Sobel

12

Su tarea es escribir un programa que tome una imagen de entrada y ejecutarla a través de detección de bordes para convertirse en una imagen de salida.

La detección de bordes funciona de la siguiente manera (si no está claro, ver detección de bordes sobel ):

  • El valor de un píxel es el brillo total de un píxel, por lo que si está en color, primero deberá convertirlo a escala de grises (para mantener las cosas simples y aptas para el golf, puede tomar el valor promedio para R, G y SI).
  • Las fórmulas para G x y G y para el píxel p (i, j) son:
    • G x = -1 * p (i-1, j-1) - 2 * p (i-1, j) - 1 * p (i-1, j + 1) + 1 * p (i + 1, j -1) + 2 * p (i + 1, j) + 1 * p (i + 1, j + 1)
    • G y = -1 * p (i-1, j-1) - 2 * p (i, j-1) - 1 * p (i + 1, j-1) + 1 * p (i-1, j +1) + 2 * p (i, j + 1) + 1 * p (i + 1, j + 1)
  • El valor para el tamaño del borde en ese píxel es entonces: √ (G x 2 + G y 2 )

La imagen de salida es para cada píxel del tamaño del borde √ (G x 2 + G y 2 ) como escala de grises.

Bonificaciones:

  • Realice un desenfoque gaussiano para suavizar la imagen antes de que se active la detección de bordes, para omitir los bordes más pequeños. Esto le da un bono de -30% en el resultado final.
  • Tome el ángulo del borde en cuenta. Le da un poco de color al píxel de salida, tomando el mismo valor de escala de grises y agregando color de una rueda de colores usando el ángulo obtenido de la fórmula arctan (G y / G x ). Esto le da otra bonificación de -30% en el resultado final.

Reglas:

  • Puede omitir el valor de los píxeles de borde y establecerlos en negro, o puede usar 0 para cualquier píxel fuera de la imagen.
  • Su imagen de salida debe estar en un formato de imagen que se pueda abrir en la mayoría de las computadoras.
  • La salida debe escribirse en el disco o ser canalizable a un archivo.
  • La entrada se proporciona como un argumento de línea de comandos, en forma de una ruta relativa a la imagen, o se canaliza desde la línea de comandos.
  • Este es el código de golf, por lo que gana el código más corto en bytes.
vrwim
fuente
¿Puedes especificar exactamente el desenfoque gaussiano? ¿También es la escala de grises de entrada? Si no, ¿cómo deberíamos aplicar esta detección de bordes a las imágenes en color? ¿Es correcto que la imagen de salida tenga exactamente el mismo tamaño que la entrada, pero la entrada solo se realiza en los píxeles internos (no en el que hemos configurado en cero)?
falla
¿Has visto los videos sobre detección de bordes de Computerphile ? Puedo oler una conexión allí :)
GiantTree
@flawr Tengo que probar qué desenfoque gaussiano es bueno para la detección de bordes, por lo que realmente no sé cuál es un buen valor. más sobre desenfoque gaussiano aquí . La imagen de entrada está en color, y primero deberá convertirla a escala de grises si desea realizar la detección de bordes. La detección de bordes se realiza ya sea A: en los píxeles internos, y establece el borde exterior de 1px de la imagen de salida en negro, o B: en todos los píxeles, y toma 0 como valor para cualquier píxel fuera de la imagen.
vrwim
@GiantTree nooooooo el video no está totalmente relacionado :)
vrwim 11/11/2015
44
¿Por qué ha sido rechazado? Parece ser una pregunta perfectamente válida.
Addison Crump

Respuestas:

13

J, 166 164 161 154 150 144 143 bytes.

No jugaba demasiado al golf; Principalmente colapsé mi implementación más larga (ver más abajo), por lo que probablemente haya mucho margen de mejora. Utiliza la biblioteca BMP. Guarda el resultado en el archivo o. Manejé los píxeles de borde solo usando celdas completas de 3x3, por lo que la imagen final tiene un ancho y una altura más pequeños en 2 píxeles.

load'bmp'
S=:s,.0,.-s=:1 2 1
p=:([:*:[:+/[:,*)"2
'o'writebmp~256#.3#"0<.255<.%:(S&p+(|:S)&p)3 3,.;._3(3%~])+/"1(3#256)#:readbmp}:stdin''
exit''

Uso:

echo 'image.bmp' | jconsole golf.ijs

Expandido:

load 'bmp'

sobel1 =: 3 3 $ 1 0 _1 2 0 _2 1 0 _1
NB. transposed
sobel2 =: |: sobel1
NB. read image
image =: readbmp }: stdin''
NB. convert default representation to R,G,B arrays
rgbimage =: (3 # 256) #: image
NB. convert to grayscale
greyimage =: 3 %~ (+/"1) rgbimage
NB. 3x3 cells around each pixel
cells =: 3 3 ,.;._3 greyimage
NB. multiply 3x3 cell by 3x3 sobel, then sum all values in it
partial =: 4 : '+/"1 +/"1 x *"2 y'
NB. square partial (vertical and horizontal) results, sum and root
combine =: [: %: *:@[ + *:@]
NB. limit RGB values to 255
limit =: 255 <. ]
newimage =: limit (sobel1&partial combine sobel2&partial) cells
NB. convert back to J-friendly representation
to_save =: 256 #. 3 #"0 <. newimage
to_save writebmp 'out.bmp'
NB. jconsole stays open by default
exit''

Muestra de entrada y salida:

Original Detección de bordes

Adrian17
fuente
Este es un buen ejemplo del ;._3operador de submatriz. Noté que definiste un verbo pcon rango 2 para operar en las submatrices después de crearlas. En su lugar, podría operar en cada subcadena cuando corte. Mi intento de implementarlo en función de su trabajo es 256#.3#"0<.255<.3 3((|:S)&*+&.*:&(+/)&,S&*);._3%&3(3#256)+/@#:. Eso debería reducirlo a 126 bytes en total.
millas
Lo reduje a 119 bytes con la 'o'writebmp~256#.3#"0<.255<.3 3(*+&.*:&(+/)&,(*|:))&((-,.0,.])1 2 1);._3%&3(3#256)+/@#:readbmp]stdin''suposición de que solo el nombre de archivo se ingresa en stdin. Puede realizar esto usando echo -npara que no se incluya una nueva línea adicional en stdin. En mi computadora, el script se cierra automáticamente cuando uso una entrada canalizada a un script, lo que significa que no tengo que incluir el exit''y puedo guardar 6 bytes adicionales, pero no estoy seguro de si esto es cierto para todos.
millas
1

Python, 161 * 0.7 = 112.7 bytes

Con el bono Gaussian Blur.

Como no prohibió explícitamente los métodos integrados, aquí está OpenCV:

from cv2 import*
from numpy import*
g=GaussianBlur(cvtColor(imread(raw_input()),6),(3,3),sigmaX=1)
x,y=Sobel(g,5,1,0),Sobel(g,5,0,1)
imwrite('s.png',sqrt(x*x+y*y))

Sin bono, 136 bytes

from cv2 import*
from numpy import*
g=cvtColor(imread(raw_input()),6)
x,y=Sobel(g,5,1,0),Sobel(g,5,0,1)
imwrite('s.png',sqrt(x*x+y*y))
  • Edit1: reemplazó las constantes nombradas por sus valores.
  • Edit2: muestras cargadas

original filtrado

Karl Napf
fuente
¿Podría dar una imagen de entrada y salida de muestra?
R. Kap
@ R.Kap mejor tarde que nunca.
Karl Napf
0

MATLAB, 212 * 0.4 = 84.8 bytes

Usando la caja de herramientas de filtro y el espacio de color HSV

function f(x);f=@(i,x)imfilter(i,x);s=@(x)fspecial(x);S=s('sobel');A=f(double(rgb2gray(imread(x)))/255,s('gaussian'));X=f(A,S);Y=f(A,S');imwrite(hsv2rgb(cat(3,atan2(Y,X)/pi/2+0.5,0*A+1,sqrt(X.^2+Y.^2))),'t.png')

o sin golf

function f(x)
f=@(i,x)imfilter(i,x);
s=@(x)fspecial(x);
S=s('sobel');
A=f(double(rgb2gray(imread(x)))/255,s('gaussian'));
X=f(A,S);
Y=f(A,S');
imwrite(hsv2rgb(cat(3,atan2(Y,X)/pi/2+0.5,0*A+1,sqrt(X.^2+Y.^2))),'t.png')
Jonas
fuente
0

Love2D Lua, 466 bytes

A=arg[2]i=love.image.newImageData q=math t=i(A)g=i(t:getWidth()-2,t:getHeight()-2)m={{-1,-2,-1},{0,0,0},{1,2,1}}M={{-1,0,1},{-2,0,2},{-1,0,1}}t:mapPixel(function(_,_,r,g,b)a=(r+g+b)/3 return a,a,a end)g:mapPixel(function(x,y)v=0 for Y=0,2 do for X=0,2 do v=v+(t:getPixel(x+X,y+Y)*m[Y+1][X+1])end end V=0 for Y=0,2 do for X=0,2 do V=V+(t:getPixel(x+X,y+Y)*M[Y+1][X+1])end end v=q.max(q.min(q.sqrt(V^2+v^2),255),0)return v,v,v end)g:encode('png',"o")love.event.quit()

Toma la entrada de la línea de comandos, emite un archivo llamado "o" en la carpeta de datos de aplicaciones de Love2D. Love2D no te permitirá guardar archivos en ningún otro lugar.

Casi tan golfizado como pude conseguirlo, probablemente podría jugar más golf.

Explicado

-- Assign the Input to A
A=arg[2]


-- Assign some macros to save FUTURE BYTES™
i=love.image.newImageData
q=math

-- t is the original image, g is the new output image. g is two pixels smaller, which is easier and better looking than a border.
t = i(A)
g = i(t:getWidth()-2,t:getHeight()-2)

-- m and M are our two sobel kernals. Fairly self explanitary.
m = {{-1,-2,-1}
    ,{0,0,0}
    ,{1,2,1}}

M = {{-1,0,1}
    ,{-2,0,2}
    ,{-1,0,1}}

-- Convert t to grayscale, to save doing this math later.
t:mapPixel(function(_,_,r,g,b)a=(r+g+b)/3 return a,a,a end)

-- Execute our kernals
g:mapPixel(function(x,y)
    -- v refers to the VERTICAL output of the Kernel m.
    v=0
    for Y=0,2 do
        for X=0,2 do
            v=v+(t:getPixel(x+X,y+Y)*m[Y+1][X+1])
        end
    end

    -- V is the HORIZONTAL of M
    V=0
    for Y=0,2 do
        for X=0,2 do
            V=V+(t:getPixel(x+X,y+Y)*M[Y+1][X+1])
        end
    end

    -- Clamp the values and sum them.
    v = q.max(q.min(q.sqrt(V^2 + v^2),255),0)
    -- Return the grayscale.
    return v,v,v
end)

-- Save, renaming the file. The golfed version just outputs as 'o'
g:encode('png',"S_".. A:gsub("(.*)%....","%1.png"))

-- Quit. Not needed, but I'm a sucker for self contained LOVE2D
love.event.quit()

Prueba

Entrada Salida

Y...

Aunque en realidad no mejora mi puntaje (lo empeora de hecho), aquí está la versión con la rueda de colores implementada.

900-270 = 630 bytes

A=arg[2]i=love.image.newImageData q=math t=i(A)g=i(t:getWidth()-2,t:getHeight()-2)m={{-1,-2,-1},{0,0,0},{1,2,1}}M={{-1,0,1},{-2,0,2},{-1,0,1}}function T(h,s,v)if s <=0 then return v,v,v end h,s,v=h*6,s,v/255 local c=v*s local x=(1-q.abs((h%2)-1))*c local m,r,g,b=(v-c),0,0,0 if h < 1 then r,g,b=c,x,0 elseif h < 2 then r,g,b=x,c,0 elseif h < 3 then r,g,b=0,c,x elseif h < 4 then r,g,b=0,x,c elseif h < 5 then r,g,b=x,0,c else r,g,b=c,0,x end return(r+m)*255,(g+m)*255,(b+m)*255 end t:mapPixel(function(_,_,r,g,b)a=(r+g+b)/3 return a,a,a end)g:mapPixel(function(x,y)v=0 for Y=0,2 do for X=0,2 do v=v+(t:getPixel(x+X,y+Y)*m[Y+1][X+1])end end V=0 for Y=0,2 do for X=0,2 do V=V+(t:getPixel(x+X,y+Y)*M[Y+1][X+1])end end h=v H=V v=q.max(q.min(q.sqrt(V^2+v^2),255),0)h=q.atan2(H,h)/q.pi*2 return T(h,1,v,255)end)g:encode('png',"S_".. A:gsub("(.*)%....","%1.png"))G=love.graphics.newImage(g)love.event.quit()

ingrese la descripción de la imagen aquí

Un taco
fuente