【问题标题】:Problems when scaling a YUV image using libyuv library使用 libyuv 库缩放 YUV 图像时的问题
【发布时间】:2017-08-15 01:48:08
【问题描述】:

我正在开发基于Camera API 2 的相机应用程序,使用libyuv 时发现了几个问题。 我想转换从 ImageReader 检索到的 YUV_420_888 图像,但在可重新处理的表面中缩放时遇到了一些问题。

本质上:图像带有绿色调,而不是相应的色调(我正在导出 .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;
}

【问题讨论】:

  • 对于它的价值,一个完整的绿色 YUV 图像可能意味着你的 byteArray 满是 0。
  • 不是,3个输入输出bytearrays的数据都有info
  • @FranciscoDurdinGarcia 你试过我的答案了吗?
  • @gmetax 是的,我试过了,结果是一样的。与输出帧无关,我认为更多的是 byteArray 的格式与I420Scale 方法不一致
  • 您是否尝试打印 i420_input_frame.data 以检查 rawpixels.net 是否输入正确?

标签: android c++ image yuv libyuv


【解决方案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 的框架

    香奈儿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
    但实际帧大小 = 通道 y 大小 = 36 * 2 / 3 = 24

    【讨论】:

    • 所以你建议他使用与我们已经建议(我和戴夫)相同的尺寸(y 尺寸)
    • @gmetax 我发现重要的是要注意应该使用实际的帧大小,并提供一种从数组大小计算它的方法。您提出的计算方法不同,但正确的解决方案取决于 OP 的需求。请耐心等待。
    【解决方案3】:

    gmetax 几乎是正确的。

    您正在使用整个数组的大小,而您应该使用 Y 分量的大小,即 src_width * src_height

    gmetax 的回答是错误的,因为他在定义输出帧时将y_size 代替了out_size。我相信正确的代码 sn-p 应该是这样的:

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

    【讨论】:

    • 实际上你在我编辑错误的部分 37 分钟后发布了你的答案 :)
    【解决方案4】:

    您正试图像 YUV420 一样缩放您的 YUV422 图像,难怪颜色都被弄乱了。首先,您需要弄清楚 YUV 输入缓冲区的确切格式。从YUV_422_888 的文档来看,它看起来可能代表平面格式和交错格式(如果像素步幅不是 1)。从您的结果看来,您的源是平面的,并且 Y 平面的处理还可以,但您的错误在于处理 U 和 V 平面。要正确缩放:

    • 你必须弄清楚你的UV平面是交错的还是 平面的。它们很可能也是平面的。
    • 使用 libyuv 中的 ScalePlane 分别缩放 UV。可能 如果您进入I420Scale,它会为个人调用ScalePlane 飞机。做同样的事情,但为您的UV 使用正确的线条大小 飞机(每个都比 I420Scale 预期的大两倍)。

    一些提示如何确定您是否具有平面或交错UV:尝试跳过图像的缩放并保存它,以确保您获得正确的结果(与源相同)。然后尝试将U 帧或V 帧归零,看看你得到了什么。如果UV 是平面的,并且您将U 平面设置为零,您应该会看到整个图片颜色发生变化。如果它们是交错的,你会得到一半的画面改变,而另一半保持不变。同样的方式,您可以检查您对平面尺寸、线尺寸和偏移量的假设。一旦您确定了 YUV 格式和布局,如果您的输入是平面的,您就可以缩放单个平面,或者如果您首先有交错输入,您需要解交错平面然后缩放它们。

    或者,您可以使用 ffmpeg/libav 中的 libswscale 并尝试不同的格式以找到正确的格式,然后使用 libyuv。

    【讨论】:

      【解决方案5】:

      绿色图像是由其中一个平面充满 0 造成的。这意味着其中一架飞机是空的。这是因为我是从 YUV NV21 而不是 YUV I420 转换的。 android中camera框架的图像来自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));
      }
      

      【讨论】:

        猜你喜欢
        • 2019-02-03
        • 1970-01-01
        • 1970-01-01
        • 2013-09-09
        • 1970-01-01
        • 1970-01-01
        • 2017-12-28
        • 2012-05-18
        • 2011-09-22
        相关资源
        最近更新 更多