OpenGL GLSL - Sobel-Kantenerkennungsfilter

9

In Bezug auf dieses Thema habe ich den Sobel Edge Detection- Filter erfolgreich in GLSL implementiert . Hier ist der Fragment-Shader-Code des Filters:

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D screenTexture;

mat3 sx = mat3( 
    1.0, 2.0, 1.0, 
    0.0, 0.0, 0.0, 
   -1.0, -2.0, -1.0 
);
mat3 sy = mat3( 
    1.0, 0.0, -1.0, 
    2.0, 0.0, -2.0, 
    1.0, 0.0, -1.0 
);

void main()
{
    vec3 diffuse = texture(screenTexture, TexCoords.st).rgb;
    mat3 I;
    for (int i=0; i<3; i++) {
        for (int j=0; j<3; j++) {
            vec3 sample  = texelFetch(screenTexture, ivec2(gl_FragCoord) + ivec2(i-1,j-1), 0 ).rgb;
            I[i][j] = length(sample); 
    }
}

float gx = dot(sx[0], I[0]) + dot(sx[1], I[1]) + dot(sx[2], I[2]); 
float gy = dot(sy[0], I[0]) + dot(sy[1], I[1]) + dot(sy[2], I[2]);

float g = sqrt(pow(gx, 2.0)+pow(gy, 2.0));
color = vec4(diffuse - vec3(g), 1.0);
} 

Und hier ist das Ergebnis eines Würfels mit Sobel-Kantenerkennung:

Würfel mit Sobel-Kantenerkennungsfilter

Wenn Sie das Bild vergrößern, werden Sie feststellen, dass Sobel viel "Rauschen" erzeugt: Aufgrund des blau / weißen Verlaufs sind überall in der Szene graue horizontale Streifen zu sehen. Darüber hinaus erzeugen die Lichtkegel ein unerwünschtes Muster auf dem Würfel. Die schwarzen Ränder links vom Würfel scheinen aufgrund des Lichtkegels in der linken Würfelhälfte ebenfalls zu verblassen.

Also las ich diesen Artikel, in dem angegeben wurde, dass man das Bild zuerst in Graustufen skalieren und einen Gaußschen Unschärfefilter verwenden sollte, um die Kanten deutlicher zu machen. Am Ende des Artikels befindet sich auch der Filter zur Erkennung kniffliger Kanten, der anscheinend bessere Ergebnisse liefert.

Jetzt habe ich zwei Fragen:

  1. Sind die folgenden Schritte korrekt, um die bestmöglichen Kantenerkennungsergebnisse zu erzielen:

    • Graustufen
    • Gaußsche Unschärfe
    • Sobel / Canny Kantenerkennung
  2. Wenn ja, wie würde ich das Originalbild mit dem verarbeiteten Bild zusammenführen? Ich meine, nach der Verarbeitung der oben genannten Schritte erhalte ich ein Bild, das entweder vollständig schwarz mit weißen Rändern oder umgekehrt ist. Wie würde ich die Kanten auf mein Originalbild / meine Originaltextur setzen?

Danke für Ihre Hilfe!

enne87
quelle
Ich frage mich, ob Sie möglicherweise die gewünschten Ergebnisse erzielen, wenn Sie das Edge-Flag von OpenGL auf irgendeine Weise verwenden. Es sieht so aus, als würden im Linienmodus nur Kanten zwischen Scheitelpunkten mit aktiviertem Kantenflag gezeichnet. Hier gibt es eine Beschreibung . (Ich habe es selbst nicht benutzt oder ich würde ein Beispiel
posten

Antworten:

9
  1. Die besten Ergebnisse hängen stark von Ihrem Anwendungsfall ab. Sie hängen auch davon ab, welchen Effekt Sie erzielen möchten. Sobel ist nur ein Flankenerkennungsfilter: Die Flanken hängen vom Eingangssignal ab. Die Wahl des Eingangssignals liegt bei Ihnen.

    Hier verwenden Sie das Farbbild als Eingabe, und der Filter erkennt zu Recht schwache Kanten im blauen Farbverlauf, während die Kanten des Würfels unterbrochen werden, wenn seine Farbe zu nahe an der Hintergrundfarbe liegt.

    Da ich davon ausgehe, dass Ihr Programm auch für das Zeichnen des Cubes verantwortlich ist, haben Sie Zugriff auf andere Informationen, die Sie Ihrem Sobel-Filter zuführen können. Zum Beispiel sind Tiefe und Normalen gute Kandidaten für die Kantenerkennung. Die Albedo vor dem Anzünden könnte man gewohnt sein. Testen Sie mit verschiedenen Eingaben und entscheiden Sie, welche verwendet werden sollen, abhängig von den Ergebnissen, die Sie erhalten.

  2. In Bezug auf Ihre Frage zum Kombinieren der Kanteninformationen empfehle ich Ihnen, gvor der Verwendung ein wenig zu filtern . Dann können Sie damit zwischen der Originalfarbe und der gewünschten Kantenfarbe interpolieren.

    Zum Beispiel könnten Sie so etwas versuchen:

    float g = sqrt(pow(gx, 2.0)+pow(gy, 2.0));

    // Try different values and see what happens
    g = smoothstep(0.4, 0.6, g);

    vec3 edgeColor = vec3(1., 0., 0.2);
    color = vec4(mix(diffuse, edgeColor, g), 1.);

Nachtrag

Um Tiefe oder Normalen zu verwenden, müssten Sie sie in einer Textur speichern, wenn dies noch nicht geschehen ist. Wenn Sie den Bildspeicher für Ihren regulären Rendering-Durchgang erstellen, können Sie ihm verschiedene Texturen hinzufügen (siehe glFramebufferTexture2D) und andere Informationen als nur die Farbe der Szene darauf schreiben.

Wenn Sie eine Tiefenstruktur (mit GL_DEPTH_ATTACHMENT) anhängen , wird diese automatisch für die Tiefe verwendet. Wenn Sie eine oder mehrere Farbtexturen (mit GL_COLOR_ATTACHMENTi) anhängen , können Sie darauf schreiben, indem Sie Ihren Fragment-Shadern mehrere Ausgaben deklarieren (dies wurde früher durchgeführt gl_FragData; so oder so, siehe glDrawBuffers).

Weitere Informationen zum Thema finden Sie unter "Multi Render Target" (MRT).

Julien Guertault
quelle
Gute Informationen, danke Julien. Noch eine Frage: Wie kann ich die Tiefen- oder Normalinformationen in meinem Fragment-Shader verwenden? Ich bin noch ziemlich neu in GLSL und habe daher keine Ahnung, wie ich die Werte in den Sobel-Algorithmus aufnehmen soll.
enne87
2
Wenn Sie den Framebuffer für Ihren Rendering-Durchgang erstellen, können Sie ihm mehrere Texturen hinzufügen. Wenn Sie eine Tiefenstruktur anhängen, wird diese automatisch für die Tiefe verwendet. Wenn Sie eine andere Farbtextur anhängen, die Sie separat schreiben können (siehe opengl.org/sdk/docs/man/html/glDrawBuffers.xhtml ), eine Funktion namens "Multi Render Target" (MRT).
Julien Guertault
@ enne87: Gerne helfen. :)
Julien Guertault
@ Trichoplax: Danke für den Vorschlag; getan.
Julien Guertault