Sobel-Kantendetektor

12

Ihre Aufgabe ist es, ein Programm zu schreiben, das ein Eingabebild aufnimmt und es durch Kantenerkennung ausführt, um ein Ausgabebild zu werden.

Die Kantenerkennung funktioniert wie folgt (falls unklar, siehe Sobel-Kantenerkennung ):

  • Der Wert für ein Pixel entspricht der Gesamthelligkeit eines Pixels. Wenn es sich also um ein farbiges Pixel handelt, müssen Sie es zuerst in Graustufen konvertieren B).
  • Die Formeln für G x und G y für Pixel p (i, j) lauten:
    • 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)
  • Der Wert für die Größe der Kante an diesem Pixel ist dann: √ (G x 2 + G y 2 )

Das Ausgabebild hat für jedes Pixel die Größe der Kante √ (G x 2 + G y 2 ) als Graustufen.

Boni:

  • Führen Sie eine Gaußsche Unschärfe durch, um das Bild vor dem Aktivieren der Kantenerkennung zu glätten und kleinere Kanten zu vermeiden. Dies ergibt einen Bonus von -30% auf das Endergebnis.
  • Berücksichtigen Sie den Winkel der Kante. Sie geben dem Ausgabepixel eine gewisse Farbe, indem Sie denselben Graustufenwert nehmen und Farbe aus einem Farbrad unter Verwendung des Winkels hinzufügen, der aus der Formel arctan (G y / G x ) erhalten wird. Dies gibt einen weiteren Bonus von -30% auf das Endergebnis.

Regeln:

  • Sie können den Wert für die Randpixel weglassen und auf Schwarz setzen, oder Sie können 0 für alle Pixel außerhalb des Bildes verwenden.
  • Ihr Ausgabebild muss in einem Bildformat vorliegen, das auf den meisten Computern geöffnet werden kann.
  • Die Ausgabe muss auf die Festplatte geschrieben oder in eine Datei umgeleitet werden.
  • Die Eingabe erfolgt als Befehlszeilenargument in Form eines relativen Pfads zum Bild oder über die Befehlszeile.
  • Das ist Code Golf, also gewinnt der kürzeste Code in Bytes!
vrwim
quelle
Können Sie die Gaußsche Unschärfe genau spezifizieren? Gilt auch die Eingabe in Graustufen, wenn nein, wie sollen wir diese Kantenerkennung auf farbige Bilder anwenden? Ist es richtig, dass das Ausgabebild genau dieselbe Größe wie die Eingabe hat, die Eingabe jedoch nur für die inneren Pixel ausgeführt wird (nicht für die, die wir auf Null gesetzt haben)?
Fehler
Haben Sie die Videos zur Kantenerkennung von Computerphile gesehen ? Ich kann dort eine Verbindung riechen :)
GiantTree
@flawr Ich muss testen, welche Gaußsche Unschärfe für die Kantenerkennung geeignet ist, daher weiß ich nicht, was ein guter Wert ist. Mehr zu Gaußscher Unschärfe hier . Das Eingabebild ist farbig, und Sie müssen es zuerst in Graustufen konvertieren, wenn Sie die Kantenerkennung durchführen möchten. Die Kantenerkennung wird entweder A: für die inneren Pixel ausgeführt, und Sie setzen den äußeren Rand des Ausgabebilds von 1 Pixel auf Schwarz, oder B: für alle Pixel, und Sie nehmen 0 als Wert für alle Pixel außerhalb des Bildes.
vrwim
@GiantTree nooooooo das Video ist völlig nicht verwandt :)
vrwim
4
Warum wurde dies abgelehnt? Es scheint eine vollkommen berechtigte Frage zu sein.
Addison Crump

Antworten:

13

J, 166 164 161 154 150 144 143 Bytes.

Nicht zu viel Golf gespielt; Ich habe meine längere Implementierung größtenteils reduziert (siehe unten), daher gibt es wahrscheinlich viel Raum für Verbesserungen. Verwendet die BMP-Bibliothek. Speichert das Ergebnis in einer Datei o. Ich habe Kantenpixel nur mit vollen 3x3-Zellen verarbeitet, sodass das endgültige Bild eine um 2 Pixel geringere Breite und Höhe aufweist.

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

Verwendung:

echo 'image.bmp' | jconsole golf.ijs

Erweitert:

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

Probenein- und ausgabe:

Original Kantenerkennung

Adrian17
quelle
Dies ist ein schönes Beispiel für den ;._3Subarray-Operator. Ich habe festgestellt, dass Sie ein Verb pmit Rang 2 definiert haben , um die Subarrays zu bearbeiten, nachdem Sie sie erstellt haben. Sie können stattdessen jedes Subarray bearbeiten, wenn Sie schneiden. Mein Versuch, es basierend auf Ihrer Arbeit umzusetzen, ist 256#.3#"0<.255<.3 3((|:S)&*+&.*:&(+/)&,S&*);._3%&3(3#256)+/@#:. Das sollte es auf insgesamt 126 Bytes reduzieren.
Meilen
Ich habe es auf 119 Bytes mit der 'o'writebmp~256#.3#"0<.255<.3 3(*+&.*:&(+/)&,(*|:))&((-,.0,.])1 2 1);._3%&3(3#256)+/@#:readbmp]stdin''Annahme, dass nur der Dateiname auf stdin eingegeben wird. Sie können dies echo -nso ausführen, dass in stdin kein zusätzlicher Zeilenumbruch enthalten ist. Auf meinem Computer wird das Skript automatisch beendet, wenn eine weitergeleitete Eingabe für ein Skript verwendet wird. exit''Dies bedeutet, dass ich das Skript nicht einschließen muss und zusätzliche 6 Bytes speichern kann, aber ich bin nicht sicher, ob dies für alle zutrifft.
Meilen
1

Python 161 * 0,7 = 112,7 Bytes

Mit dem Gaußschen Unschärfebonus.

Da Sie eingebaute Methoden nicht explizit verboten haben, ist hier 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))

Ohne Bonus 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: Ersetzt die benannten Konstanten durch ihre Werte.
  • Edit2: Hochgeladene Samples

Original gefiltert

Karl Napf
quelle
Könnten Sie möglicherweise ein Beispiel für ein Eingabe- und Ausgabebild geben?
R. Kap
@ R.Kap besser spät als nie.
Karl Napf
0

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

Verwenden der Filter-Toolbox und des HSV-Farbraums

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

oder ungolfed

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

Übernimmt die Befehlszeileneingabe und gibt sie in eine Datei mit dem Namen "o" in Ihrem Love2D-Appsdata-Ordner aus. Mit Love2D können Sie keine Dateien an einem anderen Ort speichern.

Genau so gut wie ich es bekommen könnte, könnte es wahrscheinlich noch weiter gehen.

Erklärt

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

Prüfung

Eingang Ausgabe

Und...

Obwohl es meine Punktzahl nicht verbessert (macht es noch schlimmer), ist hier die Version mit dem implementierten Farbrad.

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

Bildbeschreibung hier eingeben

Ein Taco
quelle