Detector de borda de Sobel

12

Sua tarefa é escrever um programa que obtenha uma imagem de entrada e execute-a através da detecção de borda para se tornar uma imagem de saída.

A detecção de borda funciona da seguinte maneira (se não estiver claro, consulte detecção de borda sobel ):

  • O valor de um pixel é o brilho total de um pixel; portanto, se estiver em cores, primeiro será necessário convertê-lo em escala de cinza (para manter as coisas simples e com capacidade para jogar golfe, você pode usar o valor médio de R, G e B)
  • As fórmulas para G x e G y para o pixel p (i, j) são:
    • 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)
  • O valor para o tamanho da aresta nesse pixel é então: √ (G x 2 + G y 2 )

A imagem de saída é para cada pixel o tamanho da borda √ (G x 2 + G y 2 ) como escala de cinza.

Bônus:

  • Execute um desfoque gaussiano para suavizar a imagem antes que a detecção de borda apareça, para omitir qualquer borda menor. Isso dá um bônus de -30% no resultado final.
  • Considere o ângulo da aresta. Você fornece alguma cor ao pixel de saída, obtendo o mesmo valor em escala de cinza e adicionando cores a uma roda de cores usando o ângulo obtido da fórmula arctan (G y / G x ). Isso dá outro bônus de -30% no resultado final.

Regras:

  • Você pode omitir o valor dos pixels da borda e configurá-los para preto ou usar 0 para qualquer pixel fora da imagem.
  • Sua imagem de saída deve estar em um formato de imagem que possa ser aberto na maioria dos computadores.
  • A saída deve ser gravada no disco ou canalizável para um arquivo.
  • A entrada é fornecida como um argumento da linha de comando, na forma de um caminho relativo para a imagem ou canalizada a partir da linha de comando.
  • Este é o código golf, pelo que o código mais curto em bytes vence!
vrwim
fonte
Você pode especificar exatamente o desfoque gaussiano? A entrada também está em escala de cinza; se não, como devemos aplicar essa detecção de borda às imagens coloridas? É correto que a imagem de saída tenha exatamente o mesmo tamanho da entrada, mas a entrada é realizada apenas nos pixels internos (não no que definimos como zero)?
flawr
Você já viu os vídeos sobre detecção de borda do Computerphile ? Eu posso cheirar uma conexão lá :)
GiantTree
@ flawr Eu tenho que testar o que o Gaussian Blur é bom para a detecção de borda, então eu realmente não sei o que é um bom valor. mais sobre Gaussian blur aqui . A imagem de entrada é colorida e você precisará convertê-la primeiro em escala de cinza se desejar realizar a detecção de borda. A detecção de borda é executada A: nos pixels internos, e você define a borda externa de 1px da imagem de saída como preta, ou B: em todos os pixels, e assume 0 como valor para qualquer pixel fora da imagem.
vrwim
@GiantTree Nooooooo o vídeo é totalmente não relacionados :)
vrwim
4
Por que isso foi votado para baixo? Parece ser uma pergunta perfeitamente válida.
Addison Crump

Respostas:

13

J, 166 164 161 154 150 150 143 143 bytes.

Não jogou golfe demais; Eu quase derrubei minha implementação mais longa (veja abaixo), então provavelmente há muito espaço para melhorias. Usa biblioteca BMP. Salva o resultado no arquivo o. Eu manusei pixels de borda usando apenas células 3x3 completas, para que a imagem final tenha largura e altura menores em 2 pixels.

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''

Entrada e saída de amostra:

Original Detecção de borda

Adrian17
fonte
Este é um bom exemplo do ;._3operador de subarray. Notei que você definiu um verbo pcom classificação 2 para operar nos subarrays depois de criá-los. Você poderia operar em cada sub-matriz quando cortar. Minha tentativa de implementá-lo com base no seu trabalho é 256#.3#"0<.255<.3 3((|:S)&*+&.*:&(+/)&,S&*);._3%&3(3#256)+/@#:. Isso deve reduzir para 126 bytes no total.
miles
Eu o reduzi para 119 bytes, 'o'writebmp~256#.3#"0<.255<.3 3(*+&.*:&(+/)&,(*|:))&((-,.0,.])1 2 1);._3%&3(3#256)+/@#:readbmp]stdin''assumindo que apenas o nome do arquivo é inserido no stdin. Você pode fazer isso usando echo -npara que uma nova linha extra não seja incluída no stdin. No meu computador, o script sai automaticamente ao usar uma entrada canalizada em um script, o que significa que não preciso incluir exit''e salvar 6 bytes extras, mas não tenho certeza se isso é verdade para todos.
miles
1

Python, 161 * 0,7 = 112,7 bytes

Com o bônus Gaussian Blur.

Como você não proibiu explicitamente os métodos internos, aqui está o 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))

Sem bônus, 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: Substituiu as constantes nomeadas por seus valores.
  • Edit2: amostras carregadas

original filtrado

Karl Napf
fonte
Você poderia fornecer uma amostra de imagem de entrada e saída?
R. Kap
@ R.Kap mais vale tarde do que nunca.
Karl Napf
0

MATLAB, 212 * 0,4 = 84,8 bytes

Usando a caixa de ferramentas de filtro e o espaço de cores 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')

ou não destruído

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
fonte
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()

Recebe a entrada da linha de comando e gera um arquivo chamado "o" na pasta de dados do aplicativo Love2D. O Love2D não permitirá que você salve arquivos em qualquer outro lugar.

Assim como o golfe que eu conseguia, provavelmente poderia ser ainda mais.

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()

Teste

Entrada Resultado

E...

Embora na verdade não melhore minha pontuação (piora de fato), aqui está a versão com a roda de cores 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()

insira a descrição da imagem aqui

ATaco
fonte