YUV 420 888 interprétation sur Samsung Galaxy S7 (Camera2)

j'ai écrit une conversion de YUV_420_888 en Bitmap, en considérant la logique suivante (telle que je la comprends):

enter image description here

pour résumer l'approche: les coordonnées x et y du noyau sont congruentes avec les x et y de la partie non-capitonnée du Plan Y (2D-allocation) et les x et y de la sortie-Bitmap. Les plans U et V, cependant, ont une structure différente du Plan Y, parce que ils utilisent 1 octet pour une couverture de 4 pixels, et, en outre, peuvent avoir une bande de pixel qui est plus d'un, en plus ils peuvent également avoir un rembourrage qui peut être différent de celui du Y-Plane. Par conséquent, afin d'accéder efficacement aux U et V par le noyau, je les ai placés dans des allocations 1-d et j'ai créé un index "uvIndex" qui donne la position des U - et V correspondants à l'intérieur de cette allocation 1-d, pour des coordonnées (x,y) données dans le plan (non-rembourré) Y (et, donc, la sortie Bitmap.)

afin de garder le noyau rs pauvre, j'ai exclu la zone de rembourrage dans l'yPlane en cappant la gamme x par le biais de LaunchOptions (cela reflète la bande de rang du plan y qui peut donc être ignorée à l'intérieur du noyau). Il suffit donc de considérer l'uvPixelStride et l'uvRowStride à l'intérieur de l'uvIndex, c'est - à-dire l'indice utilisé pour accéder aux valeurs u-et v -.

C'est mon code:

noyau Renderscript, nommé yuv420888.rs

  #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;
}

côté 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 sur Nexus 7 (API 22) cela renvoie des Bitmaps couleur agréable. Ce dispositif, cependant, a les bandes de pixel trivial (=1) et aucun capitonnage (i.e. rowstride=width). Test sur le brandnew Samsung S7 (API 23) je reçois des photos dont les couleurs ne sont pas correctes - sauf les vertes. Mais L'image ne montre pas un penchant général vers le vert, il semble juste que les couleurs non-vertes ne sont pas reproduites correctement. Notez que la S7 applique une bande de pixel u/v de 2, et pas de remplissage.

étant donné que la ligne de code la plus importante se trouve à l'intérieur du code rs, l'accès aux plans u/v uint uvIndex= (...) Je pense, il pourrait y avoir le problème, probablement avec la prise en compte incorrecte de pixelstrides ici. Quelqu'un voit la solution? Grâce.

mise à jour: j'ai tout vérifié, et je suis assez sûr que le code concernant l'accès de y,u,v est correct. Donc le problème doit être avec les valeurs u et v eux-mêmes. Les couleurs Non vertes ont une inclinaison pourpre,et en regardant les valeurs u, v ils semblent être dans une gamme assez étroite d'environ 110-150. Est-il vraiment possible que nous ayons besoin de faire face à des conversions YUV - > RBG spécifiques à l'appareil...?! Ai-je raté quelque chose?

mise à jour 2: ont corrigé le code, il fonctionne maintenant, grâce à la rétroaction D'Eddy.

22
demandé sur fadden 2016-03-25 04:56:04

5 réponses

Regardez

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

qui calcule votre offset U,V row (uv_row_offset) à partir de la coordonnée Y.

si uvPixelStride = 2, alors x augmente:

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

et c'est incorrect. Il n'y a pas de valeur valide de pixel U/V à uv_row_offset = 1 ou 3, puisque uvPixelStride = 2.

Vous voulez

uvPixelStride * floor(x/2)

(en supposant que vous ne vous fiez pas à vous-même pour se souvenir de la comportement critique de réduction de la division entière, si vous le faites alors):

uvPixelStride * (x/2)

devrait suffire

avec ça, votre carte devient:

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

voir si cela corrige les erreurs de couleur. En pratique, l'adresse incorrecte ici signifierait que tous les autres échantillons de couleur seraient à partir du mauvais plan de couleur, car il est probable que les données YUV sous-jacentes est semiplanar (donc le plan U commence à V plan + 1 byte, avec les deux avions intercalés)

7
répondu Eddy Talvala 2016-03-25 19:25:43

Pour les personnes qui rencontrez l'erreur

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

utiliser buffer.capacity() au lieu de buffer.remaining()

et si vous avez déjà effectué quelques opérations sur l'image, vous devrez appeler la méthode rewind() sur le buffer.

5
répondu Lolo 2016-12-21 12:28:40

en outre pour toute autre personne obtenir

android.soutien.v8.renderscript.RSIllegalArgumentException: Array too petite pour le type d'allocation

Je l'ai fixé en remplaçant yAlloc.copyFrom(y); par yAlloc.copy1DRangeFrom(0, y.length, y);

3
répondu user2486946 2017-10-18 11:23:58

Sur un Samsung Galaxy Tab 5 (Tablette) android version 5.1.1 (22), les allégations de YUV_420_888 format, la suite renderscript de mathématiques en place fonctionne bien et produit des couleurs correctes:

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);

Je ne comprends pas pourquoi la valeur horizontale (i.e., y) est graduée par un facteur de quatre au lieu de deux, mais cela fonctionne bien. Je devais également éviter l'utilisation de rsGetElementAtYuv_uchar_Y / U / V. je crois que la valeur de la foulée d'allocation associée est fixée à zéro au lieu de quelque chose approprié. L'utilisation de rsGetElementAt_uchar () est une solution raisonnable.

sur un Samsung Galaxy S5 (Smart Phone), version android 5.0 (21), avec le format allégué YUV_420_888, Je ne peux pas récupérer les valeurs u et v, ils viennent à travers comme tous les zéros. Cette résultats dans un cadre de verdure à la recherche de l'image. Lumineux est OK, mais l'image est inversée verticalement.

1
répondu Steven Punte 2016-04-09 00:50:07

ce code nécessite l'utilisation de la bibliothèque de compatibilité RenderScript (android.soutien.v8.renderscript.*).

afin de faire fonctionner la bibliothèque de compatibilité avec L'API 23 D'Android, j'ai mis à jour gradle-plugin 2.1.0 et Build-Tools 23.0.3 selon la réponse de Miao Wang à comment créer des scripts Renderscript sur Android Studio, et les faire tourner?

Si vous suivez sa réponse et obtenez une erreur "Gradle version 2.10 obligatoire "apparaît, ne change pas

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

mettez à jour le champ distributionUrl du projet\gradle\wrapper\gradle-wrapper.fichier de propriétés

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

et remplacer Fichier > Paramètres > construit,exécution,déploiement > outils de construction > Grad >Grad par utiliser l'enrubanneur de Grad par défaut comme dans " la Version de Grad 2.10 est nécessaire."Erreur .

1
répondu Nick 2017-05-23 12:10:40