YUV 420 888 интерпретация на Samsung Galaxy S7 (Camera2)

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

enter image description here

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

для того, чтобы сохранить rs-ядро lean, я исключил область заполнения в yPlane, закрыв x-диапазон через LaunchOptions (это отражает RowStride y-плоскости, которая, таким образом, может быть проигнорирована в ядре). Поэтому нам просто нужно рассмотреть 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'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;
}

сторона 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;
}

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

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

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

обновление 2: исправили код, он работает сейчас, благодаря обратной связи Эдди.

5 ответов


посмотреть

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

который вычисляет смещение строки U,V (uv_row_offset)из X-координаты 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 байт, с чередованием двух плоскостей)


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

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

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

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


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

android.поддержка.В8.решение RenderScript.RSIllegalArgumentException: массив тоже малый для типа распределения

я исправил это, изменив yAlloc.copyFrom(y); до yAlloc.copy1DRangeFrom(0, y.length, y);


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

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, они проходят как все нули. Это приводит к зеленому изображению. Световой нормально, но изображение вертикально перевернуто.


этот код требует использования библиотеки совместимости RenderScript (android.поддержка.В8.решение RenderScript.*).

чтобы получить библиотеку совместимости для работы с Android API 23, я обновил gradle-plugin 2.1.0 и Build-Tools 23.0.3 в соответствии с ответом Мяо Вана на как создать скрипты Renderscript на Android Studio и заставить их работать?

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

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

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

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

, и Файл > Настройки > Сборки, Выполнение, Развертывание > Инструменты Сборки > Gradle >Gradle to использовать оболочку gradle по умолчанию по состоянию на " требуется Gradle версии 2.10.- Ошибка!--6-->.