2016-12-14 2 views
1

J'utilise Camera 2 API pour enregistrer des images JPEG sur disque. J'ai actuellement 3-4 fps sur mon Nexus 5X, je voudrais l'améliorer à 20-30. C'est possible?Appareil photo 2, augmentation FPS

Changement du format de l'image en YUV Je parviens à générer 30 fps. Est-il possible de les enregistrer à cette fréquence, ou devrais-je abandonner et vivre avec mes 3-4 fps?

De toute évidence, je peux partager du code si nécessaire, mais si tout le monde est d'accord que ce n'est pas possible, je vais abandonner. Utiliser le NDK (avec libjpeg par exemple) est une option (mais évidemment je préférerais l'éviter ...).

Merci

EDIT: voici comment je convertir YUV android.media.Image à un seul octet []:

private byte[] toByteArray(Image image, File destination) { 

    ByteBuffer buffer0 = image.getPlanes()[0].getBuffer(); 
    ByteBuffer buffer2 = image.getPlanes()[2].getBuffer(); 
    int buffer0_size = buffer0.remaining(); 
    int buffer2_size = buffer2.remaining(); 

    byte[] bytes = new byte[buffer0_size + buffer2_size]; 

    buffer0.get(bytes, 0, buffer0_size); 
    buffer2.get(bytes, buffer0_size, buffer2_size); 

    return bytes; 
} 

EDIT 2: une autre méthode que je trouve pour convertir l'image en YUV un octet []:

private byte[] toByteArray(Image image, File destination) { 

    Image.Plane yPlane = image.getPlanes()[0]; 
    Image.Plane uPlane = image.getPlanes()[1]; 
    Image.Plane vPlane = image.getPlanes()[2]; 

    int ySize = yPlane.getBuffer().remaining(); 

    // be aware that this size does not include the padding at the end, if there is any 
    // (e.g. if pixel stride is 2 the size is ySize/2 - 1) 
    int uSize = uPlane.getBuffer().remaining(); 
    int vSize = vPlane.getBuffer().remaining(); 

    byte[] data = new byte[ySize + (ySize/2)]; 

    yPlane.getBuffer().get(data, 0, ySize); 

    ByteBuffer ub = uPlane.getBuffer(); 
    ByteBuffer vb = vPlane.getBuffer(); 

    int uvPixelStride = uPlane.getPixelStride(); //stride guaranteed to be the same for u and v planes 

    if (uvPixelStride == 1) { 

     uPlane.getBuffer().get(data, ySize, uSize); 
     vPlane.getBuffer().get(data, ySize + uSize, vSize); 
    } 
    else { 

     // if pixel stride is 2 there is padding between each pixel 
     // converting it to NV21 by filling the gaps of the v plane with the u values 
     vb.get(data, ySize, vSize); 
     for (int i = 0; i < uSize; i += 2) { 
      data[ySize + i + 1] = ub.get(i); 
     } 
    } 

    return data; 
} 
+0

Avez-vous utilisé la méthode de traçage ou d'autres techniques pour déterminer avec précision où votre temps est passé? Quelle résolution utilisez-vous? Est-ce que vous faites votre disque E/S sur un thread séparé? – CommonsWare

+0

Oui, le temps est passé dans la conversion YUV -> JPEG et dans les E/S disque. J'utilise la résolution maximale (4000 * 3000 ou plus). Et oui, les E/S disque sont enfilées. Mais si je multi-thread l'enregistrement de l'image, et si cela prend plus de temps que la création d'image, Ill probablement courir dans OOME (ou pas d'espace disque), non? –

+0

"le temps est passé dans la conversion YUV -> JPEG" - quelle conversion YUV-> JPEG? Pourquoi Camera2 ne vous donne-t-il pas le JPEG directement, en profitant du matériel de périphérique dédié à cette conversion? – CommonsWare

Répondre

1

Les unités de codeur JPEG dédiées sur les téléphones mobiles sont efficaces, mais généralement pas optimisé pour le débit. (Historiquement, les utilisateurs prenaient une photo toutes les secondes ou deux). En pleine résolution, le pipeline de la caméra du 5X ne générera pas de JPEG au-delà de quelques FPS.

Si vous avez besoin de débits plus élevés, vous devez capturer en YUV non compressé. Comme mentionné par CommonsWare, il n'y a pas assez de bande passante disque pour diffuser YUV non compressé en pleine résolution sur le disque, de sorte que vous pouvez seulement conserver un certain nombre d'images avant de manquer de mémoire.

Vous pouvez utiliser libjpeg-turbo ou un autre encodeur JPEG haute efficacité et voir combien d'images par seconde vous pouvez compresser vous-même - cela peut être plus élevé que l'unité JPEG matérielle. La manière la plus simple de maximiser le débit est de capturer YUV à 30fps, et d'exécuter un certain nombre de threads d'encodage JPEG en parallèle. Pour une vitesse maximale, vous devrez écrire manuellement le code en utilisant l'encodeur JPEG, car vos données source sont YUV et non RGB, ce que la plupart des interfaces de codage JPEG ont tendance à accepter (même si l'espace de couleurs d'un fichier JPEG encodé est généralement YUV aussi bien). Chaque fois qu'un thread codeur termine la trame précédente, il peut saisir l'image suivante provenant de la caméra (vous pouvez conserver un petit tampon circulaire des dernières images YUV pour simplifier).

+0

Merci pour la réponse détaillée! Savez-vous si OpenCV peut encoder suffisamment efficacement les JPEG (j'ai déjà installé OpenCV sur mon projet)? L'image YUV se présente sous la forme d'une image android.media.Image, qui stocke les données dans 3 plans. Savez-vous comment nourrir OpenCV (ou libjpeg) avec ces données? J'ai mis à jour ma question avec un exemple de code pour le convertir en byte [], mais cela ne fonctionne pas avec toutes les résolutions (1920 * 1080 fonctionne, 2592 * 1944 ne l'est pas), et je ne suis pas sûr qu'il soit fiable. Avez-vous de l'expérience avec ce format? –

+0

Votre exemple de code ne fonctionnera que si les données sous-jacentes sont NV21; Ce n'est pas garanti du tout en général (d'autres appareils Android peuvent avoir des mises en page différentes, c'est pourquoi vous devez faire attention aux lignes Image et pixels, etc.), même si c'est le cas sur un Nexus 5X. Et votre code d'exemple ne fonctionne probablement pas si rowStride! = Width, puisque vous copiez des valeurs de garbage (bien que vous ne spécifiez pas ce que "does not work" veut dire spécifiquement). –

+0

Je n'ai aucune idée de la rapidité avec laquelle l'encodage JPEG d'OpenCV est, malheureusement, et je ne me rappelle pas vraiment quels types de formats d'entrée YUV pris en charge par OpenCV. Si vous alimentez OpenCV NV21, vous devrez peut-être transférer les données de pixel de planar à semiplanar, si l'image d'entrée a pixelStride = 1, ou un ordre de plan différent. –