Android JNI和原生交互,常见的图像格式转换 : NV21、RGBA、Bitmap等

这篇具有很好参考价值的文章主要介绍了Android JNI和原生交互,常见的图像格式转换 : NV21、RGBA、Bitmap等。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言

最近在使用OpenCV处理图片的时候,经常会遇到需要转换图像的情况,网上相关资料比较少,也不全,有时候得费劲老半天才能搞定。
自己踩了坑后,在这里记录下,都是我在项目中遇到的图像转化操作,是一些常用的图像格式转换操作。
具体包括:

  • nv21、rgba、rgb转换
  • OpenCVMat转为Bitmap
  • Bitmap转成RGB888
  • NV21转成Bitmap
  • Camera2 中的 android.media.Image 转为 NV21
  • Android传递BitmapJNI,并转为rgbaMat
  • JPEGNV21

本文的操作都是基于Activity横屏的情况下进行的

Android JNI和原生交互,常见的图像格式转换 : NV21、RGBA、Bitmap等,音视频开发,android,JNI,NDK,Bitmap,转换,OpenCV,NV21

2. nv21、rgba、rgb转换

nv21YUV420格式中的一种,在Android中,Camera1获取的摄像头数据,就是NV21格式的。
rgba、rgb格式,是不同于YUV的另一种色彩表示方式,通常我们需要转为RGB格式,再去做图像检测和处理。
所以在Android中,nv21rgb的转换,是比较常用、比较普遍的。

2.1 nv21转为rgba格式的Mat

这里传入的jbyteArray data_nv21格式,首先转成nv21Mat,然后在通过cv::cvtColor方法,通过cv::COLOR_YUV2RGBA_NV21这个参数值,转为rgba格式的Mat

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_heiko_myncnnlib_NcnnNativeLib_nv21toARGB(JNIEnv *env, jobject thiz, jbyteArray data_,
                                                  jint h, jint w) {
    jbyte *data = env->GetByteArrayElements(data_, NULL);

    cv::Mat nv21(h + h / 2, w, CV_8UC1, data);
    cv::Mat rgba(h, w, CV_8UC4);
    //nv21转为rgba格式
    cv::cvtColor(nv21, rgba, cv::COLOR_YUV2RGBA_NV21);

	//省略了后续无关代码....

	//释放资源
	env->ReleaseByteArrayElements(data_, data, 0);
}

2.2 nv21转为rgb的Mat

nv21转成rgb格式的Mat,这里的COLOR_YUV420sp2RGBCOLOR_YUV2RGB_NV21是一样的。

cv::Mat rgba(h, w, CV_8UC3);
//将nv21的数据转为RGB
cv::cvtColor(nv21, rgb, cv::COLOR_YUV420sp2RGB); //也可以传COLOR_YUV2RGB_NV21

2.3 rgba转为rgb的Mat

cv::Mat rgb(rows, cols, CV_8UC3);
//将rgba转为rgb
cv::cvtColor(rgba, rgb, CV_RGBA2RGB);

3. OpenCV的Mat转为Bitmap

JNI中,用OpenCV处理好图像后,得到的结果是Mat,那么需要将其转为byteArray,然后传递到Android层,再转为Bitmap,显示到ImageView上。

3.1 RGBA转成Bitmap

转成RGBA相对比较简单,只要将rgbaMat,转为jbyteArray,传递到Android层就好。

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_heiko_myncnnlib_NcnnNativeLib_nv21toARGB(JNIEnv *env, jobject thiz, jbyteArray data_,
                                                  jint h, jint w) {
    jbyte *data = env->GetByteArrayElements(data_, NULL);

    cv::Mat nv21(h + h / 2, w, CV_8UC1, data);
    cv::Mat rgba(h, w, CV_8UC4);
    cv::cvtColor(nv21, rgba, cv::COLOR_YUV2RGBA_NV21);

    int rows = h;
    int cols = w;
    jbyteArray byteArray = env->NewByteArray(rows * cols * 4);

    env->SetByteArrayRegion(byteArray, 0, rows * cols * 4, reinterpret_cast<jbyte*>(rgba.data));
    env->ReleaseByteArrayElements(data_, data, 0);

    return byteArray;
}

Android层进行调用,这里创建Bitmap的时候,使用的是Bitmap.Config.ARGB_8888

//由于前摄像头放置位置是90度方向的,所以这里height和width对调 (实际上应该是在JNI里进行旋转操作,这里是怎么方便怎么来)
 var result = nativeLib.nv21toARGB(data,height,width)
 //var result = nativeLib.nv21toARGB(data,width,height)
 //byte数组转为ARGB8888的Bitmap
 val bitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
 var buffer = ByteBuffer.wrap(result)
 bitmap.copyPixelsFromBuffer(buffer)
runOnUiThread {
	//显示到Bitmap上
    binding.img1.setImageBitmap(bitmap)
}

3.2 RGB888转RGB565后,再转成Bitmap

3.2.1 方式一

先来看一下RGB888RGB565的方法

uint16_t *rgb888toRgb565(cv::Mat &rgb, int rows, int cols) {
    cv::Vec3b *data = rgb.ptr<cv::Vec3b>(0);

    uint16_t *rgb565 = new uint16_t[rows * cols];
    for (int i = 0; i < rows * cols; i++) {
        int r = data[i][0];
        int g = data[i][1];
        int b = data[i][2];

        rgb565[i] = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
    }
    return rgb565;
}

实现JNI方法,这里传入的data_rgb888格式,然后转成Mat,再调用rgb888toRgb565转成rgb565,最后在转成jbyteArray返回给Android层。

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_heiko_MyTest_rgb888ToRgb565(JNIEnv *env, jobject thiz, jbyteArray data_,jint w, jint h) {
    jbyte *data = env->GetByteArrayElements(data_, NULL);
    unsigned char *rgb_data = reinterpret_cast<unsigned char *>(data);
    cv::Mat rgb(h, w, CV_8UC3, rgb_data);

    int rows = h;
    int cols = w;
    jbyteArray byteArray = env->NewByteArray(rows * cols * 2);
    uint16_t *rgb565 = rgb888toRgb565(rgb, rows, cols);

    env->SetByteArrayRegion(byteArray, 0, rows * cols * 2, reinterpret_cast<jbyte *>(rgb565));
    env->ReleaseByteArrayElements(data_, data, 0);
    return byteArray;
}

Android层进行调用,这里创建Bitmap的时候,使用的是Bitmap.Config.RGB_565

//这里的data是RGB888格式,具体看4.x小节
val result : ByteArray = nativeLib.rgb888ToRgb565(data, imageWidth, imageHeight)
//byte数组转为Bitmap
val bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.RGB_565)
var buffer = ByteBuffer.wrap(detectResult)
bitmap.copyPixelsFromBuffer(buffer)

runOnUiThread { 
	//显示到ImageView上
    binding.img1.setImageBitmap(bitmap)
}
3.2.2 发现一种更简单的方式

可以直接用OpenCV的API来转成BGR565

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_heiko_MyTest_test(JNIEnv *env, jobject thiz, jobject data1) {
	//省略了代码...
	
	cv::Mat mask2;
	cv::cvtColor(mask, mask2, CV_RGB2BGR565);
	
	int size = rows * cols * 2;
	jbyteArray byteArray = env->NewByteArray(size);
	env->SetByteArrayRegion(byteArray, 0, size, reinterpret_cast<jbyte *>(mask2.data));
	
	return byteArray;
}

3.3 RGBA转RGB565

rgba也可以先转成rgb565后,再传递给Android层,代码如下

uint16_t *rgbaToRgb565(cv::Mat &rgb, int rows, int cols) {
    cv::Vec4b *data = rgb.ptr<cv::Vec4b>(0);

    uint16_t *rgb565 = new uint16_t[rows * cols];
    for (int i = 0; i < rows * cols; i++) {
        int r = data[i][0];
        int g = data[i][1];
        int b = data[i][2];

        rgb565[i] = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
    }
    return rgb565;
}

4. Bitmap转RGB888

Android中的BitmapARGB格式进行存储的,所以我们先取到Bitmap的像素数组,然后对其进行遍历,分别取到每个像素点的RGB数据,赋值到新的ByteArray里,就得到RGB888格式的图像数据了。

//解析bytes为bitmap,bytes是jpeg格式的图片流
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val width: Int = bitmap.width
val height: Int = bitmap.height
val pixels = IntArray(width * height)
//获取像素赋值给 pixels
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)

val rgb888 = ByteArray(width * height * 3)
for (i in 0 until width * height) {
	 // 注意:Android的Bitmap是ARGB格式,而不是RGBA
	 rgb888[i * 3] = Color.red(pixels[i]).toByte()
	 rgb888[i * 3 + 1] = Color.green(pixels[i]).toByte()
	 rgb888[i * 3 + 2] = Color.blue(pixels[i]).toByte()
}

5. YUV420转Bitmap

这里的yuv420的具体格式是NV21,也就是将NV21格式转为Bitamp
具体操作为先将nv21ByteArray转化为YuvImage对象,然后压缩为JPEG格式的ByteArray,最后通过BitmapFactory.decodeByteArray()来得到Bitmap

fun convertYUV420ToBitmap(
        yuv420Data: ByteArray?,
        width: Int,
        height: Int
    ): Bitmap {
        // 创建YuvImage对象
        val yuvImage = YuvImage(yuv420Data, ImageFormat.NV21, width, height, null)

        // 创建ByteArrayOutputStream对象
        val outputStream = ByteArrayOutputStream()

        // 将YuvImage对象压缩为JPEG格式的数据
        yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputStream)

        // 将JPEG数据解码为Bitmap对象
        val jpegData = outputStream.toByteArray()
        return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size)
}

6. android.media.Image 转为 NV21

Android Camera2相机中取到的一帧数据是android.media.Image,我们设置android.graphics.ImageFormatImageFormat.YUV_420_888,这个格式是YCbCr的泛化格式,不会具体指明是YU12,YV12,NV12,或是是NV21。它能够表示任何4:2:0的平面和半平面格式,每个分量用8 bits表示。
这里,我们来将Image转为NV21格式。

fun imageToNV21(image: Image): ByteArray {
    val planes: Array<Image.Plane> = image.planes
    val yBuffer = planes[0].buffer
    val uBuffer = planes[1].buffer
    val vBuffer = planes[2].buffer
    val ySize = yBuffer.remaining()
    val uSize = uBuffer.remaining()
    val vSize = vBuffer.remaining()
    val yuvData = ByteArray(ySize + uSize + vSize)

    yBuffer[yuvData, 0, ySize]
    vBuffer[yuvData, ySize, vSize]
    uBuffer[yuvData, ySize + vSize, uSize]
    return yuvData
}

7. Android传递Bitmap给JNI,并转为rgba的Mat

Android中,也可以直接向JNI传递Bitmap对象,然后在JNI中,再去对Bitmap进行操作。

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_zeekr_ncnnlib_NcnnNativeLib_humanDetectBitmap(JNIEnv *env, jobject thiz, jobject bitmap) {
    AndroidBitmapInfo bitmapInfo;
    //获取Bitmap的信息
    AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
    int rows = bitmapInfo.height;
    int cols = bitmapInfo.width;

    void *bitmapPixels;
    //获取Bitmap的像素
    AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels);

	//转成rgba的Mat
    cv::Mat rgba(rows, cols, CV_8UC4, bitmapPixels);

    AndroidBitmap_unlockPixels(env, bitmap);

	//省略了后续无关代码
}

关于在JNI中创建Bitmap,并传递到Android层,具体可以看我的这篇文章 : Android JNI/NDK 入门从一到二_氦客的博客-CSDN博客

8. JPEG转NV21

传入jpeg格式的ByteArray,返回NV21格式的ByteArray

fun jpegToNV21(jpegData: ByteArray, width: Int, height: Int): ByteArray {
   val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size)
   val argb = IntArray(width * height)
   bitmap.getPixels(argb, 0, width, 0, 0, width, height)
   val yuv = ByteArray(width * height * 3 / 2)
   encodeYUV420SP(yuv, argb, width, height)
   bitmap.recycle()
   return yuv
}

private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) {
   val frameSize = width * height
   var yIndex = 0
   var uvIndex = frameSize
   //var a: Int
   var R: Int
   var G: Int
   var B: Int
   var Y: Int
   var U: Int
   var V: Int
   for (j in 0 until height) {
       for (i in 0 until width) {
           //a = argb[j * width + i] and -0x1000000 shr 24
           R = argb[j * width + i] and 0xff0000 shr 16
           G = argb[j * width + i] and 0xff00 shr 8
           B = argb[j * width + i] and 0xff shr 0
           Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16
           U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128
           V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128
           yuv420sp[yIndex++] = (if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte()
           if (j % 2 == 0 && i % 2 == 0) {
               yuv420sp[uvIndex++] = (if (V < 0) 0 else if (V > 255) 255 else V).toByte()
               yuv420sp[uvIndex++] = (if (U < 0) 0 else if (U > 255) 255 else U).toByte()
           }
       }
   }
}

本文为氦客在CSDN上独家发布 : https://blog.csdn.net/EthanCo文章来源地址https://www.toymoban.com/news/detail-735826.html

到了这里,关于Android JNI和原生交互,常见的图像格式转换 : NV21、RGBA、Bitmap等的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Java代码与JNI交互基础数据类型转换(三)

    🔥 Android Studio 版本 🔥  🔥 基础类型数据的转换 🔥 定义传递基础数据类型到Native的JNI接口函数 Native层创建callNativeInt 提供给Java调用 CMakeLists.txt 文件下配置动态库名称  添加 target_link_libraries 配置 Java调用Native层函数   运行打印日志 Java调用Native分别打印基础类型数据  下

    2024年02月13日
    浏览(43)
  • Android MediaCodec将h264实时视频流数据解码为yuv,并转换yuv的颜色格式为nv21

    初始化mediacodec 处理数据,解码h264数据为yuv格式 这里传入的是h264格式的实时视频流数据。 处理获取到的nv21颜色格式的yuv数据  yuv视频数据颜色格式转换 h264实时视频流的数据来源 写入h264视频流到sdcard中 rtsp获取h264实时视频流数据  编写C代码加载ffmpeg库 源码地址 https://gi

    2024年01月17日
    浏览(62)
  • “Python OpenCV 图像格式转换:RGB与BGR互转“——使用OpenCV库进行图像处理的过程中,经常需要进行不同格式之间的转换。其中最为常见的就是R...

    “Python OpenCV 图像格式转换:RGB与BGR互转”——使用OpenCV库进行图像处理的过程中,经常需要进行不同格式之间的转换。其中最为常见的就是RGB和BGR格式之间的转换。本文将详细介绍如何使用opencv-python库将图像从RGB格式转换为BGR格式以及从BGR格式转换为RGB格式。 要实现图像格

    2024年02月12日
    浏览(82)
  • java jni nv21和nv12互转

    目录 nv12和nv21效果比较, libyuv性能比较 NV12 NV21 YUV420格式介绍 jni YUV420toYUV420SemiPlanar java YUV420toYUV420SemiPlanar

    2024年02月11日
    浏览(30)
  • Android Java代码与JNI交互 JNI方法Java类字段 (六)

    🔥 Android Studio 版本 🔥    🔥 Java 基础类型数据对应 Native层的字母 🔥  通过 jni 查找java某个类中相应字段对应的数据类型 , 需要使用到 jni 中的 GetFieldID() 函数 🔥 Java 引用类型数据对应 Native层字符串 🔥   🔥 创建 JNIAccessField 文件 🔥 🔥 配置动态库名称 🔥  🔥 生成

    2024年02月16日
    浏览(43)
  • Android Java代码与JNI交互 JNI访问Java类方法 (七)

    🔥 Android Studio 版本 🔥    🔥 创建包含JNI的类 JNIAccessMethod.java 🔥  🔥 Java方法对应Native层方法名称 🔥 🔥 配置动态库名称 🔥  🔥 生成可关联的库链接 🔥  为了让Java能够调用 access-method-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联 

    2024年02月16日
    浏览(40)
  • Android Java代码与JNI交互 JNI访问Java构造方法(九)

    🔥 Android Studio 版本 🔥    🔥 创建包含JNI相关函数类 JNIConstructorClass.java 🔥  🔥 配置动态库名称 🔥   🔥 生成可关联的库链接 🔥  为了让Java能够调用 constructor-class-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联constructor-class-lib 库  🔥

    2024年02月15日
    浏览(40)
  • Android Java代码与JNI交互 JNI子线程访问Java方法 (八)

    🔥 Android Studio 版本 🔥   🔥 创建包含JNI相关函数类 JNIInvokeMethod.java 🔥  🔥 配置动态库名称 🔥  🔥 生成可关联的库链接 🔥  为了让Java能够调用 invoke-method-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联 invoke-method-lib 库  🔥 提供给N

    2024年02月16日
    浏览(48)
  • 【环境配置】Android-Studio-OpenCV-JNI以及常见错误 ( 持续更新 )

    最近一个项目要编译深度学习的库,需要用到 opencv 和 JNI,本文档用于记录环境配置中遇到的常见错误以及解决方案 解决办法: 删除文件 .idea/gradle.xml 和 .idea/workspace.xml , 重新编译; 解决办法:Invalid Gradle JDK configuration found 原因是NDK版本过高,跟当前的AndroidStudio版本不匹配

    2024年02月11日
    浏览(45)
  • Android Studio 进行NDK开发,实现JNI,以及编写C++与Java交互(Java调用本地函数)并编译出本地so动态库

    1.首先认识一下NDK。 (1)什么是NDK? NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,

    2024年02月11日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包