Проблемы при масштабировании изображения YUV с использованием библиотеки libyuv

Я разрабатываю приложение для камеры на основе Camera API 2 и обнаружил несколько проблем с использованием libyuv. . Я хочу преобразовать YUV_420_888 изображений, полученных из ImageReader, но у меня возникают проблемы с масштабированием на повторно обрабатываемой поверхности.

По сути: изображения получаются с оттенками зеленого вместо соответствующих тонов (я экспортирую файлы .yuv и проверяю их с помощью http://rawpixels.net/).

Вы можете увидеть пример ввода здесь: введите здесь описание изображения

И что я получаю после выполнения масштабирования: введите здесь описание изображения

Я думаю, что делаю что-то не так с шагами или предоставляю неверный формат YUV (может быть, мне нужно преобразовать изображение в другой формат?). Однако я не могу понять, где ошибка, так как я не знаю, как соотнести зеленый цвет с алгоритмом масштабирования.

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

#include <jni.h>
#include <stdint.h>
#include <android/log.h>
#include <inc/libyuv/scale.h>
#include <inc/libyuv.h>
#include <stdio.h>


#define  LOG_TAG    "libyuv-jni"

#define unused(x) UNUSED_ ## x __attribute__((__unused__))
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS_)

struct YuvFrame {
    int width;
    int height;
    uint8_t *data;
    uint8_t *y;
    uint8_t *u;
    uint8_t *v;
};

static struct YuvFrame i420_input_frame;
static struct YuvFrame i420_output_frame;

extern "C" {

JNIEXPORT jbyteArray JNICALL
Java_com_android_camera3_camera_hardware_session_output_photo_yuv_YuvJniInterface_scale420YuvByteArray(
        JNIEnv *env, jclass /*clazz*/, jbyteArray yuvByteArray_, jint src_width, jint src_height,
        jint out_width, jint out_height) {

    jbyte *yuvByteArray = env->GetByteArrayElements(yuvByteArray_, NULL);

    //Get input and output length
    int input_size = env->GetArrayLength(yuvByteArray_);
    int out_size = out_height * out_width;

    //Generate input frame
    i420_input_frame.width = src_width;
    i420_input_frame.height = src_height;
    i420_input_frame.data = (uint8_t *) yuvByteArray;
    i420_input_frame.y = i420_input_frame.data;
    i420_input_frame.u = i420_input_frame.y + input_size;
    i420_input_frame.v = i420_input_frame.u + input_size / 4;

    //Generate output frame
    free(i420_output_frame.data);
    i420_output_frame.width = out_width;
    i420_output_frame.height = out_height;
    i420_output_frame.data = new unsigned char[out_size * 3 / 2];
    i420_output_frame.y = i420_output_frame.data;
    i420_output_frame.u = i420_output_frame.y + out_size;
    i420_output_frame.v = i420_output_frame.u + out_size / 4;
    libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBilinear;

    int result = I420Scale(i420_input_frame.y, i420_input_frame.width,
                           i420_input_frame.u, i420_input_frame.width / 2,
                           i420_input_frame.v, i420_input_frame.width / 2,
                           i420_input_frame.width, i420_input_frame.height,
                           i420_output_frame.y, i420_output_frame.width,
                           i420_output_frame.u, i420_output_frame.width / 2,
                           i420_output_frame.v, i420_output_frame.width / 2,
                           i420_output_frame.width, i420_output_frame.height,
                           mode);
    LOGD("Image result %d", result);
    env->ReleaseByteArrayElements(yuvByteArray_, yuvByteArray, 0);
    return NULL;
}

person Francisco Durdin Garcia    schedule 22.03.2017    source источник
comment
Что бы это ни стоило, полностью зеленое изображение YUV, вероятно, означает, что ваш byteArray заполнен 0.   -  person Alex Taylor    schedule 23.03.2017
comment
это не так, данные трех входных и выходных массивов байтов имеют информацию   -  person Francisco Durdin Garcia    schedule 23.03.2017
comment
@FranciscoDurdinGarcia вы пробовали мой ответ?   -  person gmetax    schedule 30.03.2017
comment
@gmetax да, я пробовал, и результат тот же. Это не о выходном кадре, я думаю, это больше о формате этого byteArray, который не соответствует методу I420Scale   -  person Francisco Durdin Garcia    schedule 30.03.2017
comment
Вы пытались распечатать i420_input_frame.data, чтобы проверить с помощью rawpixels.net правильность ввода?   -  person gmetax    schedule 30.03.2017
comment
Да, изображение, которое я разместил по вопросу (хорошее), представляет собой входной кадр, экспортированный в YUV и визуализированный в rawpixels.net.   -  person Francisco Durdin Garcia    schedule 30.03.2017
comment
для отладки я бы напечатал i420_input_frame.data i420_input_frame.y i420_input_frame.u i420_input_frame.v перед //Generate output frame, чтобы убедиться, что все указатели верны   -  person gmetax    schedule 30.03.2017


Ответы (5)


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

    ...
    //Get input and output length
    int input_size = env->GetArrayLength(yuvByteArray_);
    int y_size = src_width * src_height;
    int out_size = out_height * out_width;

    //Generate input frame
    i420_input_frame.width = src_width;
    i420_input_frame.height = src_height;
    i420_input_frame.data = (uint8_t *) yuvByteArray;
    i420_input_frame.y = i420_input_frame.data;
    i420_input_frame.u = i420_input_frame.y + y_size;
    i420_input_frame.v = i420_input_frame.u + y_size / 4;

    //Generate output frame
    free(i420_output_frame.data);
    i420_output_frame.width = out_width;
    i420_output_frame.height = out_height;
    i420_output_frame.data = new unsigned char[out_size * 3 / 2];
    i420_output_frame.y = i420_output_frame.data;
    i420_output_frame.u = i420_output_frame.y + out_size;
    i420_output_frame.v = i420_output_frame.u + out_size / 4;
    ...

вероятно, ваш код основан на этом https://github.com/begeekmyfriend/yasea/blob/master/library/src/main/libenc/jni/libenc.cc и в соответствии с этим кодом вы должны использовать y_size

person gmetax    schedule 29.03.2017

У вас проблема с входным размером кадра:

Должен быть:

int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size

Например, если у вас есть кадр размером 6x4

Размер Chanel y: 6*4 = 24

 1 2 3 4 5 6
 _ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4

Размер Chanel u: 3*2 = 6

  1   2   3 
 _ _ _ _ _ _
|   |   |   | 
|_ _|_ _|_ _| 1
|   |   |   | 
|_ _|_ _|_ _| 2

Шанель v размер: 3*2 = 6

  1   2   3 
 _ _ _ _ _ _
|   |   |   | 
|_ _|_ _|_ _| 1
|   |   |   | 
|_ _|_ _|_ _| 2

Размер массива = 6*4+3*2+3*2 = 36
Но фактический размер кадра = размер канала y = 36 * 2 / 3 = 24

person Rama    schedule 03.04.2017
comment
так что вы предлагаете ему использовать тот же размер (размер y), как мы уже предложили (я и Дейв) - person gmetax; 04.04.2017
comment
@gmetax Я счел важным отметить, что следует использовать фактический размер кадра, и предоставить метод для его вычисления на основе размера массива. Метод, который вы предлагаете для его расчета, отличается, но правильное решение зависит от потребностей ОП. Потерпи. - person Rama; 04.04.2017

gmetax почти правильный.

Вы используете размер всего массива, где вы должны использовать размер компонента Y, который равен src_width * src_height.

Ответ gmetax неверен, поскольку он поставил y_size вместо out_size при определении выходного кадра. Правильный фрагмент кода, я полагаю, будет выглядеть так:

//Get input and output length
int input_size = env->GetArrayLength(yuvByteArray_);
int y_size = src_width * src_height;
int out_size = out_height * out_width;

//Generate input frame
i420_input_frame.width = src_width;
i420_input_frame.height = src_height;
i420_input_frame.data = (uint8_t *) yuvByteArray;
i420_input_frame.y = i420_input_frame.data;
i420_input_frame.u = i420_input_frame.y + y_size;
i420_input_frame.v = i420_input_frame.u + y_size / 4;

//Generate output frame
free(i420_output_frame.data);
i420_output_frame.width = out_width;
i420_output_frame.height = out_height;
i420_output_frame.data = new unsigned char[out_size * 3 / 2];
i420_output_frame.y = i420_output_frame.data;
i420_output_frame.u = i420_output_frame.y + out_size;
i420_output_frame.v = i420_output_frame.u + out_size / 4;
person Dave    schedule 29.03.2017
comment
на самом деле вы опубликовали свой ответ через 37 минут после того, как я отредактировал часть, в которой я был неправ :) - person gmetax; 29.03.2017

Вы пытаетесь масштабировать свое изображение YUV422, как если бы оно было YUV420, неудивительно, что все цвета испорчены. Прежде всего вам нужно выяснить, какой именно формат у вашего входного буфера YUV. Из документации YUV_422_888 похоже, что он может представлять плоскость как а также форматы с чередованием (если шаг пикселя не равен 1). Судя по вашим результатам, ваш источник плоский, и обработка плоскости Y в порядке, но ваша ошибка заключается в обработке плоскостей U и V. Чтобы получить правильное масштабирование:

  • Вы должны выяснить, являются ли ваши плоскости U и V чередующимися или планарными. Скорее всего они тоже плоские.
  • Используйте ScalePlane из libyuv, чтобы отдельно масштабировать U и V. Возможно, если вы войдете в I420Scale, он вызовет ScalePlane для отдельных планов. Сделайте то же самое, но используйте правильные размеры линий для ваших плоскостей U и V (каждая в два раза больше, чем ожидает I420Scale).

Несколько советов, как определить, есть ли у вас плоские или чередующиеся U и V: попробуйте пропустить масштабирование вашего изображения и сохранить его, чтобы убедиться, что вы получите правильный результат (идентичный исходному). Затем попробуйте обнулить кадр U или кадр V и посмотреть, что получится. Если U и V являются плоскими, и вы установили memset U в ноль, вы должны увидеть изменение цвета всего изображения. Если они чередуются, вы получите изменение половины изображения, а другое останется прежним. Точно так же вы можете проверить свои предположения о размерах, линейных размерах и смещениях ваших плоскостей. После того, как вы уверены в своем формате YUV и макете, вы можете масштабировать отдельные плоскости, если ваши входные данные планарные, или если у вас есть чередующийся ввод, сначала вам нужно отменить чередование плоскостей, а затем масштабировать их.

В качестве альтернативы вы можете использовать libswscale из ffmpeg/libav и попробовать разные форматы, чтобы найти правильный, а затем использовать libyuv.

person Pavel P    schedule 03.05.2017

Зеленые изображения были вызваны тем, что одна из плоскостей была заполнена нулями. Это означает, что один из самолетов был пуст. Это было вызвано тем, что я конвертировал YUV NV21 вместо YUV I420. Изображения с камеры в андроиде идут как I420 YUV.

Нам нужно преобразовать их в YUV I420 для правильной работы с Libyuv. После этого мы можем начать использовать несколько операций, которые предлагает вам библиотека. Например, поворот, масштабирование и т. д.

Вот вырезка о том, как выглядит метод масштабирования:

JNIEXPORT jint JNICALL
Java_com_aa_project_images_yuv_myJNIcl_scaleI420(JNIEnv *env, jclass type,
                                                 jobject srcBufferY,
                                                 jobject srcBufferU,
                                                 jobject srcBufferV,
                                                 jint srcWidth, jint srcHeight,
                                                 jobject dstBufferY,
                                                 jobject dstBufferU,
                                                 jobject dstBufferV,
                                                 jint dstWidth, jint dstHeight,
                                                 jint filterMode) {

    const uint8_t *srcY = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferY));
    const uint8_t *srcU = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferU));
    const uint8_t *srcV = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferV));
    uint8_t *dstY = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferY));
    uint8_t *dstU = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferU));
    uint8_t *dstV = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferV));

    return libyuv::I420Scale(srcY, srcWidth,
                             srcU, srcWidth / 2,
                             srcV, srcWidth / 2,
                             srcWidth, srcHeight,
                             dstY, dstWidth,
                             dstU, dstWidth / 2,
                             dstV, dstWidth / 2,
                             dstWidth, dstHeight,
                             static_cast<libyuv::FilterMode>(filterMode));
}
person Francisco Durdin Garcia    schedule 15.11.2018