Mi è capitato di implementare qualcosa di simile su OpenGL ES 2.0 usando il rilevamento degli angoli di Harris, e mentre non ho ancora finito, ho pensato di condividere l'implementazione basata su shader che ho finora. L'ho fatto come parte di un framework open source basato su iOS , quindi puoi controllare il codice se sei curioso di sapere come funziona un determinato passaggio.
Per fare ciò, utilizzo i seguenti passi:
- Ridurre l'immagine ai valori di luminanza usando un prodotto punto dei valori RGB con il vettore (0,2125, 0,7154, 0,0721).
Calcola le derivate X e Y sottraendo i valori del canale rosso dai pixel sinistro e destro e sopra e sotto il pixel corrente. Quindi memorizzo il derivato x al quadrato nel canale rosso, il derivato Y al quadrato nel canale verde e il prodotto dei derivati X e Y nel canale blu. Lo shader di frammenti per questo assomiglia al seguente:
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);
}
dove le variazioni sono solo le coordinate della trama di offset in ciascuna direzione. Li precalcolo nello shader di vertici per eliminare le letture di texture dipendenti, che sono notoriamente lente su queste GPU mobili.
Applica una sfocatura gaussiana a questa immagine derivata. Ho usato una sfocatura orizzontale e verticale separata e ho approfittato del filtro delle trame hardware per eseguire una sfocatura a nove colpi con solo cinque letture delle trame su ogni passaggio. Descrivo questo shader in questa risposta Stack Overflow .
Eseguire il calcolo del rilevamento dell'angolo Harris effettivo utilizzando i valori della derivata di input offuscata. In questo caso, sto effettivamente utilizzando il calcolo descritto da Alison Noble nel suo dottorato di ricerca. tesi di laurea "Descrizioni delle superfici delle immagini". Lo shader che gestisce questo assomiglia al seguente:
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);
}
Esegui la soppressione non massima locale e applica una soglia per evidenziare i pixel che passano. Uso il seguente shader di frammento per campionare gli otto pixel nelle vicinanze di un pixel centrale e identificare se è il massimo in quel raggruppamento:
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);
}
Questo processo genera una mappa senza angoli dai tuoi oggetti che assomiglia a questo:
I seguenti punti sono identificati come angoli in base alla soppressione e alla soglia non massime:
Con le soglie appropriate impostate per questo filtro, è in grado di identificare tutti i 16 angoli in questa immagine, anche se tende a posizionare gli angoli di circa un pixel all'interno dei bordi effettivi dell'oggetto.
Su un iPhone 4, questo rilevamento dell'angolo può essere eseguito a 20 FPS su fotogrammi 640x480 di video provenienti dalla fotocamera e un iPhone 4S può facilmente elaborare video di quella dimensione a oltre 60 FPS. Questo dovrebbe essere molto più veloce dell'elaborazione associata alla CPU per un'attività come questa, anche se in questo momento il processo di lettura dei punti è associato alla CPU e un po 'più lento di quanto dovrebbe essere.
Se vuoi vederlo in azione puoi prendere il codice per il mio framework ed eseguire l'esempio FilterShowcase che ne deriva. L'esempio di rilevamento dell'angolo di Harris viene eseguito sul video in diretta dalla fotocamera del dispositivo, anche se, come ho già detto, la lettura dei punti d'angolo si verifica attualmente sulla CPU, il che sta rallentando. Anche per questo sto passando a un processo basato su GPU.