使用 libyuv 库缩放 YUV 图像时出现的问题
Problems when scaling a YUV image using libyuv library
我正在开发一个基于 Camera API 2
的相机应用程序,我在使用 libyuv 时发现了几个问题。
我想转换从 ImageReader 检索到的 YUV_420_888
图像,但在可重新处理的表面上缩放时遇到了一些问题。
本质上:图像带有绿色色调而不是相应的色调(我正在导出 .yuv 文件并使用 http://rawpixels.net/ 检查它们)。
您可以在此处查看输入示例:
执行缩放后得到的结果:
我想我的步幅有问题,或者提供了无效的 YUV 格式(也许我必须将图像转换为另一种格式?)。但是,我不知道错误在哪里,因为我不知道如何将绿色与缩放算法相关联。
这是我正在使用的转换代码,您可以忽略return 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
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;
您对框架的输入大小有疑问:
应该是:
int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size
例如,如果您有一个 6x4 的框架
香奈儿y尺寸:6*4=24
1 2 3 4 5 6
_ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4
香奈儿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
但是实际的Frame Size = channel y Size = 36 * 2 / 3 = 24
您正在尝试将 YUV422 图像缩放为 YUV420,难怪颜色都乱七八糟。首先,您需要弄清楚 YUV 输入缓冲区的确切格式。从 YUV_422_888 的文档看来,它可能代表平面格式和交错格式(如果像素跨度不是 1)。从您的结果来看,您的源似乎是平面的并且 Y 平面的处理没问题,但您的错误在于处理 U 和 V 平面。要正确缩放:
- 你必须弄清楚你的
U
和 V
平面是交错的还是
平面的。它们很可能也是平面的。
- 使用 libyuv 中的
ScalePlane
分别缩放 U
和 V
。也许
如果您进入 I420Scale
,它会为个人调用 ScalePlane
飞机。做同样的事情,但为您的 U
和 V
使用正确的线宽
平面(每个平面都比 I420Scale 预期的大两倍)。
一些提示如何确定您是否有平面或交错 U
和 V
:尝试跳过缩放图像并保存它,以确保您获得正确的结果(与来源)。然后尝试将 U
帧或 V
帧归零,看看你得到了什么。如果 U
和 V
是平面的,并且您将 U
平面设置为零,您应该会看到整个图片都在改变颜色。如果它们是交错的,你会得到一半的图片改变而另一张保持不变。您可以用同样的方式检查您对平面的尺寸、线条尺寸和偏移量的假设。一旦您确定了您的 YUV 格式和布局,如果您的输入是平面的,您可以缩放单个平面,或者如果您首先有交错输入,您需要解交错平面然后缩放它们。
或者,您可以使用 ffmpeg/libav 中的 libswscale 并尝试不同的格式以找到正确的格式,然后使用 libyuv。
绿色图像是由于其中一个平面全是 0 造成的。这意味着其中一架飞机是空的。这是因为我是从 YUV NV21 而不是 YUV I420 转换而来的。 android 中相机框架的图像来自 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));
}
我正在开发一个基于 Camera API 2
的相机应用程序,我在使用 libyuv 时发现了几个问题。
我想转换从 ImageReader 检索到的 YUV_420_888
图像,但在可重新处理的表面上缩放时遇到了一些问题。
本质上:图像带有绿色色调而不是相应的色调(我正在导出 .yuv 文件并使用 http://rawpixels.net/ 检查它们)。
您可以在此处查看输入示例:
执行缩放后得到的结果:
我想我的步幅有问题,或者提供了无效的 YUV 格式(也许我必须将图像转换为另一种格式?)。但是,我不知道错误在哪里,因为我不知道如何将绿色与缩放算法相关联。
这是我正在使用的转换代码,您可以忽略return 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
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;
您对框架的输入大小有疑问:
应该是:
int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size
例如,如果您有一个 6x4 的框架
香奈儿y尺寸:6*4=24
1 2 3 4 5 6
_ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4
香奈儿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
但是实际的Frame Size = channel y Size = 36 * 2 / 3 = 24
您正在尝试将 YUV422 图像缩放为 YUV420,难怪颜色都乱七八糟。首先,您需要弄清楚 YUV 输入缓冲区的确切格式。从 YUV_422_888 的文档看来,它可能代表平面格式和交错格式(如果像素跨度不是 1)。从您的结果来看,您的源似乎是平面的并且 Y 平面的处理没问题,但您的错误在于处理 U 和 V 平面。要正确缩放:
- 你必须弄清楚你的
U
和V
平面是交错的还是 平面的。它们很可能也是平面的。 - 使用 libyuv 中的
ScalePlane
分别缩放U
和V
。也许 如果您进入I420Scale
,它会为个人调用ScalePlane
飞机。做同样的事情,但为您的U
和V
使用正确的线宽 平面(每个平面都比 I420Scale 预期的大两倍)。
一些提示如何确定您是否有平面或交错 U
和 V
:尝试跳过缩放图像并保存它,以确保您获得正确的结果(与来源)。然后尝试将 U
帧或 V
帧归零,看看你得到了什么。如果 U
和 V
是平面的,并且您将 U
平面设置为零,您应该会看到整个图片都在改变颜色。如果它们是交错的,你会得到一半的图片改变而另一张保持不变。您可以用同样的方式检查您对平面的尺寸、线条尺寸和偏移量的假设。一旦您确定了您的 YUV 格式和布局,如果您的输入是平面的,您可以缩放单个平面,或者如果您首先有交错输入,您需要解交错平面然后缩放它们。
或者,您可以使用 ffmpeg/libav 中的 libswscale 并尝试不同的格式以找到正确的格式,然后使用 libyuv。
绿色图像是由于其中一个平面全是 0 造成的。这意味着其中一架飞机是空的。这是因为我是从 YUV NV21 而不是 YUV I420 转换而来的。 android 中相机框架的图像来自 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));
}