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:
Die folgenden Punkte werden als Ecken basierend auf der nicht maximalen Unterdrückung und Schwellenwertbildung identifiziert:
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.