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

2023-11-04 13:15

本文主要是介绍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横屏的情况下进行的

在这里插入图片描述

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的Bitmapval 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

先来看一下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.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格式,而不是RGBArgb888[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.planesval yBuffer = planes[0].bufferval uBuffer = planes[1].bufferval vBuffer = planes[2].bufferval 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的Matcv::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 * heightvar yIndex = 0var uvIndex = frameSize//var a: Intvar R: Intvar G: Intvar B: Intvar Y: Intvar U: Intvar V: Intfor (j in 0 until height) {for (i in 0 until width) {//a = argb[j * width + i] and -0x1000000 shr 24R = argb[j * width + i] and 0xff0000 shr 16G = argb[j * width + i] and 0xff00 shr 8B = argb[j * width + i] and 0xff shr 0Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128yuv420sp[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

这篇关于Android和JNI交互 : 常见的图像格式转换 : NV21、RGBA、Bitmap等的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/345317

相关文章

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

C语言中的数据类型强制转换

《C语言中的数据类型强制转换》:本文主要介绍C语言中的数据类型强制转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C语言数据类型强制转换自动转换强制转换类型总结C语言数据类型强制转换强制类型转换:是通过类型转换运算来实现的,主要的数据类型转换分为自动转换