【问题标题】:Scaling YUV420 Image using libyuv produces weird output使用 libyuv 缩放 YUV420 图像会产生奇怪的输出
【发布时间】:2019-02-03 22:52:42
【问题描述】:

This 问题可能重复,主要部分来自here。我尝试了那里提供的任何解决方案,但它们对我不起作用。

背景

我正在捕捉从 ARCore 的 frame.acquireCameraImage() 方法返回的 YUV_420_888 图像格式的图像。由于我已将相机配置设置为 1920*1080 分辨率,因此我需要将其缩小到 224*224 以将其传递给我的 tensorflow-lite 实现。我通过 Android NDK 使用 LibYuv 库来做到这一点。

实施

准备图像帧

    //Figure out the source image dimensions
    int y_size = srcWidth * srcHeight;

    //Get dimensions of the desired output image
    int out_size = destWidth * destHeight;

    //Generate input frame
    i420_input_frame.width = srcWidth;
    i420_input_frame.height = srcHeight;
    i420_input_frame.data = (uint8_t*) yuvArray;
    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 = destWidth;
    i420_output_frame.height = destHeight;
    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 的 I420Scale 方法缩放图像

libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBox;
jint result = libyuv::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);

返回给java

    //Create a new byte array to return to the caller in Java
    jbyteArray outputArray = env -> NewByteArray(out_size * 3 / 2);
    env -> SetByteArrayRegion(outputArray, 0, out_size, (jbyte*) i420_output_frame.y);
    env -> SetByteArrayRegion(outputArray, out_size, out_size / 4, (jbyte*) i420_output_frame.u);
    env -> SetByteArrayRegion(outputArray, out_size + (out_size / 4), out_size / 4, (jbyte*) i420_output_frame.v);

实际图片:

缩放后的样子:

如果我从 i420_input_frame 创建一个不缩放的图像会是什么样子:

由于缩放会严重破坏颜色,因此 tensorflow 无法正确识别对象。 (它在他们的示例应用程序中正确识别)我做错了什么来搞乱颜色?

【问题讨论】:

    标签: android tensorflow android-ndk tensorflow-lite libyuv


    【解决方案1】:

    颜色问题是由于您使用不同的 YUV 格式而引起的。相机框架使用的 YUV 格式是 YUV NV21。 此格式 (NV21) 是 Android 相机预览中的标准图片格式。 YUV 4:2:0 平面图像,具有 8 位 Y 样本,然后是具有 8 位 2x2 子采样色度样本的交错 V/U 平面。

    如果你的颜色是相反的,这意味着:

    • 您正在使用 YUV NV12(平面 U 是 V,V 是 U)。
    • 您的一个颜色平面正在做一些奇怪的事情。

    为了与libyuv 正常工作,我建议您使用transformI420 方法将相机输出转换为YUV I420,并通过参数发送格式:

    return libyuv::ConvertToI420(src, srcSize, //src data
                                 dstY, dstWidth, //dst planes
                                 dstU, dstWidth / 2,
                                 dstV, dstWidth / 2,
                                 cropLeft, cropTop, //crop start
                                 srcWidth, srcHeight, //src dimensions
                                 cropRight - cropLeft, cropBottom - cropTop, //dst dimensions
                                 rotationMode,
                                 libyuv::FOURCC_NV21); //libyuv::FOURCC_NV12
    

    完成此转换后,您将能够使用所有 I420scaleI420rotate... 等正确使用 libyuv。您的 scale 方法应如下所示:

    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));
    }
    

    如果您想在完成所有过程后将此图像转换为 JPEG。您可以使用I420toNV21 方法,然后使用从 YUV 到 JPEG 的 android 本机转换。您也可以使用libJpegTurbo,这是针对这种情况的补充库。

    【讨论】:

      【解决方案2】:

      要么我做错了(我无法修复),要么 LibYuv 在处理来自 Android 的 YUV 图像时无法正确处理颜色。

      参考Libyuv库上发布的官方错误:https://bugs.chromium.org/p/libyuv/issues/detail?id=815&can=1&q=&sort=-id

      他们建议我先使用Android420ToI420() 方法,然后再应用我需要的任何转换。我最终首先使用Android420ToI420(),然后是缩放,然后是转换为 RGB。最后,输出比上面发布的杯子图像略好,但失真的颜色仍然存在。我最终使用 OpenCV 来缩小图像并将其转换为 RGBA 或 RGB 格式。

      // The camera image received is in YUV YCbCr Format at preview dimensions
      // so we will scale it down to 224x224 size using OpenCV
      // Y plane (0) non-interleaved => stride == 1; U/V plane interleaved => stride == 2
      // Refer : https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
      val cameraPlaneY = cameraImage.planes[0].buffer
      val cameraPlaneUV = cameraImage.planes[1].buffer
      
      // Create a new Mat with OpenCV. One for each plane - Y and UV
      val y_mat = Mat(cameraImage.height, cameraImage.width, CvType.CV_8UC1, cameraPlaneY)
      val uv_mat = Mat(cameraImage.height / 2, cameraImage.width / 2, CvType.CV_8UC2, cameraPlaneUV)
      var mat224 = Mat()
      var cvFrameRGBA = Mat()
      
      // Retrieve an RGBA frame from the produced YUV
      Imgproc.cvtColorTwoPlane(y_mat, uv_mat, cvFrameRGBA, Imgproc.COLOR_YUV2BGRA_NV21)
      
      
      //Then use this frame to retrieve all RGB channel data
      //Iterate over all pixels and retrieve information of RGB channels
        for(rows in 1 until cvFrameRGBA.rows())
            for(cols in 1 until cvFrameRGBA.cols()) {
                val imageData = cvFrameRGBA.get(rows, cols)
                // Type of Mat is 24
                // Channels is 4
                // Depth is 0
                rgbBytes.put(imageData[0].toByte())
                rgbBytes.put(imageData[1].toByte())
                rgbBytes.put(imageData[2].toByte())
            }
      

      【讨论】:

        猜你喜欢
        • 2019-04-13
        • 1970-01-01
        • 2017-08-15
        • 2015-08-18
        • 2021-07-26
        • 1970-01-01
        • 2015-06-04
        • 2018-02-09
        • 1970-01-01
        相关资源
        最近更新 更多