Я разрабатываю приложение для камеры на основе 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;
}
Вы можете попробовать этот код, который использует 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
У вас есть проблема с размером ввода кадра:
Так должно быть:
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
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;
Вы пытаетесь масштабировать свое изображение YUV422, как если бы оно было YUV420, неудивительно, что все цвета перепутаны. Прежде всего вам необходимо выяснить, в каком именно формате находится ваш входной буфер YUV. Из документации YUV_422_888 похоже, что он может представлять как плоский, так и чередующийся формат (если шаг пикселя не равен 1). Из ваших результатов видно, что ваш источник плоский, и обработка плоскости Y в порядке, но ваша ошибка в обработке плоскостей U и V. Чтобы правильно масштабировать:
U
а также V
плоскости чередуются илиScalePlane
от либюв к масштабу U
а также V
по отдельности. возможноI420Scale
это вызывает ScalePlane
для индивидуальногоU
а также V
Несколько советов, как выяснить, есть ли у вас плоскостный или чередующийся U
а также V
: попытайтесь пропустить масштабирование вашего изображения и сохранить его, чтобы гарантировать, что вы получите правильный результат (идентичный источнику). Затем попробуйте обнулить U
рамка или V
кадр и посмотрим, что вы получите. Если U
а также V
плоские и вы memset U
от плоскости до нуля вы должны увидеть всю картину, меняющую цвет. Если они чередуются, вы получите половину смены изображения, а другая останется прежней. Таким же образом вы можете проверить свои предположения о размерах, размерах линий и смещениях ваших самолетов. Если вы уверены в своем формате и формате YUV, вы можете масштабировать отдельные плоскости, если ваш ввод плоский, или если у вас есть чередующийся ввод, сначала вам нужно деинтерлейсировать плоскости, а затем масштабировать их.
Кроме того, вы можете использовать libswscale из ffmpeg / libav и попробовать разные форматы, чтобы найти правильный, а затем использовать libyuv.
Зеленые изображения были вызваны тем, что один из самолетов был заполнен нулями. Это означает, что один из самолетов был пуст. Это было вызвано тем, что я конвертировал из 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));
}