Jak wykrywać rogi w obrazach binarnych za pomocą OpenGL?

13

Mam binarne obrazy 160 x 120, takie jak:

oryginalny obraz

Chciałbym wykryć rogi tych białych plam. Są one wcześniej zamknięte przez morfologię matematyczną, więc nie powinny mieć żadnych wewnętrznych narożników. W tym konkretnym przypadku chciałbym 16 narożników, takich jak:

przykład wykrywania zakrętów

Moja pierwsza próba polegała na użyciu niektórych funkcji OpenCV, takich jak goodFeaturesToTrack lub FAST, ale są one szczególnie wolne (plus FAST jest bardzo niestabilny). Moim pomysłem byłoby wykonanie takiego obliczenia na GPU, ponieważ pochodzi z niego mój obraz źródłowy. Szukałem w Internecie pomysłów na pisanie takich shaderów (używam OpenGL ES 2.0), ale nie znalazłem nic konkretnego. Masz pomysł, jak mogę uruchomić taki algorytm?

Stéphane Péchard
źródło
2
FAST jest wolny? :)
endolith
1
tak, zabawne prawda? w rzeczywistości jest szybszy niż wcześniejsze algorytmy, takie jak SURF lub SIFT, ale jest mniej precyzyjny, dość niestabilny z jednego obrazu na drugi i wciąż nie jest wystarczająco szybki, aby można go było wykonać na procesorze
Stéphane Péchard
Jak ważne jest dokładne wykrywanie ich na każdej klatce? Jak szybko poruszają się prostokąty? Czy można wykryć narożniki większości ramek i interpolować je w ramkach, w których algorytm nie trafia?
justis,
@ justis dobrze, sposób, w jaki to robię teraz (poprzez użycie funkcji cvFindContours () i cvApproxPoly () OpenCV) nie jest zbyt stabilny w czasie, więc filtruję wynik za pomocą filtra dolnoprzepustowego, wprowadzając opóźnienie. Czy uważasz, że dzięki interpolacji mogę uzyskać bardziej stabilny wynik?
Stéphane Péchard,

Odpowiedzi:

3

Na jakich rozmiarach pracujesz? Przy jakiej liczbie klatek? Na jakim sprzęcie? Z mojego doświadczenia wynika, że ​​FAST jest ładna, eee, szybka.

Widziałem też FAST używany jako wykrywacz ROI z goodFeaturesToTrack działający na zidentyfikowanych ROI, aby zapewnić lepszą stabilność bez uruchamiania kary gFTT na całym obrazie.

„Harris” detektor rogu jest również potencjalnie bardzo szybki, ponieważ składa się z bardzo prostych operacji (nr sqrt () na piksel na przykład!) - nie tak stabilny jak gFTT, ale być może bardziej, niż szybko.

(Pod względem implementacji GPU Googling gpu cornerwydaje się prezentować całkiem sporo linków, ale nie mam pojęcia, jak mogą być odpowiednie - zazwyczaj wdrażam w FPGA.)

Martin Thompson
źródło
Moje obrazy to 160x120, podobno przy 30 klatkach na sekundę, na iPhonie, ale oczywiście aplikacja ma o wiele więcej do zrobienia :-) Widziałem aplikację wdrażającą SZYBKO dość szybko na takim urządzeniu, ale była to tylko wersja demonstracyjna robienie tego ... Właśnie dlatego szukam rozwiązań opartych na GPU.
Stéphane Péchard
15

Właśnie zdarzyło mi się implementować coś takiego w OpenGL ES 2.0 przy użyciu wykrywania narożników Harrisa i chociaż nie jestem całkowicie ukończony, pomyślałem, że podzielę się implementacją opartą na modułach cieniujących, którą do tej pory miałem. Zrobiłem to jako część platformy open source opartej na iOS , więc możesz sprawdzić kod, jeśli jesteś ciekawy, jak działa jakiś konkretny krok.

Aby to zrobić, wykonuję następujące kroki:

  • Zmniejsz obraz do wartości luminancji, używając iloczynu iloczynu wartości RGB z wektorem (0,2125, 0,7154, 0,0721).
  • Oblicz pochodne X i Y, odejmując wartości kanału czerwonego od pikseli po lewej i prawej oraz powyżej i poniżej bieżącego piksela. Następnie przechowuję pochodną x podniesioną do kwadratu w kanale czerwonym, pochodną Y podniesioną do kwadratu w kanale zielonym, a iloczyn pochodnych X i Y w kanale niebieskim. Moduł cieniujący fragmenty wygląda następująco:

    precision highp float;
    
    varying vec2 textureCoordinate;
    varying vec2 leftTextureCoordinate;
    varying vec2 rightTextureCoordinate;
    
    varying vec2 topTextureCoordinate; 
    varying vec2 bottomTextureCoordinate;
    
    uniform sampler2D inputImageTexture;
    
    void main()
    {
     float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
     float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
     float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
     float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
    
     float verticalDerivative = abs(-topIntensity + bottomIntensity);
     float horizontalDerivative = abs(-leftIntensity + rightIntensity);
    
     gl_FragColor = vec4(horizontalDerivative * horizontalDerivative, verticalDerivative * verticalDerivative, verticalDerivative * horizontalDerivative, 1.0);
    }
    

    gdzie różnice są tylko przesuniętymi współrzędnymi tekstury w każdym kierunku. Obliczam je wstępnie w module cieniującym wierzchołki, aby wyeliminować zależne odczyty tekstur, które są bardzo powolne na tych mobilnych GPU.

  • Zastosuj rozmycie gaussowskie do tego obrazu pochodnego. Użyłem oddzielnego rozmycia poziomego i pionowego i skorzystałem ze sprzętowego filtrowania tekstur, aby uzyskać rozmycie dziewięciokrotnie z tylko pięcioma odczytami tekstury przy każdym przejściu. Opisuję ten moduł cieniujący w tej odpowiedzi Przepełnienie stosu .

  • Uruchom rzeczywiste obliczenie detekcji narożnika Harrisa, używając niewyraźnych wartości pochodnych wejściowych. W tym przypadku faktycznie używam obliczeń opisanych przez Alison Noble w jej doktoracie. rozprawa „Opisy powierzchni obrazu”. Moduł cieniujący, który to obsługuje, wygląda następująco:

    varying highp vec2 textureCoordinate;
    
    uniform sampler2D inputImageTexture;
    
    const mediump float harrisConstant = 0.04;
    
    void main()
    {
     mediump vec3 derivativeElements = texture2D(inputImageTexture, textureCoordinate).rgb;
    
     mediump float derivativeSum = derivativeElements.x + derivativeElements.y;
    
     // This is the Noble variant on the Harris detector, from 
     // Alison Noble, "Descriptions of Image Surfaces", PhD thesis, Department of Engineering Science, Oxford University 1989, p45.     
     mediump float harrisIntensity = (derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z)) / (derivativeSum);
    
     // Original Harris detector
     //     highp float harrisIntensity = derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z) - harrisConstant * derivativeSum * derivativeSum;
    
     gl_FragColor = vec4(vec3(harrisIntensity * 10.0), 1.0);
    }
    
  • Wykonuj lokalne nie maksymalne tłumienie i zastosuj próg, aby podświetlić piksele, które przechodzą. Używam następującego modułu cieniującego fragmenty do próbkowania ośmiu pikseli w sąsiedztwie centralnego piksela i stwierdzam, czy jest to maksimum w tej grupie:

    uniform sampler2D inputImageTexture;
    
    varying highp vec2 textureCoordinate;
    varying highp vec2 leftTextureCoordinate;
    varying highp vec2 rightTextureCoordinate;
    
    varying highp vec2 topTextureCoordinate;
    varying highp vec2 topLeftTextureCoordinate;
    varying highp vec2 topRightTextureCoordinate;
    
    varying highp vec2 bottomTextureCoordinate;
    varying highp vec2 bottomLeftTextureCoordinate;
    varying highp vec2 bottomRightTextureCoordinate;
    
    void main()
    {
        lowp float bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate).r;
        lowp float bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
        lowp float bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
        lowp vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
        lowp float leftColor = texture2D(inputImageTexture, leftTextureCoordinate).r;
        lowp float rightColor = texture2D(inputImageTexture, rightTextureCoordinate).r;
        lowp float topColor = texture2D(inputImageTexture, topTextureCoordinate).r;
        lowp float topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate).r;
        lowp float topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
    
        // Use a tiebreaker for pixels to the left and immediately above this one
        lowp float multiplier = 1.0 - step(centerColor.r, topColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, topLeftColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, leftColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, bottomLeftColor);
    
        lowp float maxValue = max(centerColor.r, bottomColor);
        maxValue = max(maxValue, bottomRightColor);
        maxValue = max(maxValue, rightColor);
        maxValue = max(maxValue, topRightColor);
    
        gl_FragColor = vec4((centerColor.rgb * step(maxValue, centerColor.r) * multiplier), 1.0);
    }
    

Ten proces generuje mapę pustkowia z twoich obiektów, która wygląda następująco:

Mapa Cornerness

Następujące punkty są identyfikowane jako rogi na podstawie nie-maksymalnego tłumienia i progowania:

Zidentyfikowane rogi

Po ustawieniu odpowiednich progów dla tego filtra może on zidentyfikować wszystkie 16 narożników na tym obrazie, chociaż zwykle umieszcza narożniki o jeden piksel wewnątrz rzeczywistych krawędzi obiektu.

Na iPhonie 4 to wykrywanie narożników można uruchomić przy 20 klatkach na sekundę na klatkach 640 x 480 wideo pochodzących z kamery, a iPhone 4S może z łatwością przetwarzać wideo o tym rozmiarze przy 60+ klatkach na sekundę. Powinno to być o wiele szybsze niż przetwarzanie związane z procesorem w przypadku takiego zadania, chociaż w tej chwili proces odczytywania punktów jest związany z procesorem i nieco wolniejszy niż powinien.

Jeśli chcesz zobaczyć to w akcji, możesz pobrać kod dla mojego frameworka i uruchomić dołączony do niego przykład FilterShowcase. Przykład wykrywania narożników Harrisa działa na wideo na żywo z kamery urządzenia, chociaż jak wspomniałem, odczyt punktów narożnych odbywa się obecnie na procesorze, co naprawdę spowalnia to. W tym celu przechodzę również do procesu opartego na GPU.

Brad Larson
źródło
1
Bardzo dobrze! Śledzę twoje ramy na githubie, wydaje się to naprawdę interesujące, gratulacje!
Stéphane Péchard
Czy masz gdzieś przykład, jak uzyskać współrzędne narożne z powrotem do procesora? Czy istnieje jakiś inteligentny sposób na GPU, czy też wymaga on odczytu, a następnie zapętla procesor przez zwracaną bitmapę w poszukiwaniu zaznaczonych pikseli?
Quasimondo
@Quasimondo - Pracowałem nad użyciem piramid histogramowych do ekstrakcji punktów: tevs.eu/files/vmv06.pdf , aby uniknąć iteracji związanej z procesorem pikseli w celu wykrywania narożnika. Ostatnio byłem trochę rozproszony, więc nie do końca to skończyłem, ale chciałbym wkrótce.
Brad Larson
Cześć @BradLarson, wiem, że to bardzo stary wątek i dziękuję za odpowiedź. Właśnie sprawdziłem KGPUImageHarrisCornerDetection.m w środowisku GPUImage. Aby wyodrębnić położenie narożnika z obrazu, użyłeś glReadPixels do odczytu obrazu do bufora, a następnie zapętliłeś bufor, aby zapisać punkty z colotByte> 0 w tablicy. Czy jest jakiś sposób, aby to wszystko zrobić w GPU, gdzie nie musimy czytać obrazu w buforze i pętli?
Sahil Bajaj
1
@SahilBajaj - Jedną z technik, które widziałem (ale nie miałem jeszcze czasu na wdrożenie), jest użycie piramid histogramowych do szybkiego wydobycia punktów z takich rzadkich obrazów jak ten. To znacznie by to przyspieszyło.
Brad Larson
3

„Solidne” detektory narożne, takie jak Shi-Tomasi i Moravec, są niezwykle powolne. sprawdź je tutaj - http://en.wikipedia.org/wiki/Corner_detection SZYBKO prawdopodobnie jest jedynym wystarczająco dobrym lekkim detektorem narożnym. Możesz poprawić FAST, wykonując nie maksymalne tłumienie - wybierz wynik FAST z najlepszym wynikiem „cornerness” (istnieje kilka intuicyjnych sposobów jego obliczenia, w tym Shi-Tomasi i Moravec jako score cornerness). Masz również wybór spośród kilku detektorów FAST - od FAST-5 do FAST-12 i FAST_ER (ostatni jest prawdopodobnie zbyt duży dla urządzeń mobilnych) Innym sposobem jest wygenerowanie FAST - pobierz generator kodu FAST ze strony autora i wytrenuj go na zbiorze prawdopodobnych obrazów. http://www.edwardrosten.com/work/fast.html

mirror2image
źródło