YUV_420_888 перевод на Samsung Galaxy S7 (Camera2)

Я написал преобразование из YUV_420_888 в Bitmap, учитывая следующую логику (как я ее понимаю):

введите описание изображения здесь

Подводя итог подходу: координаты ядер x и y сравнимы как с x, так и с y бездонной части Y-плоскости (2d-распределение) и x и y выходного битового массива. Однако U- и V-Planes имеют другую структуру, чем Y-Plane, поскольку они используют 1 байт для покрытия 4 пикселей и, кроме того, могут иметь PixelStride, который более одного, кроме того, они могут также имеют прокладку, которая может отличаться от прокладки Y-плоскости. Поэтому для эффективного доступа к Us и Vs ядром я помещаю их в 1-ю выделение и создавал индекс "uvIndex", который дает положение соответствующих U- и V в пределах этого 1-го распределения для данного ( x, y) в (без прокладки) Y-плоскости (и, следовательно, выходной битмап).

Чтобы сохранить ядро ​​rs-Kernel, я исключил область заполнения в yPlane, закрыв x-диапазон через LaunchOptions (это отражает RowStride у-плоскости, который, таким образом, можно игнорировать в ядре). Поэтому нам просто нужно рассмотреть uvPixelStride и uvRowStride в uvIndex, то есть индекс, используемый для доступа к значениям u- и v.

Это мой код:

Ядро Renderscript, названное 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 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;
}

Сторона 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 and v 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;
}

Тестирование на Nexus 7 (API 22) дает хорошие цветовые растровые изображения. Однако это устройство имеет тривиальные пиксельные полосы (= 1) и отсутствие прокладки (то есть rowstride = width). Тестирование на brandnew Samsung S7 (API 23) Я получаю картинки, цвета которых неправильны - кроме зеленых. Но картинка не показывает общего смещения по направлению к зеленому, просто кажется, что не зеленые цвета не воспроизводятся правильно. Обратите внимание, что S7 применяет пиксельную полосу u/v, равную 2, и никакое отступы.

Поскольку самая важная строка кода находится внутри rs-кода, доступ к u/v-плоскостям uint uvIndex = (...) Я думаю, может возникнуть проблема, возможно, с неправильным рассмотрением пикселей. Кто-нибудь видит решение? Спасибо.

UPDATE: я проверил все, и я уверен, что код относительно доступа y, u, v правильный. Таким образом, проблема должна быть связана с самими значениями u и v. Не зеленые цвета имеют фиолетовый наклон, и, глядя на значения u, v, они, кажется, находятся в довольно узком диапазоне около 110-150. Действительно ли возможно, что нам нужно справляться с конкретными устройствами YUV → RBG конверсий...?! Я что-то пропустил?

UPDATE 2: скорректированный код, теперь он работает благодаря Eddy Feedback.

Ответы

Ответ 1

Посмотрите

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

который вычисляет смещение строки U, V (uv_row_offset) из Y-координаты Y.

если uvPixelStride = 2, то при увеличении 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

и это неверно. Нет допустимого значения U/V-пикселя в uv_row_offset = 1 или 3, так как uvPixelStride = 2.

Вы хотите

uvPixelStride * floor(x/2)

(предполагая, что вы не доверяете себе, чтобы помнить критическое поведение округления вниз целочисленного деления, если вы это сделаете):

uvPixelStride * (x/2)

должно быть достаточно

При этом ваше отображение будет выглядеть следующим образом:

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

Посмотрите, исправляет ли это ошибки цвета. На практике неправильная адресация здесь означает, что каждый другой образец цвета будет иметь неправильную цветовую плоскость, поскольку, вероятно, базовые данные YUV являются полупланарными (так что U-плоскость начинается с V-плоскости + 1 байт, причем две плоскости чередуются)

Ответ 2

Для людей, которые сталкиваются с ошибкой

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

используйте buffer.capacity() вместо buffer.remaining()

и если вы уже сделали некоторые операции с изображением, вам нужно вызвать метод rewind() в буфере.

Ответ 3

Кроме того, для всех, кто получает

android.support.v8.renderscript.RSIllegalArgumentException: массив тоже маленький для типа распределения

Я исправил его, изменив yAlloc.copyFrom(y); на yAlloc.copy1DRangeFrom(0, y.length, y);

Ответ 4

На Samsung Galaxy Tab 5 (Tablet), версия Android 5.1.1 (22), с предполагаемым форматом YUV_420_888, следующая математика renderscript работает хорошо и создает правильные цвета:

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

Я не понимаю, почему горизонтальное значение (т.е. y) масштабируется в четыре раза вместо двух, но оно работает хорошо. Мне также нужно было избегать использования rsGetElementAtYuv_uchar_Y | U | V. Я считаю, что связанное значение шага выделения устанавливается равным нулю, а не чем-то соответствующим. Использование rsGetElementAt_uchar() - разумная работа.

На Samsung Galaxy S5 (смартфон), версия Android версии 5.0 (21), с предполагаемым форматом YUV_420_888, я не могу восстановить значения u и v, они проходят через все нули. В результате получается зеленое изображение. Светящийся в порядке, но изображение вертикально перевернуто.

Ответ 5

Этот код требует использования библиотеки совместимости RenderScript (android.support.v8.rderscript. *).

Чтобы библиотека совместимости работала с Android API 23, я обновился до gradle -plugin 2.1.0 и Build-Tools 23.0.3 в соответствии с ответом Miao Wang на Как создать сценарии Renderscript в Android Studio и заставить их работать?

Если вы выполните его ответ и получите сообщение об ошибке "Gradle требуется версия 2.10", НЕ меняйте

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

Вместо этого обновите поле distributionUrl файла Project\ gradle\wrapper\gradle -wrapper.properties в

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

и измените Файл > Настройки > Сборка, выполнение, развертывание > Инструменты сборки > Gradle > Gradle до Использовать обертку по умолчанию Gradle согласно "Gradle Требуется версия 2.10." Ошибка.

Ответ 6

Re: RSIllegalArgumentException

В моем случае это был тот случай, когда buffer.remaining() не был кратен шагу: длина последней строки была меньше шага (т.е. только до того места, где были фактические данные).