2011-01-26 19 views
10

Sto cercando di imparare gli shader per implementare qualcosa nella mia app per iPhone. Finora ho compreso semplici esempi come la realizzazione di un'immagine a colori in scala di grigi, soglie, ecc. La maggior parte degli esempi comportano semplici operazioni in cui l'elaborazione dell'immagine di input pixel I(x,y) produce una semplice modifica dei colori dello stesso pixelFiltro gaussiano con ombreggiatori OpenGL

Ma che ne dici di Convolution ?. Ad esempio, l'esempio più semplice sarebbe il filtro gaussiano,

in cui il pixel dell'immagine di uscita O(x,y) dipende non solo da I(x,y) ma anche da 8 pixel circostanti.

O(x,y) = (I(x,y)+ surrounding 8 pixels values)/9; 

Normalmente, questo non può essere fatto con un singolo buffer di immagine o i pixel di ingresso cambieranno mentre il filtro viene eseguito. Come posso farlo con gli shader? Inoltre, dovrei gestire i bordi da solo? o c'è una funzione built-it o qualcosa che controlla l'accesso ai pixel non valido come I(-1,-1)?

Grazie in anticipo

PS: sarò generoso (leggi: dare un sacco di punti);)

risposta

8

Un approccio di shader-based altamente ottimizzato per l'esecuzione di una sfocatura gaussiana di nove hit was presented by Daniel Rákos . Il suo processo utilizza l'interpolazione sottostante fornita dal filtro di trama nell'hardware per eseguire un filtro a nove colpi utilizzando solo cinque letture di trama per passaggio. Questo è anche diviso in passaggi orizzontali e verticali separati per ridurre ulteriormente il numero di letture di trama richieste.

Ho eseguito un'implementazione di questo, ottimizzato per OpenGL ES e le GPU iOS, in my image processing framework (nella classe GPUImageFastBlurFilter). Nei miei test, è possibile eseguire una singola sfocatura passata di un frame 640x480 in 2.0 ms su un iPhone 4, che è piuttosto veloce.

Ho usato il seguente vertex shader:

attribute vec4 position; 
attribute vec2 inputTextureCoordinate; 

uniform mediump float texelWidthOffset; 
uniform mediump float texelHeightOffset; 

varying mediump vec2 centerTextureCoordinate; 
varying mediump vec2 oneStepLeftTextureCoordinate; 
varying mediump vec2 twoStepsLeftTextureCoordinate; 
varying mediump vec2 oneStepRightTextureCoordinate; 
varying mediump vec2 twoStepsRightTextureCoordinate; 

void main() 
{ 
    gl_Position = position; 

    vec2 firstOffset = vec2(1.3846153846 * texelWidthOffset, 1.3846153846 * texelHeightOffset); 
    vec2 secondOffset = vec2(3.2307692308 * texelWidthOffset, 3.2307692308 * texelHeightOffset); 

    centerTextureCoordinate = inputTextureCoordinate; 
    oneStepLeftTextureCoordinate = inputTextureCoordinate - firstOffset; 
    twoStepsLeftTextureCoordinate = inputTextureCoordinate - secondOffset; 
    oneStepRightTextureCoordinate = inputTextureCoordinate + firstOffset; 
    twoStepsRightTextureCoordinate = inputTextureCoordinate + secondOffset; 
} 

e la seguente Shader frammento:

precision highp float; 

uniform sampler2D inputImageTexture; 

varying mediump vec2 centerTextureCoordinate; 
varying mediump vec2 oneStepLeftTextureCoordinate; 
varying mediump vec2 twoStepsLeftTextureCoordinate; 
varying mediump vec2 oneStepRightTextureCoordinate; 
varying mediump vec2 twoStepsRightTextureCoordinate; 

// const float weight[3] = float[](0.2270270270, 0.3162162162, 0.0702702703); 

void main() 
{ 
    lowp vec3 fragmentColor = texture2D(inputImageTexture, centerTextureCoordinate).rgb * 0.2270270270; 
    fragmentColor += texture2D(inputImageTexture, oneStepLeftTextureCoordinate).rgb * 0.3162162162; 
    fragmentColor += texture2D(inputImageTexture, oneStepRightTextureCoordinate).rgb * 0.3162162162; 
    fragmentColor += texture2D(inputImageTexture, twoStepsLeftTextureCoordinate).rgb * 0.0702702703; 
    fragmentColor += texture2D(inputImageTexture, twoStepsRightTextureCoordinate).rgb * 0.0702702703; 

    gl_FragColor = vec4(fragmentColor, 1.0); 
} 

per eseguire questo. Le due passate possono essere ottenute inviando un valore 0 per lo texelWidthOffset (per il passaggio verticale), quindi alimentando quel risultato in una corsa in cui si assegna un valore 0 per lo texelHeightOffset (per il passaggio orizzontale).

Ho anche alcuni esempi più avanzati di convoluzioni nel framework sopra collegato, incluso il rilevamento del bordo Sobel.

+0

L'utilizzo dell'interpolazione lineare non è una novità. Dov'è il vantaggio del solito modo più semplice? Cerca le coordinate [-4, -2, 0, 2, 4] per ottenere un intervallo di 10 pixel e quindi applicare la ponderazione di ogni due texel? – djmj

+0

@djmj - Non sono sicuro di seguirti. Che modo più semplice intendi? Quali sono i due texel a cui ti stai riferendo? Hai un riferimento che puoi indicare che descrive questo approccio? –

+0

Vedere: http://drilian.com/journal/images/TextureGrid.png. Se si campiona a (2, 0.5) si otterrà il valore interpolato bilineare di pixel (1, 0) e (2, 0). Campionando a (2, 1) otterrai il valore medio dei 4 pixel vicini!Quindi usa solo un offset di 2 pixel e poi applica la ponderazione gaussiana. – djmj

2

Sfocatura orizzontale utilizzando l'interpolazione bilineare. La sfocatura verticale è analogica. Srotolare per ottimizzare.

//5 offsets for 10 pixel sampling! 
float[5] offset = [-4.0f, -2.0f, 0.0f, 2.0f, 4.0f]; 
//int[5] weight = [1, 4, 6, 4, 1]; //sum = 16 
float[5] weightInverse = [0.0625f, 0.25f, 0.375, 0.25f, 0.0625f]; 

vec4 finalColor = vec4(0.0f); 

for(int i = 0; i < 5; i++) 
    finalColor += texture2D(inputImage, vec2(offset[i], 0.5f)) * weightInverse[i];