web-dev-qa-db-fra.com

Convertir Android camera2 api YUV_420_888 en RGB

J'écris une application qui prend le flux de la caméra, le convertit en RGB, afin de faire un traitement. 

Cela fonctionne bien sur l'ancienne implémentation de la caméra qui utilise le format NV21 Yuv. Le problème que je rencontre concerne le nouveau format Yuv, YUV_420_888. L'image n'est plus convertie correctement en RVB dans le nouvel api Camera2 qui envoie le format yuv YUV_420_888 au lieu du format NV21 (YUV_420_SP).

Quelqu'un peut-il me dire s'il vous plaît comment devrais-je convertir YUV_420_888 en RVB?

Merci

15

Dans mon approche, j'utilise OpenCV Mat et le script de https://Gist.github.com/camdenfullmer/dfd83dfb0973663a7974

Tout d’abord, vous convertissez votre image YUV_420_888 en Mat avec le code indiqué dans le lien ci-dessus. 

* mImage est mon objet Image que je reçois dans ImageReader.OnImageAvailableListener

Mat mYuvMat = imageToMat(mImage);

public static Mat imageToMat(Image image) {
    ByteBuffer buffer;
    int rowStride;
    int pixelStride;
    int width = image.getWidth();
    int height = image.getHeight();
    int offset = 0;

    Image.Plane[] planes = image.getPlanes();
    byte[] data = new byte[image.getWidth() * image.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
    byte[] rowData = new byte[planes[0].getRowStride()];

    for (int i = 0; i < planes.length; i++) {
        buffer = planes[i].getBuffer();
        rowStride = planes[i].getRowStride();
        pixelStride = planes[i].getPixelStride();
        int w = (i == 0) ? width : width / 2;
        int h = (i == 0) ? height : height / 2;
        for (int row = 0; row < h; row++) {
            int bytesPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
            if (pixelStride == bytesPerPixel) {
                int length = w * bytesPerPixel;
                buffer.get(data, offset, length);

                if (h - row != 1) {
                    buffer.position(buffer.position() + rowStride - length);
                }
                offset += length;
            } else {


                if (h - row == 1) {
                    buffer.get(rowData, 0, width - pixelStride + 1);
                } else {
                    buffer.get(rowData, 0, rowStride);
                }

                for (int col = 0; col < w; col++) {
                    data[offset++] = rowData[col * pixelStride];
                }
            }
        }
    }

    Mat mat = new Mat(height + height / 2, width, CvType.CV_8UC1);
    mat.put(0, 0, data);

    return mat;
}

Nous avons 1 canal YUV Mat. Définir une nouvelle image pour BGR (pas encore RVB):

Mat bgrMat = new Mat(mImage.getHeight(), mImage.getWidth(),CvType.CV_8UC4);

Je viens juste de commencer à apprendre OpenCV, ce qui ne devrait pas forcément être un mat à 4 canaux, mais plutôt à trois, mais cela fonctionne pour moi. .

Imgproc.cvtColor(mYuvMat, bgrMat, Imgproc.COLOR_YUV2BGR_I420);

Nous pouvons maintenant effectuer tous les traitements d'image, comme la recherche de contours, de couleurs, de cercles, etc. Pour imprimer une image à l'écran, nous devons la convertir en bitmap:

Mat rgbaMatOut = new Mat();
Imgproc.cvtColor(bgrMat, rgbaMatOut, Imgproc.COLOR_BGR2RGBA, 0);
final Bitmap bitmap = Bitmap.createBitmap(bgrMat.cols(), bgrMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(rgbaMatOut, bitmap);

J'ai tous mes traitements d'images dans un fil séparé, donc pour configurer mon ImageView, je dois le faire sur le fil d'interface utilisateur.

runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if(bitmap != null) {
                            mImageView.setImageBitmap(bitmap);
                        }
                    }
                });
8
Daniel Więcek

Camera2 YUV_420_888 en Tapis RVB (à l'open) en Java

@Override
    public void onImageAvailable(ImageReader reader){
        Image image = null;

        try {
            image = reader.acquireLatestImage();
            if (image != null) {

                byte[] nv21;
                ByteBuffer yBuffer = mImage.getPlanes()[0].getBuffer();
                ByteBuffer uBuffer = mImage.getPlanes()[1].getBuffer();
                ByteBuffer vBuffer = mImage.getPlanes()[2].getBuffer();

                int ySize = yBuffer.remaining();
                int uSize = uBuffer.remaining();
                int vSize = vBuffer.remaining();

                nv21 = new byte[ySize + uSize + vSize];

                //U and V are swapped
                yBuffer.get(nv21, 0, ySize);
                vBuffer.get(nv21, ySize, vSize);
                uBuffer.get(nv21, ySize + vSize, uSize);

                Mat mRGB = getYUV2Mat(nv21);



            }
        } catch (Exception e) {
            Log.w(TAG, e.getMessage());
        }finally{
            image.close();// don't forget to close
        }
    }



    public Mat getYUV2Mat(byte[] data) {
    Mat mYuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CV_8UC1);
    mYuv.put(0, 0, data);
    Mat mRGB = new Mat();
    cvtColor(mYuv, mRGB, Imgproc.COLOR_YUV2RGB_NV21, 3);
    return mRGB;
}
1
Shyam Kumar

Avez-vous essayé d'utiliser ce script? C'est une réponse postée par yydcdut on this question

https://github.com/pinguo-yuyidong/Camera2/blob/master/camera2/src/main/rs/yuv2rgb.rs

0
Ciprian

Environ 10 fois plus rapide que la fonction "imageToMat" mentionnée ci-dessus est le code suivant:

Image image = reader.acquireLatestImage();
...
Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CvType.CV_8UC1);
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
final byte[] data = new byte[buffer.limit()];
buffer.get(data);
yuv.put(0, 0, data);
...
image.close();
0
coyer

J'ai donc rencontré exactement le même problème, car j'avais un code qui prenait les données d'octet [] au format YUV_420_SP de OnPreviewFrame () et les convertissait au format RGB.

La clé ici est que les «anciennes» données dans l'octet [] sont les suivantes: YYYYYY ... CrCbCrCbCrCb, et les «nouvelles» données de l'API Camera2 sont divisées en 3 plans: 0 = Y, 1 = Cb, 2 = Cr ., d'où vous pouvez obtenir chaque octet [] s. Donc, tout ce que vous avez à faire est de réorganiser les nouvelles données sous la forme d'un tableau unique qui correspond à l'ancien format, que vous pouvez transmettre à vos fonctions toRGB () existantes:

Image.Plane[] planes = image.getPlanes(); // in YUV220_888 format
int acc = 0, i;
ByteBuffer[] buff = new ByteBuffer[planes.length];

for (i = 0; i < planes.length; i++) {
   buff[i] = planes[i].getBuffer();
   acc += buff[i].capacity();
}
byte[] data = new byte[acc],
   tmpCb = new byte[buff[1].capacity()] , tmpCr = new byte[buff[2].capacity()];

buff[0].get(data, 0, buff[0].capacity()); // Y
acc = buff[0].capacity();

buff[2].get(tmpCr, 0, buff[2].capacity()); // Cr
buff[1].get(tmpCb, 0, buff[1].capacity()); // Cb

for (i=0; i<tmpCb.length; i++) {
    data[acc] = tmpCr[i];
    data[acc + 1] = tmpCb[i];
    acc++;
}

..et les données [] sont maintenant formatées comme l'ancien YUV_420_SP.

(espérons que cela aidera quelqu'un malgré les années passées ..)

0
Gutiman