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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/130505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI开源 - LangChain UI 之 Flowise

原文&#xff1a;AI开源 - LangChain UI 之 Flowise 一、Flowise 简介 Flowise 是一个为 LangChain 设计的用户界面(UI)&#xff0c;使得使用 LangChain 变得更加容易&#xff08;低代码模式&#xff09;。 通过拖拽可视化的组件&#xff0c;组建工作流&#xff0c;就可以轻…

ScrapeKit库中Swift爬虫程序写一段代码

以下是一个使用ScrapeKit库的Swift爬虫程序&#xff0c;用于爬取网页视频的代码&#xff1a; import ScrapeKit// 创建一个配置对象&#xff0c;用于指定爬虫ip服务器信息 let config Configuration(proxyHost: "duoip", proxyPort: 8000)// 创建一个爬虫对象 let s…

diffusers-Load pipelines,models,and schedulers

https://huggingface.co/docs/diffusers/using-diffusers/loadinghttps://huggingface.co/docs/diffusers/using-diffusers/loading 有一种简便的方法用于推理是至关重要的。扩散系统通常由多个组件组成&#xff0c;如parameterized model、tokenizers和schedulers&#xff0c…

Spring-Spring 之底层架构核心概念解析

BeanDefinition BeanDefinition表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等lazyInit&#xff1a;表示Bean是否是懒加载initMeth…

LeetCode 421. 数组中两个数的最大异或值

原题链接&#xff1a;https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/description/?envTypedaily-question&envId2023-11-04 题目分析 异或且时间复杂度在nlogn内第一反应想到字典树&#xff0c;扫一遍存进字典树&#xff0c;然后遍历每个数&…

【Git企业开发】第四节.Git的分支管理策略和bug分支

文章目录 前言一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 1.2 企业分支管理策略二、bug分支三、删除临时分支四、总结总结 前言 一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 通常合并分支时&#xff0c;如果可能&#xff0c;Git 会…

AI:51-基于深度学习的电影评价

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

【CSS】div 盒子居中的常用方法

<body><div class"main"><div class"box"></div></div> </body>绝对定位加 margin: auto; &#xff1a; <style>* {padding: 0;margin: 0;}.main {width: 400px;height: 400px;border: 2px solid #000;positio…

C++prime之输入输出文件

作为一种优秀的语言&#xff0c;C必然是能操作文件的&#xff0c;但是我们要知道&#xff0c;C是不直接处理输入输出的&#xff0c;而是通过一族定义在标准库中的类型来处理IO的。 ‘流’和‘缓冲区’ ‘流’和‘缓冲区’ C程序把输入输出看作字节流&#xff0c;并且其只检查…

Microsoft Dynamics 365 CE 扩展定制 - 5. 外部集成

本章内容包括: 使用.NET从其他系统连接到Dynamics 365使用OData(Java)从其他系统连接到Dynamics 365使用外部库从外部源检索数据使用web应用程序连接到Dynamics 365运行Azure计划任务设置Azure Service Bus终结点与Azure Service Bus构建近乎实时的集成使用来自Azure服务总线…

接口测试入门,如何划分接口文档

1.首先最主要的就是要分析接口测试文档&#xff0c;每一个公司的测试文档都是不一样的。具体的就要根据自己公司的接口而定&#xff0c;里面缺少的内容自己需要与开发进行确认。 我认为一针对于测试而言的主要的接口测试文档应该包含的内容分为以下几个方面。 a.具体的一个业…

selenium自动化测试入门 —— 设置等待时间

time.sleep(3) 固定等待3秒 driver.implicitly_wait(10) 隐性的等待&#xff0c;对应全局 WebDriverWait( driver, timeout).until(‘有返回值的__call__()方法或函数’) 显性的等待&#xff0c;对应到元素 一、time.sleep(seconds) 固定等待 import time time.sleep(3) #…

axios 全局错误处理和请求取消

这两个功能都是用拦截器实现。 前景提要&#xff1a; ts 简易封装 axios&#xff0c;统一 API 实现在 config 中配置开关拦截器 全局错误处理 在构造函数中&#xff0c;添加一个响应拦截器即可。在构造函数中注册拦截器的好处是&#xff0c;无论怎么实例化封装类&#xff0c…

基础课19——客服系统知识库的搭建流程

1.收集整理业务数据 注意&#xff1a;我们在做业务数据收集时&#xff0c;往往是甲方提供给我们的&#xff0c;这时就需要确定一个标准&#xff0c;否则对知识库梳理工作会带来很大的难度&#xff0c;建议和甲方沟通确认一个双方都统一的知识库原材料。 2.创建知识库 在创建知…

Docker基础(简单易懂)

目录 一、docker是什么 核心概念 二、docker安装 1、卸载docker 2、使用yum 安装 三、docker常用命令 1、帮助命令 2、镜像命令 1&#xff09;查看镜像 2&#xff09;查询镜像 3&#xff09;拉取镜像 4&#xff09;删除镜像 3、容器命令 四、容器数据卷 五、Dock…

深度学习之基于Tensorflow人脸面部表情识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Tensorflow的人脸面部表情识别系统是一种基于深度学习技术的图像处理应用&#xff0c;该系统主要通过人脸图像数…

CoCa论文笔记

摘要 计算机视觉任务中&#xff0c;探索大规模预训练基础模型具有重要意义&#xff0c;因为这些模型可以可以极快地迁移到下游任务中。本文提出的CoCa&#xff08;Contrastive Captioner&#xff09;&#xff0c;一个极简设计&#xff0c;结合对比损失和captioning损失预训练一…

Zabbix如何监控腾讯云NAT网关

1、NAT网关介绍 NAT 网关&#xff08;NAT Gateway&#xff09;是一种支持 IP 地址转换服务&#xff0c;提供网络地址转换能力&#xff0c;主要包括SNAT&#xff08;Source Network Address Translation&#xff0c;源网络地址转换&#xff09;和DNAT&#xff08;Destination N…

如何使用Python和Matplotlib创建双Y轴动态风格折线图 | 数据可视化教程

前言 我的科研论文中需要绘制一个精美的折线图&#xff0c;我的折线图中有三条曲线&#xff0c;分别表示期望角速度指令信号&#xff0c;和实际的角速度信号&#xff0c;还有实际的航向角信号&#xff0c;现在我已经拥有了数据&#xff0c;使用Python中matplotlib.plt.plot来直…

Java线程的基本概念和五种状态

1. 线程 1.1 创建线程 创建线程通常有以下三种方式&#xff1a; 实现 Runnable 接口&#xff0c;并重写其 run 方法&#xff1a; public class J1_Method01 {public static void main(String[] args) {System.out.println("Main线程的ID为&#xff1a;" Thread.curr…