android - Problemas al escalar una imagen YUV usando la biblioteca libyuv
c++ (4)
Estoy desarrollando una aplicación de cámara basada en Camera API 2
y he encontrado varios problemas al usar libyuv . Quiero convertir YUV_420_888
imágenes YUV_420_888
recuperadas de un ImageReader, pero tengo algunos problemas con el escalado en una superficie reprocesable.
En esencia: las imágenes salen con tonos de verde en lugar de tener los tonos correspondientes (estoy exportando los archivos .yuv y revisándolos utilizando http://rawpixels.net/ ).
Puedes ver un ejemplo de entrada aquí:
Y lo que obtengo después de realizar escalado:
Creo que estoy haciendo algo mal con zancadas, o proporcionando un formato YUV no válido (¿tal vez tengo que transformar la imagen a otro formato?). Sin embargo, no puedo averiguar dónde está el error ya que no sé cómo correlacionar el color verde con el algoritmo de escala.
Este es el código de conversión que estoy usando, puede ignorar el retorno NULL ya que hay un procesamiento adicional que no está relacionado con el problema.
#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;
}
Estás intentando escalar tu imagen YUV422 como si fuera YUV420, no es de extrañar que todos los colores estén desordenados. En primer lugar, necesita averiguar qué formato exacto de su búfer de entrada YUV. Según la documentación de YUV_422_888 , parece que puede representar formatos planares e intercalados (si el paso de píxel no es 1). A partir de sus resultados, parece que su fuente es plana y el procesamiento del plano Y está bien, pero su error está en el manejo de los planos U y V. Para obtener la escala correcta:
- Tienes que averiguar si tus planos
U
yV
están intercalados o planares. Lo más probable es que también sean planares. - Utilice
ScalePlane
de libyuv para escalarU
yV
separado. Quizás si entras en laI420Scale
, llama aScalePlane
para planos individuales. Haga lo mismo, pero use los tamaños de línea correctos para sus planosU
yV
(cada uno es dos veces más grande de lo que espera I420Scale).
Algunos consejos sobre cómo averiguar si tiene U
y V
planas o intercaladas: intente omitir la escala de su imagen y guardarla, para asegurarse de obtener el resultado correcto (idéntico al de la fuente). Luego intente poner a cero el cuadro U
o el cuadro V
y ver qué obtiene. Si U
y V
son planas y memset U
plano a cero, debería ver la imagen completa cambiando de color. Si están intercalados, se cambiará la mitad de la imagen y la otra se mantendrá igual. De la misma manera puede verificar sus suposiciones sobre el tamaño, el tamaño de las líneas y las compensaciones de sus aviones. Una vez que esté seguro acerca de su formato y diseño YUV, puede escalar planos individuales si su entrada es plana, o si ha intercalado la entrada, primero necesita desentrelazar planos y luego escalarlos.
Alternativamente, puede usar libswscale desde ffmpeg / libav y probar diferentes formatos para encontrar el correcto y luego usar libyuv.
Puede probar el código que usa y_size
lugar del tamaño completo de su matriz.
...
//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;
...
probablemente su código se base en ese https://github.com/begeekmyfriend/yasea/blob/master/library/src/main/libenc/jni/libenc.cc y de acuerdo con ese código tiene que usar el y_size
Tienes un problema con el tamaño de entrada del marco:
Debería ser:
int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size
Por ejemplo, si tienes un Frame que es 6x4
Tamaño de canal: 6 * 4 = 24
1 2 3 4 5 6
_ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4
Chanel u tamaño: 3 * 2 = 6
1 2 3
_ _ _ _ _ _
| | | |
|_ _|_ _|_ _| 1
| | | |
|_ _|_ _|_ _| 2
Chanel v tamaño: 3 * 2 = 6
1 2 3
_ _ _ _ _ _
| | | |
|_ _|_ _|_ _| 1
| | | |
|_ _|_ _|_ _| 2
Tamaño de la matriz = 6 * 4 + 3 * 2 + 3 * 2 = 36
Pero Tamaño de cuadro real = canal y Tamaño = 36 * 2/3 = 24
gmetax es casi correcto
Está utilizando el tamaño de la matriz completa donde debería usar el tamaño del componente Y, que es src_width * src_height
.
La respuesta de gmetax es errónea, ya que ha puesto y_size
en lugar de out_size
al definir el marco de salida. El fragmento de código correcto, creo, se vería así:
//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;