Как получить растровое изображение из session.update() в ARCore [Android Studio]

Я пытаюсь получить растровое изображение из текущего кадра моей ARSession с помощью ARCore. Но он всегда равен нулю. Я уже давно ищу в Интернете, но не могу понять, что я делаю неправильно.

try {
    capturedImage = mFrame.acquireCameraImage();

    ByteBuffer buffer = capturedImage.getPlanes()[0].getBuffer();

    byte[] bytes = new byte[buffer.capacity()];

    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length,null);

    if (bitmap == null) 
        Log.e(TAG,"Bitmap was NOT initialized!");

} catch(Exception e){

}

Я получаю mFrame из onDrawFrame моего GLSurfaceView, который я использую для отображения изображения с камеры. Все работает отлично, за исключением того, что мой Bitmap равен нулю.

Я использую кнопку, так что используется только один кадр, как показано ниже:

scanButton = (Button) findViewById(R.id.scanButton);
scanButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        checkbox = false;
        if (capturedImage!=null) capturedImage.close();
            BitmapMethod();
    }
});

capturedImage, buffer и bytes не равны нулю.

Возможно, что-то не так с mFrame.acquireCameraImage()?

Большое спасибо


person 高博衍    schedule 28.08.2018    source источник


Ответы (3)


Возможно, что-то не так с mFrame.acquireCameraImage()?

Нет, mFrame.acquireCameraImage() работает как положено.

Но он всегда равен нулю

Растровое изображение всегда будет равно нулю, поскольку фабрика растровых изображений не понимает передаваемые ей данные изображения.

Метод mFrame.acquireCameraImage() отвечает объектом типа Image в формате YUV или YCbCr. Эти типы изображений имеют 3 плоскости, что объясняется -file">здесь очень красиво. ByteArray, содержащиеся в этих плоскостях, могут быть прочитаны непосредственно CPU/GPU в коде native. BitmapFactory не может читать этот тип данных. Следовательно, вам нужно преобразовать это изображение YUV во что-то другое.

Для этого вам нужно использовать YuvImage для создания экземпляра YUV и последующего преобразования его в JPEG с помощью метода compressToJpeg. Получив из этого byteArray, вы можете просто делать то, что делали выше. Используйте BitmapFactory, чтобы преобразовать его в растровое изображение и добавить в свой файл ImageView.

Примечание: YUV имеет 3 плоскости. Создайте единый массив байтов из всех плоскостей и затем передайте его конструктору YUV. Хотя это и не сложно, это должно выглядеть примерно так:

//The camera image received is in YUV YCbCr Format. Get buffers for each of the planes and use them to create a new bytearray defined by the size of all three buffers combined
val cameraPlaneY = cameraImage.planes[0].buffer
val cameraPlaneU = cameraImage.planes[1].buffer
val cameraPlaneV = cameraImage.planes[2].buffer

//Use the buffers to create a new byteArray that 
val compositeByteArray = ByteArray(cameraPlaneY.capacity() + cameraPlaneU.capacity() + cameraPlaneV.capacity())

cameraPlaneY.get(compositeByteArray, 0, cameraPlaneY.capacity())
cameraPlaneU.get(compositeByteArray, cameraPlaneY.capacity(), cameraPlaneU.capacity())
cameraPlaneV.get(compositeByteArray, cameraPlaneY.capacity() + cameraPlaneU.capacity(), cameraPlaneV.capacity())

val baOutputStream = ByteArrayOutputStream()
val yuvImage: YuvImage = YuvImage(compositeByteArray, ImageFormat.NV21, cameraImage.width, cameraImage.height, null)
yuvImage.compressToJpeg(Rect(0, 0, cameraImage.width, cameraImage.height), 75, baOutputStream)
val byteForBitmap = baOutputStream.toByteArray()
val bitmap = BitmapFactory.decodeByteArray(byteForBitmap, 0, byteForBitmap.size)
imageView.setImageBitmap(bitmap)

Это просто грубый код. Возможно, у него есть возможности для улучшения. Также см. здесь.

person Clinkz    schedule 28.08.2018
comment
Большое спасибо за ваше объяснение! Это работает отлично. (Хотя сначала мне пришлось разобраться с вашим кодом на Котлине, ха-ха). - person 高博衍; 28.08.2018
comment
Очень хорошее объяснение. Работал как шарм. - person AndroDev; 08.06.2021
comment
Я рад, что люди все еще находят это полезным. Я был настолько оторван от реальности, что мой собственный ответ кажется мне сюрреалистичным. :D - person Clinkz; 10.07.2021

Я также закончил воссоздавать ту же ситуацию, с которой столкнулись вы. Я получал объект Image как Null.

После некоторых исследований я обнаружил, что проблема была в потоке логики.

Затем я написал следующий код, и он решил мою проблему:

Я определил следующее логическое значение для захвата текущего кадра при нажатии кнопки:

private static boolean captureCurrentFrame = false;

Этот код я написал в функции onClick(), чтобы получить RGB и изображение глубины текущего кадра:

public void captureFrame(View view){
    Toast.makeText(getApplicationContext(),"Capturing depth and rgb photo",Toast.LENGTH_SHORT).show();
    captureCurrentFrame = true;
  }

Этот раздел я написал в методе onDrawFrame() сразу после получения кадра из session.update():

if(captureCurrentFrame) {
        RGBImage = frame.acquireCameraImage();
        DepthImage = frame.acquireDepthImage();

        Log.d("Image","Format of the RGB Image: " + RGBImage.getFormat());
        Log.d("Image","Format of the Depth Image: " + DepthImage.getFormat());

        RGBImage.close();
        DepthImage.close();

        captureCurrentFrame = false;
      }

Причина получения Null в моем случае заключалась в том, что код в прослушивателе onClick срабатывал до прохождения метода onDraw(), в результате чего изображения не сохранялись в переменных.

Поэтому я переключил логику на onDraw() и запустил этот поток через логическую переменную, установленную слушателем.

person Amey Meher    schedule 12.09.2020

Я не знаю, ищет ли кто-нибудь ответ, но это мой код.

        Image image = mFrame.acquireCameraImage();
        byte[] nv21;
        // Get the three planes.
        ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
        ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
        ByteBuffer vBuffer = image.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);

        int width = image.getWidth();
        int height = image.getHeight();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
        byte[] byteArray = out.toByteArray();
        Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);

person user16020    schedule 21.03.2019
comment
Можете ли вы объяснить, почему это ответ и как вы изменили код OP, чтобы получить его? - person elbrant; 21.03.2019