Подтвердить что ты не робот

Проблемы при масштабировании изображения 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;
}
4b9b3361

Ответ 1

Вы можете попробовать этот код, который использует 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

Ответ 2

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

Это должно быть:

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

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

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

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

Ответ 3

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;

Ответ 4

Вы пытаетесь масштабировать изображение 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.