Comment détecter les coins dans une image binaire avec OpenGL?

13

J'ai des images binaires 160x120 telles que:

image originale

Je voudrais détecter les coins de ces taches blanches. Ils sont auparavant fermés par la morphologie mathématique donc il ne devrait pas y avoir de coins intérieurs. Dans ce cas spécifique, je voudrais 16 coins, comme:

exemple de détection de coins

Ma première tentative a été d'utiliser certaines fonctions OpenCV comme goodFeaturesToTrack ou FAST mais elles sont particulièrement lentes (en plus FAST est très instable). Mon idée serait de faire un tel calcul sur le GPU, car mon image source en provient. J'ai cherché des idées sur le Web pour écrire de tels shaders (j'utilise OpenGL ES 2.0), mais je n'ai rien trouvé de concret. Une idée comment je pourrais démarrer un tel algorithme?

Stéphane Péchard
la source
2
FAST est lent? :)
endolith
1
oui, drôle non? en fait, c'est plus rapide que les algorithmes précédents comme SURF ou SIFT, mais c'est moins précis, assez instable d'une image à l'autre et toujours pas assez rapide pour être fait sur le CPU
Stéphane Péchard
Dans quelle mesure est-il important de les détecter avec précision sur chaque image? À quelle vitesse les rectangles se déplacent-ils? Est-il correct de détecter les coins sur la plupart des images et de les interpoler sur les images où l'algorithme manque?
justis
@justis bien, la façon dont je le fais en ce moment (grâce à l'utilisation des fonctions cvFindContours () et cvApproxPoly () d'OpenCV) n'est pas très stable dans le temps, donc je filtre le résultat avec un filtre passe-bas, introduisant un décalage. Pensez-vous que je peux obtenir un résultat plus stable avec une interpolation?
Stéphane Péchard

Réponses:

3

Sur quelle taille d'images travaillez-vous? À quelle fréquence d'images? Sur quel matériel? FAST est joli, euh, rapide dans mon expérience.

J'ai également vu FAST utilisé comme détecteur de ROI avec goodFeaturesToTrack exécuté sur les ROI identifiés pour fournir une meilleure stabilité sans exécuter la pénalité de gFTT sur l'image entière.

Le détecteur de coin "Harris" est également potentiellement très rapide car il est composé d'opérations très simples (pas de sqrt () par pixel par exemple!) - pas aussi stable que gFTT, mais peut-être plus que FAST.

(En termes d'implémentation GPU, Google gpu cornersemble présenter beaucoup de liens, mais je n'ai aucune idée de leur adéquation - j'ai tendance à implémenter dans FPGA.)

Martin Thompson
la source
Mes images sont 160x120, soi-disant à 30 images par seconde, sur un iPhone, mais bien sûr, l'application a beaucoup plus à faire :-) J'ai vu une application implémenter FAST assez rapidement sur un tel appareil, mais ce n'était qu'une démo seulement faire ça ... C'est pourquoi je cherche des solutions basées sur le GPU.
Stéphane Péchard
15

Il se trouve que j'implémentais quelque chose comme ça sur OpenGL ES 2.0 en utilisant la détection de coin Harris, et bien que je ne sois pas complètement terminé, je pensais partager l'implémentation basée sur les shaders que j'ai jusqu'à présent. J'ai fait cela dans le cadre d'un framework open source basé sur iOS , vous pouvez donc consulter le code si vous êtes curieux de savoir comment fonctionne une étape particulière.

Pour ce faire, j'utilise les étapes suivantes:

  • Réduisez l'image à ses valeurs de luminance en utilisant un produit scalaire des valeurs RVB avec le vecteur (0,2125, 0,7154, 0,0721).
  • Calculez les dérivées X et Y en soustrayant les valeurs du canal rouge des pixels gauche et droit et au-dessus et en dessous du pixel actuel. Je stocke ensuite la dérivée x au carré dans le canal rouge, la dérivée Y au carré dans le canal vert et le produit des dérivés X et Y dans le canal bleu. Le fragment shader pour cela ressemble à ceci:

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

    où les variations ne sont que les coordonnées de texture décalées dans chaque direction. Je les précalcule dans le vertex shader pour éliminer les lectures de texture dépendantes, qui sont notoirement lentes sur ces GPU mobiles.

  • Appliquez un flou gaussien à cette image dérivée. J'ai utilisé un flou horizontal et vertical séparé, et profite du filtrage de texture matériel pour faire un flou à neuf coups avec seulement cinq lectures de texture à chaque passage. Je décris ce shader dans cette réponse Stack Overflow .

  • Exécutez le calcul de détection de coin Harris réel en utilisant les valeurs dérivées d'entrée floues. Dans ce cas, j'utilise en fait le calcul décrit par Alison Noble dans son doctorat. dissertation "Descriptions des surfaces d'images". Le shader qui gère cela ressemble à ceci:

    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);
    }
    
  • Effectuez une suppression locale non maximale et appliquez un seuil pour mettre en surbrillance les pixels qui passent. J'utilise le shader de fragment suivant pour échantillonner les huit pixels au voisinage d'un pixel central et identifier s'il s'agit ou non du maximum dans ce regroupement:

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

Ce processus génère une carte de cornerness à partir de vos objets qui ressemble à ceci:

Carte de Cornerness

Les points suivants sont identifiés comme des coins basés sur la suppression et le seuillage non maximum:

Coins identifiés

Avec des seuils appropriés définis pour ce filtre, il peut identifier les 16 coins de cette image, bien qu'il ait tendance à placer les coins à environ un pixel à l'intérieur des bords réels de l'objet.

Sur un iPhone 4, cette détection de coin peut être exécutée à 20 FPS sur des images vidéo 640x480 provenant de la caméra, et un iPhone 4S peut facilement traiter des vidéos de cette taille à 60+ FPS. Cela devrait être beaucoup plus rapide que le traitement lié au processeur pour une tâche comme celle-ci, bien que le processus de lecture des points soit actuellement lié au processeur et un peu plus lent qu'il ne devrait l'être.

Si vous voulez voir cela en action, vous pouvez récupérer le code de mon framework et exécuter l'exemple FilterShowcase qui l'accompagne. L'exemple de détection de coin Harris fonctionne sur la vidéo en direct de la caméra de l'appareil, bien que, comme je l'ai mentionné, la lecture des points de coin se produise actuellement sur le processeur, ce qui ralentit vraiment cela. Je passe également à un processus basé sur GPU.

Brad Larson
la source
1
Très agréable! Je suis ton framework sur github, ça me semble vraiment intéressant, bravo!
Stéphane Péchard
Avez-vous un exemple quelque part comment récupérer les coordonnées des coins sur le CPU? Existe-t-il un moyen intelligent de GPU ou nécessite-t-il une relecture puis une boucle sur le processeur via le bitmap renvoyé à la recherche de pixels marqués?
Quasimondo
@Quasimondo - J'ai travaillé sur l'utilisation de pyramides d'histogramme pour l'extraction de points: tevs.eu/files/vmv06.pdf afin d'éviter l'itération liée au CPU sur les pixels pour la détection des coins. J'ai été un peu distrait ces derniers temps, donc je n'ai pas tout à fait fini, mais j'aimerais bientôt.
Brad Larson
Bonjour @BradLarson, je sais que c'est un fil très ancien et merci pour votre réponse. Je viens de vérifier KGPUImageHarrisCornerDetection.m dans le framework GPUImage. Pour extraire l'emplacement du coin de l'image, vous avez utilisé glReadPixels pour lire l'image dans le tampon, puis bouclé sur le tampon pour stocker des points avec colotByte> 0 dans un tableau. Existe-t-il un moyen de faire tout cela dans le GPU où nous n'avons pas à lire l'image dans le tampon et la boucle?
Sahil Bajaj
1
@SahilBajaj - Une technique que j'ai vue (et que je n'ai pas encore eu le temps de mettre en œuvre) consiste à utiliser des pyramides d'histogramme pour effectuer une extraction rapide de points à partir d'images clairsemées comme celle-ci. Cela accélérerait considérablement cela.
Brad Larson
3

Les détecteurs de coin "robustes" comme Shi-Tomasi et Moravec sont notoirement lents. vérifiez-les ici - http://en.wikipedia.org/wiki/Corner_detection FAST est probablement le seul détecteur de coin assez bon et léger. Vous pouvez améliorer FAST en effectuant une suppression non maximale - choisissez la sortie FAST avec le meilleur score de "cornerness" (il existe plusieurs façons intuitives de le calculer, y compris Shi-Tomasi et Moravec comme score de cornerness). Vous avez également le choix entre plusieurs détecteurs FAST - de FAST-5 à FAST-12 et FAST_ER (le dernier est probablement trop énorme pour les mobiles) Une autre façon est de générer FAST - obtenez le générateur de code FAST depuis le site de l'auteur et entraînez-le sur l'ensemble des images probables. http://www.edwardrosten.com/work/fast.html

mirror2image
la source