OpenGL GLSL - Filtr wykrywania krawędzi Sobela

9

W odniesieniu do tego tematu z powodzeniem zaimplementowałem filtr Sobel Edge Detection w GLSL. Oto kod modułu cieniującego fragmentu filtra:

#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);
} 

A oto wynik sześcianu z wykrywaniem krawędzi Sobela:

Kostka z filtrem wykrywania krawędzi Sobela

Jeśli powiększysz obraz, zobaczysz, że jest dużo „szumu” wytwarzanego przez Sobela: Szare poziome paski na całej scenie z powodu niebiesko-białego gradientu. Ponadto lekkie stożki wytwarzają niechciany wzór na kostce. Czarne krawędzie po lewej stronie sześcianu również wydają się blaknąć z powodu jasnego stożka po lewej stronie sześcianu.

Przeczytałem więc ten artykuł, w którym stwierdzono, że obraz należy najpierw skalować w skali szarości i użyć filtru rozmycia Gaussa, aby krawędzie były bardziej widoczne. Na dole tego artykułu znajduje się również filtr wykrywania krawędzi kanistych, który wydaje się dawać lepsze wyniki.

Teraz mam dwa pytania:

  1. Czy poniższe kroki są prawidłowe, aby uzyskać najlepsze możliwe wyniki wykrywania krawędzi:

    • Skala szarości
    • Rozmycie Gaussa
    • Wykrywanie krawędzi Sobel / Canny
  2. Jeśli tak, to jak połączyć oryginalny obraz z przetworzonym obrazem? Po przetworzeniu powyższych kroków otrzymuję obraz całkowicie czarny z białymi krawędziami lub odwrotnie. Jak powinienem umieścić krawędzie na moim oryginalnym obrazie / teksturze?

Dzięki za pomoc!

enne87
źródło
Zastanawiam się, czy możesz uzyskać pożądane wyniki, wykorzystując w jakiś sposób flagę krawędzi OpenGL . Wygląda na to, że tylko krawędzie między wierzchołkami z włączoną flagą krawędzi są rysowane w trybie linii. Tutaj jest opis . (Nie użyłem go sam lub opublikowałbym przykład.)
user1118321,

Odpowiedzi:

9
  1. Najlepsze wyniki silnie zależą od przypadku użycia. Zależą również od tego, jaki efekt chcesz osiągnąć. Sobel to tylko filtr wykrywający zbocze: zbocza będą zależeć od sygnału wejściowego, wybór tego sygnału zależy od Ciebie.

    Tutaj używasz kolorowego obrazu jako danych wejściowych, a filtr słusznie wykrywa słabe krawędzie w niebieskim gradiencie, podczas gdy krawędzie sześcianu zostają przerwane, gdy jego kolor jest zbyt blisko od koloru tła.

    Ponieważ zakładam, że twój program jest również odpowiedzialny za rysowanie kostki, masz dostęp do innych informacji, które możesz przekazać do filtra Sobel. Na przykład głębokość i normalne są dobrymi kandydatami do wykrywania krawędzi. Można do tego wykorzystać albedo przed oświetleniem. Przetestuj przy użyciu różnych danych wejściowych i zdecyduj, których użyć w zależności od uzyskanych wyników.

  2. Jeśli chodzi o pytanie dotyczące sposobu łączenia informacji o krawędziach, sugeruję gtrochę przefiltrować przed użyciem. Następnie można go użyć do interpolacji między kolorem oryginalnym a pożądanym kolorem krawędzi.

    Na przykład możesz spróbować czegoś takiego:

    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.);

Uzupełnienie

Aby użyć głębokości lub normalnych, musisz zapisać je w teksturze, jeśli nie zostało to jeszcze zrobione. Podczas tworzenia bufora klatek dla zwykłego przejścia renderowania można do niego dołączać różne tekstury (patrz glFramebufferTexture2D) i zapisywać do nich inne informacje niż tylko kolor sceny.

Jeśli dołączysz teksturę głębokości (za pomocą GL_DEPTH_ATTACHMENT), zostanie ona użyta automatycznie do głębokości. Jeśli dołączysz jedną lub kilka tekstur kolorów (za pomocą GL_COLOR_ATTACHMENTi), możesz do nich pisać, deklarując kilka wyjść do shaderów fragmentów (kiedyś tak było gl_FragData; w obu przypadkach, patrz glDrawBuffers).

Aby uzyskać więcej informacji na ten temat, wyszukaj temat „Multi Render Target” (MRT).

Julien Guertault
źródło
Niezła informacja, dzięki Julien. Jeszcze jedno pytanie: jak mogę użyć głębokości lub normalnych informacji w moim module cieniującym fragmenty? Nadal jestem całkiem nowy w GLSL i dlatego nie mam pojęcia, jak uwzględnić wartości w algorytmie Sobela.
enne87
2
Podczas tworzenia bufora ramki dla przepustki renderowania możesz dołączyć do niego więcej niż jedną teksturę. Jeśli dołączysz teksturę głębokości, zostanie ona automatycznie zastosowana do głębi. Po dołączeniu innej tekstury koloru możesz do niej pisać osobno (patrz opengl.org/sdk/docs/man/html/glDrawBuffers.xhtml ), funkcję o nazwie „Multi Render Target” (MRT).
Julien Guertault,
@ enne87: Z przyjemnością pomagam. :)
Julien Guertault
@trichoplax: dzięki za sugestię; gotowy.
Julien Guertault