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:
Les points suivants sont identifiés comme des coins basés sur la suppression et le seuillage non maximum:
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.