Wie erkennt man mit OpenGL Ecken in Binärbildern?


13

Ich habe binäre 160x120 Bilder wie:

Original Bild

Ich möchte Ecken dieser weißen Flecken erkennen. Sie wurden zuvor durch mathematische Morphologie geschlossen, sodass keine inneren Ecken vorhanden sein sollten. In diesem speziellen Fall würde ich 16 Ecken wünschen, wie:

Beispiel der Eckenerkennung

Mein erster Versuch war die Verwendung einiger OpenCV-Funktionen wie goodFeaturesToTrack oder FAST, aber diese sind besonders langsam (und FAST ist sehr instabil). Meine Idee wäre, eine solche Berechnung auf der GPU durchzuführen, da mein Quellbild davon stammt. Ich habe im Internet nach Ideen zum Schreiben solcher Shader gesucht (ich verwende OpenGL ES 2.0), aber nichts Konkretes gefunden. Irgendeine Idee, wie ich einen solchen Algorithmus starten könnte?


2
SCHNELL ist langsam? :)
Endolith

1
ja, witzig oder? Tatsächlich ist es schneller als frühere Algorithmen wie SURF oder SIFT, aber es ist weniger präzise, ​​ziemlich instabil von einem Bild zum anderen und immer noch nicht schnell genug, um auf der CPU ausgeführt zu werden
Stéphane Péchard

Wie wichtig ist es, diese bei jedem Frame genau zu erkennen? Wie schnell bewegen sich die Rechtecke? Ist es in Ordnung, die Ecken der meisten Frames zu erkennen und sie in den Frames zu interpolieren, in denen der Algorithmus fehlt?
Justis

@justis gut, die Art und Weise, wie ich es jetzt mache (durch die Verwendung der OpenCV-Funktionen cvFindContours () und cvApproxPoly ()), ist im Zeitverlauf nicht sehr stabil, so dass ich das Ergebnis mit einem Tiefpassfilter filtere, der eine Verzögerung einführt. Glaubst du, ich kann mit einer Interpolation ein stabileres Ergebnis erzielen?
Stéphane Péchard

Antworten:


3

Mit welcher Bildgröße arbeiten Sie? Mit welcher Bildrate? Auf welcher Hardware? SCHNELL ist meiner Erfahrung nach hübsch, ähm, schnell.

Ich habe auch gesehen, wie FAST als ROI-Detektor mit goodFeaturesToTrack für die identifizierten ROIs verwendet wurde, um eine bessere Stabilität zu erzielen, ohne dass der gFTT-Nachteil für das gesamte Bild auftritt.

Der "Harris" -Eckendetektor ist möglicherweise auch sehr schnell, da er aus sehr einfachen Operationen besteht (zum Beispiel kein sqrt () pro Pixel!) - nicht so stabil wie gFTT, aber möglicherweise noch stabiler als FAST.

(In Bezug auf die GPU-Implementierung gpu cornerscheint Googeln eine Menge Links zu bieten , aber ich habe keine Ahnung, wie geeignet sie sein könnten - ich tendiere dazu, sie in FPGA zu implementieren.)


Meine Bilder sind 160x120, angeblich mit 30 Bildern pro Sekunde, auf einem iPhone, aber die Anwendung hat natürlich noch viel mehr zu tun :-) Ich habe gesehen, dass eine App auf einem solchen Gerät recht schnell FAST implementiert, aber es war nur eine Demo das mache ich ... deshalb suche ich nach gpu-basierten lösungen.
Stéphane Péchard

15

Ich habe gerade so etwas auf OpenGL ES 2.0 mithilfe der Harris-Eckenerkennung implementiert, und obwohl ich noch nicht vollständig fertig bin, dachte ich, ich würde die bisherige Shaderbasierte Implementierung teilen. Ich habe dies als Teil eines iOS-basierten Open-Source-Frameworks durchgeführt , sodass Sie den Code überprüfen können, wenn Sie neugierig sind, wie ein bestimmter Schritt funktioniert.

Dazu benutze ich die folgenden Schritte:

  • Reduzieren Sie das Bild mit einem Skalarprodukt der RGB-Werte mit dem Vektor (0,2125, 0,7154, 0,0721) auf seine Luminanzwerte.
  • Berechnen Sie die X- und Y-Ableitungen, indem Sie die Rotkanalwerte von den Pixeln links und rechts sowie über und unter dem aktuellen Pixel subtrahieren. Ich speichere dann das Quadrat der x-Ableitung im roten Kanal, das Quadrat der Y-Ableitung im grünen Kanal und das Produkt der X- und Y-Ableitungen im blauen Kanal. Der Fragment-Shader dafür sieht folgendermaßen aus:

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

    wobei die Abweichungen nur die versetzten Texturkoordinaten in jeder Richtung sind. Ich berechne diese im Vertex-Shader vor, um abhängige Textur-Lesevorgänge zu eliminieren, die auf diesen mobilen GPUs notorisch langsam sind.

  • Wenden Sie eine Gaußsche Unschärfe auf dieses abgeleitete Bild an. Ich habe eine getrennte horizontale und vertikale Unschärfe verwendet und die Hardware-Texturfilterung genutzt, um eine Unschärfe mit neun Treffern mit nur fünf Texturlesevorgängen bei jedem Durchgang zu erzielen. Ich beschreibe diesen Shader in dieser Stack Overflow-Antwort .

  • Führen Sie die tatsächliche Harris-Eckenerkennungsberechnung mit den Werten der unscharfen Eingabeableitung aus. In diesem Fall verwende ich die Berechnung, die Alison Noble in ihrer Doktorarbeit beschrieben hat. Dissertation "Beschreibungen von Bildoberflächen". Der Shader, der dies erledigt, sieht folgendermaßen aus:

    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);
    }
    
  • Führen Sie eine lokale Unterdrückung ohne Maximalwert durch, und wenden Sie einen Schwellenwert an, um die durchlaufenden Pixel hervorzuheben. Ich benutze den folgenden Fragment-Shader, um die acht Pixel in der Nachbarschaft eines zentralen Pixels abzutasten und festzustellen, ob es das Maximum in dieser Gruppierung ist oder nicht:

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

Bei diesem Vorgang wird aus Ihren Objekten eine Kornniveaukarte erstellt, die wie folgt aussieht:

Cornerness Karte

Die folgenden Punkte werden als Ecken basierend auf der nicht maximalen Unterdrückung und Schwellenwertbildung identifiziert:

Erkannte Ecken

Wenn die richtigen Schwellenwerte für diesen Filter festgelegt sind, können alle 16 Ecken in diesem Bild identifiziert werden, obwohl die Ecken in der Regel um etwa ein Pixel innerhalb der tatsächlichen Kanten des Objekts platziert werden.

Auf einem iPhone 4 kann diese Eckenerkennung mit 20 FPS auf 640 x 480 Videoframes ausgeführt werden, die von der Kamera stammen, und ein iPhone 4S kann problemlos Videos dieser Größe mit mehr als 60 FPS verarbeiten. Dies sollte für eine Aufgabe wie diese viel schneller als die CPU-gebundene Verarbeitung sein, obwohl der Prozess des Zurücklesens der Punkte derzeit CPU-gebunden und etwas langsamer ist, als er sein sollte.

Wenn Sie dies in Aktion sehen möchten, können Sie den Code für mein Framework abrufen und das dazugehörige FilterShowcase-Beispiel ausführen. Das Harris-Eckenerkennungsbeispiel läuft dort mit Live-Video von der Gerätekamera, obwohl, wie ich bereits erwähnte, das Zurücklesen von Eckpunkten derzeit auf der CPU erfolgt, was dies wirklich verlangsamt. Auch dafür bin ich auf einen GPU-basierten Prozess umgestiegen.


1
Sehr schön! Ich folge deinem Framework auf Github, es scheint wirklich interessant zu sein, Glückwunsch!
Stéphane Péchard

Hast du irgendwo ein Beispiel, wie man die Eckkoordinaten tatsächlich wieder in die CPU bekommt? Gibt es eine Möglichkeit für eine intelligente GPU oder ist ein Rücklesen und anschließendes Durchlaufen der zurückgegebenen Bitmap durch die CPU erforderlich, um nach markierten Pixeln zu suchen?
Quasimondo

@Quasimondo - Ich habe daran gearbeitet, Histogrammpyramiden für die Punktextraktion zu verwenden : tevs.eu/files/vmv06.pdf , um die CPU-gebundene Iteration über Pixel für die Eckenerkennung zu vermeiden. Ich war in letzter Zeit etwas abgelenkt, habe das also noch nicht ganz beendet, aber ich würde es gerne bald tun.
Brad Larson

Hallo @BradLarson, ich weiß, dass dies ein sehr alter Thread ist und danke Ihnen für Ihre Antwort. Ich habe gerade KGPUImageHarrisCornerDetection.m im GPUImage-Framework überprüft. Um die Eckposition aus dem Bild zu extrahieren, haben Sie glReadPixels verwendet, um das Bild in den Puffer zu lesen. Anschließend haben Sie den Puffer in einer Schleife durchlaufen, um Punkte mit colotByte> 0 in einem Array zu speichern. Gibt es eine Möglichkeit, dies alles in der GPU zu tun, wo wir das Bild nicht in Buffer and Loop lesen müssen?
Sahil Bajaj

1
@SahilBajaj - Eine Technik, die ich gesehen habe (und für deren Implementierung ich noch keine Zeit hatte), ist die Verwendung von Histogramm-Pyramiden , um Punkte aus so wenigen Bildern schnell zu extrahieren. Das würde das erheblich beschleunigen.
Brad Larson

3

"Robuste" Eckendetektoren wie Shi-Tomasi und Moravec sind notorisch langsam. Überprüfen Sie sie hier - http://en.wikipedia.org/wiki/Corner_detection FAST ist wahrscheinlich der einzige leichte Eckendetektor, der gut genug ist. Sie können FAST verbessern, indem Sie die Unterdrückung auf ein Minimum beschränken. Wählen Sie FAST-Ausgabe mit dem besten "Cornerness" -Wert. von FAST-5 bis FAST-12 und FAST_ER (letzteres ist wahrscheinlich zu umfangreich für Mobilgeräte) Eine andere Möglichkeit ist, FAST zu generieren - holen Sie sich den FAST-Code-Generator von der Autorenseite und trainieren Sie ihn auf der Menge der wahrscheinlichen Bilder. http://www.edwardrosten.com/work/fast.html


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.