2016-03-25 15 views
15

ho scritto una conversione da YUV_420_888 a Bitmap, considerando la seguente logica (se ho capito bene):YUV_420_888 interpretazione sul Samsung Galaxy S7 (Camera2)

enter image description here

In sintesi l'approccio: del kernel coordinate x e y sono congruenti sia con la xey della parte non imbottita del Y-Plane (allocazione 2d) e la xey della bitmap di output. I piani U e V, tuttavia, hanno una struttura diversa rispetto al piano Y, perché usano 1 byte per una copertura di 4 pixel e, in aggiunta, possono avere un PixelStride che è più di uno, inoltre potrebbero hanno anche un riempimento che può essere diverso da quello del piano Y. Pertanto, per accedere in modo efficiente a U e V dal kernel, li ho inseriti in allocazioni 1-d e creato un indice "uvIndex" che fornisce la posizione dei corrispondenti U- e V all'interno dell'allocazione 1-d, per data (x, y) coordinate nel piano Y (non imbottito) (e, quindi, il bitmap di output).

Al fine di mantenere il rs-Kernel snello, ho escluso l'area di padding nella yPlane limitando l'intervallo x tramite LaunchOptions (ciò riflette il RowStride del piano y che quindi può essere ignorato ENTRO il kernel). Quindi dobbiamo solo considerare uvPixelStride e uvRowStride all'interno di uvIndex, cioè l'indice utilizzato per accedere ai valori u e v.

Questo è il mio codice:

Renderscript Kernel, yuv420888.rs nome

#pragma version(1) 
    #pragma rs java_package_name(com.xxxyyy.testcamera2); 
    #pragma rs_fp_relaxed 

    int32_t width; 
    int32_t height; 

    uint picWidth, uvPixelStride, uvRowStride ; 
    rs_allocation ypsIn,uIn,vIn; 

// The LaunchOptions ensure that the Kernel does not enter the padding zone of Y, so yRowStride can be ignored WITHIN the Kernel. 
uchar4 __attribute__((kernel)) doConvert(uint32_t x, uint32_t y) { 

// index for accessing the uIn's and vIn's 
uint uvIndex= uvPixelStride * (x/2) + uvRowStride*(y/2); 

// get the y,u,v values 
uchar yps= rsGetElementAt_uchar(ypsIn, x, y); 
uchar u= rsGetElementAt_uchar(uIn, uvIndex); 
uchar v= rsGetElementAt_uchar(vIn, uvIndex); 

// calc argb 
int4 argb; 
    argb.r = yps + v * 1436/1024 - 179; 
    argb.g = yps -u * 46549/131072 + 44 -v * 93604/131072 + 91; 
    argb.b = yps +u * 1814/1024 - 227; 
    argb.a = 255; 

uchar4 out = convert_uchar4(clamp(argb, 0, 255)); 
return out; 
} 

lato Java:

private Bitmap YUV_420_888_toRGB(Image image, int width, int height){ 
    // Get the three image planes 
    Image.Plane[] planes = image.getPlanes(); 
    ByteBuffer buffer = planes[0].getBuffer(); 
    byte[] y = new byte[buffer.remaining()]; 
    buffer.get(y); 

    buffer = planes[1].getBuffer(); 
    byte[] u = new byte[buffer.remaining()]; 
    buffer.get(u); 

    buffer = planes[2].getBuffer(); 
    byte[] v = new byte[buffer.remaining()]; 
    buffer.get(v); 

    // get the relevant RowStrides and PixelStrides 
    // (we know from documentation that PixelStride is 1 for y) 
    int yRowStride= planes[0].getRowStride(); 
    int uvRowStride= planes[1].getRowStride(); // we know from documentation that RowStride is the same for u and v. 
    int uvPixelStride= planes[1].getPixelStride(); // we know from documentation that PixelStride is the same for u and v. 


    // rs creation just for demo. Create rs just once in onCreate and use it again. 
    RenderScript rs = RenderScript.create(this); 
    //RenderScript rs = MainActivity.rs; 
    ScriptC_yuv420888 mYuv420=new ScriptC_yuv420888 (rs); 

    // Y,U,V are defined as global allocations, the out-Allocation is the Bitmap. 
    // Note also that uAlloc and vAlloc are 1-dimensional while yAlloc is 2-dimensional. 
    Type.Builder typeUcharY = new Type.Builder(rs, Element.U8(rs)); 
    typeUcharY.setX(yRowStride).setY(height); 
    Allocation yAlloc = Allocation.createTyped(rs, typeUcharY.create()); 
    yAlloc.copyFrom(y); 
    mYuv420.set_ypsIn(yAlloc); 

    Type.Builder typeUcharUV = new Type.Builder(rs, Element.U8(rs)); 
    // note that the size of the u's and v's are as follows: 
    //  ( (width/2)*PixelStride + padding ) * (height/2) 
    // = (RowStride       ) * (height/2) 
    // but I noted that on the S7 it is 1 less... 
    typeUcharUV.setX(u.length); 
    Allocation uAlloc = Allocation.createTyped(rs, typeUcharUV.create()); 
    uAlloc.copyFrom(u); 
    mYuv420.set_uIn(uAlloc); 

    Allocation vAlloc = Allocation.createTyped(rs, typeUcharUV.create()); 
    vAlloc.copyFrom(v); 
    mYuv420.set_vIn(vAlloc); 

    // handover parameters 
    mYuv420.set_picWidth(width); 
    mYuv420.set_uvRowStride (uvRowStride); 
    mYuv420.set_uvPixelStride (uvPixelStride); 

    Bitmap outBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 
    Allocation outAlloc = Allocation.createFromBitmap(rs, outBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); 

    Script.LaunchOptions lo = new Script.LaunchOptions(); 
    lo.setX(0, width); // by this we ignore the y’s padding zone, i.e. the right side of x between width and yRowStride 
    lo.setY(0, height); 

    mYuv420.forEach_doConvert(outAlloc,lo); 
    outAlloc.copyTo(outBitmap); 

    return outBitmap; 
} 

test sui Nexus 7 (API 22) questo restituisce bel colore Bitmap . Questo dispositivo, tuttavia, ha triviali pixeltrides (= 1) e nessun padding (cioè rowstride = width). Provando sul nuovissimo Samsung S7 (API 23) ottengo immagini i cui colori non sono corretti - tranne quelli verdi. Ma l'immagine non mostra una tendenza generale verso il verde, sembra solo che i colori non verdi non vengano riprodotti correttamente. Notare che S7 applica un pixel verticale su 2 e nessun riempimento.

Poiché la riga di codice più importante è all'interno del codice rs l'accesso degli u/v planes uint uvIndex = (...) Penso che ci potrebbe essere il problema, probabilmente con una considerazione errata di pixelstrides qui. Qualcuno vede la soluzione? Grazie.

AGGIORNAMENTO: ho controllato tutto, e sono abbastanza sicuro che il codice relativo all'accesso di y, u, v sia corretto. Quindi il problema deve essere con i valori u e v stessi. I colori non verdi hanno un'inclinazione viola, e osservando i valori di u, sembrano essere in un intervallo piuttosto ristretto di circa 110-150. È davvero possibile che dobbiamo affrontare le conversioni YUV -> RBG specifiche del dispositivo ...?! Mi sono perso qualcosa?

UPDATE 2: ha corretto il codice, ora funziona, grazie a Feedback di Eddy.

+0

assegnandomi RSIllegalArgumentException: Array troppo piccolo per il tipo di allocazione. on line "yAlloc.copyFrom (y);" qualche idea. Il telefono di prova è Nexus 6p – Umair

+0

quali valori hai per lunghezza [y], yRowStride, larghezza e altezza? – Settembrini

+0

@Settembrini Sto usando questo codice e funziona alla grande sulla maggior parte dei dispositivi, ma su Pixel viene lanciato 'android.support.v8.renderscript.RSIllegalArgumentException: Array troppo piccolo per tipo di allocazione. Come lo risolvo? Impossibile trovare molto su Google – Ryan

risposta

6

Osservare

floor((float) uvPixelStride*(x)/2) 

che calcola la riga U, V offset (uv_row_offset) da Y coordinata x.

se uvPixelStride = 2, quindi all'aumentare di X:

x = 0, uv_row_offset = 0 
x = 1, uv_row_offset = 1 
x = 2, uv_row_offset = 2 
x = 3, uv_row_offset = 3 

e questo non è corretto. Non esiste un valore di pixel U/V valido su uv_row_offset = 1 o 3, poiché uvPixelStride = 2.

Volete

uvPixelStride * floor(x/2) 

(si assumendo non fidarti di te stesso ricordare il comportamento critico di arrotondamento per difetto di divisione intera, se lo fai poi):

uvPixelStride * (x/2) 

dovrebbero essere abbastanza

Con questo, la mappatura diventa:

x = 0, uv_row_offset = 0 
x = 1, uv_row_offset = 0 
x = 2, uv_row_offset = 2 
x = 3, uv_row_offset = 2 

Vedere se questo corregge gli errori di colore. In pratica, l'indirizzamento errato qui significherebbe che ogni altro campione di colore sarebbe dal piano di colore sbagliato, poiché è probabile che i dati YUV sottostanti siano semiplanari (quindi il piano U inizia al piano V + 1 byte, con i due piani intercalati)

+0

Grazie Eddy, questo è stato considerato un errore stupido, non l'ho visto. Ora funziona perfettamente. Per quanto riguarda i piani di interlacciamento: ho notato che il byte i nel piano u è ripetuto nel byte i + 1 nel piano v, e il byte j nel piano v è ripetuto dal byte j-1 nel piano u. Pensi che potrebbero esserci altri casi, in cui l'interleaving si presenta in altre forme, così che non possiamo semplicemente assumere di ottenere tutti i dati dal piano [1] e tutti i dati v dal piano [2]? – Settembrini

+0

La documentazione dice: L'ordine dei piani nell'array restituito da Image # getPlanes() è GARANTITO tale che il piano # 0 è sempre Y, il piano # 1 è sempre U (Cb), e il piano # 2 è sempre V (Cr) . – Settembrini

+0

Come aggiornamento: se entrambe le istanze RenderScript e ScriptC_yuv420888 sono già state create all'interno di onCreate, la conversione completa richiede 50 ms per un'immagine 1280x720, il che è abbastanza soddisfacente, penso. – Settembrini

0

su un Samsung Galaxy Tab 5 (Tablet), la versione Android 5.1.1 (22), con la presunta formato YUV_420_888, il seguente matematica renderscript funziona bene e produce i colori corretti:

uchar yValue = rsGetElementAt_uchar(gCurrentFrame, x + y * yRowStride); 
uchar vValue = rsGetElementAt_uchar(gCurrentFrame, ((x/2) + (y/4) * yRowStride) + (xSize * ySize)); 
uchar uValue = rsGetElementAt_uchar(gCurrentFrame, ((x/2) + (y/4) * yRowStride) + (xSize * ySize) + (xSize * ySize)/4); 

non capisco perché il valore orizzontale (cioè, y) viene ridimensionato di un fattore quattro anziché due, ma funziona bene. Avevo anche bisogno di evitare l'uso di rsGetElementAtYuv_uchar_Y | U | V. Credo che il valore dell'asse di allocazione associato sia impostato su zero invece che su qualcosa di appropriato. L'uso di rsGetElementAt_uchar() è una soluzione ragionevole.

Su un Samsung Galaxy S5 (Smart Phone), versione Android 5.0 (21), con il presunto formato YUV_420_888, non riesco a recuperare i valori uev, vengono visualizzati come tutti zero. Ciò si traduce in un'immagine dall'aspetto verde. Luminoso è OK, ma l'immagine è capovolta verticalmente.

0

Questo codice richiede l'utilizzo della libreria di compatibilità RenderScript (android.support.v8.renderscript. *).

Al fine di ottenere la libreria di compatibilità per lavorare con API Android 23, ho aggiornato a Gradle-plugin 2.1.0 e costruire-Tools 23.0.3 come per la risposta di Miao Wang a How to create Renderscript scripts on Android Studio, and make them run?

Se si segue la sua risposta e ottenere "è necessario Gradle versione 2.10" viene visualizzato un errore, non cambiare

classpath 'com.android.tools.build:gradle:2.1.0' 

Invece, aggiornare il campo distributionUrl del Progetto \ Gradle \ involucro \ gradle-wrapper.properties file

distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 

e cambiare File> Impostazioni> Costruisce, esecuzione, distribuzione> Crea Strumenti> Gradle> Gradle a Use default Gradle involucro secondo "Gradle Version 2.10 is required." Error.

4

Per le persone che incontrano errore

android.support.v8.renderscript.RSIllegalArgumentException: Array too small for allocation type 

uso buffer.capacity() invece di buffer.remaining()

e se già fatto alcune operazioni sull'immagine, è necessario chiamare il metodo rewind() sul buffer.

+0

Ottengo l'errore ma 'buffer.capacity() == buffer.remaining()' e non ho effettuato alcuna operazione sull'immagine. Usando 'yAlloc.copy1DRangeFrom (0, y.length, y);' come descrittore sopra risolto il problema. –

2

Inoltre per chiunque altro ottenere

android.support.v8.renderscript.RSIllegalArgumentException: Array troppo piccolo per tipo di allocazione

ho riparato cambiando yAlloc.copyFrom(y); a yAlloc.copy1DRangeFrom(0, y.length, y);

Problemi correlati