Image转YuvImage 方法及解析

Image转YuvImage 方法及解析

如何将Image图像装YUV格式。图像算法大多数是以yuv格式输入,所以如何将其转换很重要。

java代码部分
    public static YuvImage convImageToYuvImage(int imageWidth, Image image, int rotation, boolean needMirror) {
        int width = image.getWidth();

        if (imageWidth > 0) {
            width = imageWidth;
        }

        int height = image.getHeight();
        //对于常见的 YUV 420 格式来说,每个像素由一个 Y 分量和一个 U、V 分量对组成,其中每个像素的 Y 分量占据一个字节,而 U、V 分量每个像素共享一个字节,因此每个像素总共占用 1.5 个字节。
        int yuvSize = width * height * 3 / 2;
        byte[] dstNV21 = new byte[yuvSize];
        Image.Plane[] planes = image.getPlanes();
        Image.Plane yPlane = planes[0];
        Image.Plane uPlane = planes[1];
        Image.Plane vPlane = planes[2];
        YuvUtils.convertNVToNV21(yPlane.getBuffer(), yPlane.getRowStride()
                , uPlane.getBuffer(), vPlane.getBuffer(), vPlane.getRowStride()
                , dstNV21, width, image.getHeight(), rotation, needMirror);
        // it is better to close image outside.
//        image.close();
        if (rotation == 90 || rotation == 270) {
            int temp = width;
            width = height;
            height = temp;
        }
        return new YuvImage(dstNV21, ImageFormat.NV21, width, height, null);
    }
JNI代码部分
extern "C"
JNIEXPORT void JNICALL
Java_com_camera_yuvutils_YuvUtils_convertNVToNV21(JNIEnv *env, jclass clazz, jobject y,
                                                        jint row_stride_y, jobject u, jobject v,
                                                        jint row_stride_uv, jbyteArray out,
                                                        jint width, jint height, jint rotation,
                                                        jboolean mirror) {
    auto* src_y = static_cast<uint8_t *>(env->GetDirectBufferAddress(y));
    auto* src_u = static_cast<uint8_t *>(env->GetDirectBufferAddress(u));
    auto* src_v = static_cast<uint8_t *>(env->GetDirectBufferAddress(v));
    auto *dst = (uint8_t*)env->GetByteArrayElements(out, nullptr);
    auto *dst_y = dst;
    auto *dst_u = dst_y + width * height;
    auto *dst_v = dst_u + (width * height >> 2);
    RotationMode rotationMode = getRotationMode(rotation);
    rotation = rotation % 360;
    int outW = width;
    int outH = height;
    if (rotation % 180 != 0) {
        outW = height;
        outH = width;
    }
    int outW_half = outW >> 1;
    int outW_quarter = outW_half >> 1;
    if (src_u > src_v) {
        NV12ToI420Rotate(src_y, row_stride_y,
                         src_v, row_stride_uv,
                         dst_y, outW,
                         dst_u, outW_half,
                         dst_v, outW_half,
                         width, height, rotationMode);
    } else {
        NV12ToI420Rotate(src_y, row_stride_y,
                         src_u, row_stride_uv,
                         dst_y, outW,
                         dst_v, outW_half,
                         dst_u, outW_half,
                         width, height, rotationMode);
    }

    uint8_t* temp = static_cast<uint8_t *>(malloc(outW * outH * 3 / 2));
    auto* tmp_y = temp;
    auto* tmp_u = tmp_y + width * height;
    auto* tmp_v = tmp_u + (width * height >> 2);
    if (mirror) {
        I420Mirror(dst_y, outW, dst_u, outW_half, dst_v, outW_half,
                   tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
                   outW, outH);
    } else {
        I420Copy(dst_y, outW, dst_u, outW_half, dst_v, outW_half,
                 tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
                 outW, outH);
    }
    libyuv::I420ToNV21(tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
                       dst_y, outW,dst_y + width * height, outW, outW, outH);
    free(temp);
    env->ReleaseByteArrayElements(out, reinterpret_cast<jbyte *>(dst), 0);
}

auto src_y = static_cast<uint8_t >(env->GetDirectBufferAddress(y));

  • env 是 JNI 提供的环境变量,用于在 Java 代码和 Native 代码之间进行通信。
  • GetDirectBufferAddress 是 JNI 环境变量 env 的一个方法,用于获取 DirectByteBuffer 对象的数据地址。
  • yuv 是分别表示 Y、U、V 三个分量的 DirectByteBuffer 对象,它们可能包含了图像的 YUV 数据。
  • static_cast<uint8_t *> 是 C++ 中的静态类型转换,用于将返回的 void * 类型的地址转换为 uint8_t * 类型的指针,即无符号 8 位整数类型的指针。

作用是将 Java 中的 DirectByteBuffer 对象转换为 C/C++ 中的指针,以便在后续的代码中直接操作 YUV 数据。

auto *dst = (uint8_t*)env->GetByteArrayElements(out, nullptr);
auto *dst_y = dst;
auto *dst_u = dst_y + width * height;
auto *dst_v = dst_u + (width * height >> 2);

env->GetByteArrayElements(out, nullptr):这行代码通过 JNI 获取 Java 中的 byte[] 数组对象 out 的元素,并返回对应的 C/C++ 中的指针。这个指针 dst 指向了数组中第一个元素的地址。

auto *dst_y = dst;:这行代码将 dst 指针赋值给 dst_y,表示 dst_y 指向了 Y 分量的起始位置。

auto *dst_u = dst_y + width * height;:这行代码将 dst_y 指针加上 width * height,得到了 U 分量的起始位置,即 dst_u 指向了 U 分量的起始位置。这里的 width * height 表示 Y 分量的像素数量,因为 U 分量和 V 分量的像素数量通常是 Y 分量的四分之一。

auto *dst_v = dst_u + (width * height >> 2);:这行代码将 dst_u 指针加上 (width * height >> 2),得到了 V 分量的起始位置,即 dst_v 指向了 V 分量的起始位置。这里的 (width * height >> 2) 表示将 Y 分量的像素数量除以 4,得到了 V 分量的像素数量。

int outW = width;
int outH = height;
if (rotation % 180 != 0) {
    outW = height;
    outH = width;
}

在这里插入图片描述

Android Camera旋转角度_android 手机camera orientation-CSDN博客

所以在当为90度或270度的时候需要将宽高交换

int outW_half = outW >> 1;
int outW_quarter = outW_half >> 1;
if (src_u > src_v) {
    NV12ToI420Rotate(src_y, row_stride_y,
                     src_v, row_stride_uv,
                     dst_y, outW,
                     dst_u, outW_half,
                     dst_v, outW_half,
                     width, height, rotationMode);
} else {
    NV12ToI420Rotate(src_y, row_stride_y,
                     src_u, row_stride_uv,
                     dst_y, outW,
                     dst_v, outW_half,
                     dst_u, outW_half,
                     width, height, rotationMode);
}
int outW_half = outW >> 1;

原因如下

在 YUV 420 格式中,每个像素对应一个 Y 值,但是对应的 U 和 V 值是共享的。具体地说,U 和 V 值的采样率是 Y 值的四分之一,也就是说,每 4 个像素共享一个 U 值,每 4 个像素共享一个 V 值。

因此,如果 YUV 图像的总像素数量为 width * height,那么其中包含的 Y 像素数量就是 width * height,而 U 和 V 像素的数量分别是 width * height / 4。

至于宽度的计算,因为 U 和 V 分量的采样率通常是 Y 分量的一半,所以 U 和 V 分量的宽度通常是 Y 分量宽度的一半。这就是为什么在这段代码中,U 和 V 分量的宽度被设置为 outW_half(即 outW 的一半)的原因。
在这里插入图片描述
在这里插入图片描述

if (rotation == 90 || rotation == 270) {
    int temp = width;
    width = height;
    height = temp;
}
return new YuvImage(dstNV21, ImageFormat.NV21, width, height, null);

前面通过地址传入YUV库中,处理后获得yuvByte数组。通过new YuvImage获取到yuvImage对象。自从完成了Image到YUVIamge转换。

项目需要的库文件及配置

GitHub - lemenkov/libyuv: Unofficial libyuv mirror. Please submit any issues or PRs upstream.

在这里插入图片描述

库及头文件下载。

CMakeList.txt 中使用

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("yuvutils")

# jniLibs
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
include_directories(${PROJECT_SOURCE_DIR}/libyuv/include)

#add_subdirectory(${PROJECT_SOURCE_DIR}/libyuv)

add_library( # Sets the name of the library.
        yuvutils

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        yuvutils.cpp)


add_library(yuv STATIC IMPORTED)
set_target_properties(
        yuv
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_CURRENT_SOURCE_DIR}/libyuv/jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libyuv.a)


# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        yuvutils

        # Links the target library to the log library
        # included in the NDK.
        yuv)

build.gradle中配置

externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
    }
}
 library.
        yuvutils

        # Links the target library to the log library
        # included in the NDK.
        yuv)

build.gradle中配置

externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
    }
}
  • 45
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值