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

28

Решение

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

1

Другие решения

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

Так должно быть:

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

Например, если у вас есть кадр 6×4

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

1

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

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

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

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

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

0

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

Нам нужно конвертировать их в 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));
}
0
По вопросам рекламы [email protected]