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.rhkb.cn/news/180831.html

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

相关文章

【音视频 | Ogg】libogg库详细介绍以及使用——附带libogg库解析.opus文件的C源码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

uni-app小程序使用vant

步骤一&#xff1a;安装 Vant Weapp # 通过 npm 安装 npm i vant/weapp -S --production# 通过 yarn 安装 yarn add vant/weapp --production# 安装 0.x 版本 npm i vant-weapp -S --production步骤二&#xff1a;在根目录下创建“wxcomponents”文件夹 步骤三&#xff1a;找…

分享86个工作总结PPT,总有一款适合您

分享86个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/12aRTr5NKG5YTnMnwNbqOrQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知…

利用 docker 实现JMeter分布式压测

为什么需要分布式&#xff1f; 在工作中经常需要对一些关键接口做高QPS的压测&#xff0c;JMeter是由Java 语言开发&#xff0c;没创建一个线程&#xff08;虚拟用户&#xff09;&#xff0c;JVM默认会为每个线程分配1M的堆栈内存空间。受限于单台试压机的配置很难实现太高的并…

计算机组成与体系结构

计算机组成与体系结构&#xff08;2-4分&#xff09; 课程摘要 计算机组成结构 CPU组成 骚戴理解&#xff1a; 指令寄存器&#xff08;IR&#xff09;存放的是从内存中取得指令&#xff0c;就像个中间站一样&#xff0c;不过是存放指令的中间站程序计数器&#xff08;PC&…

在Photoshop中如何校正倾斜的图片

在Photoshop中如何校正倾斜的图片呢&#xff1f;今天就教大家如何操作。 将需要操作的图片拉到PS软件中&#xff0c;自动形成项目。 点击上方“滤镜”中的“镜头校正”。 进入“镜头校正”窗口&#xff0c;点击左侧的“拉直工具”。文章源自设计学徒自学网-http://www.sx1c.co…

Java日期比较大小的3种方式及拓展

目录 一、字符串String的日期比较 二、数值型long比较 三、日期型Date直接比较 四、Date型日期的获取方式 五、Calendar获取年月日【拓展】 一、字符串String的日期比较 String型的日期通过compareTo()来比较&#xff0c;因为String实现了comparable接口 endDate.compare…

006 Linux 进程的概念 | 获取进程的PID

前言 本文将会向您进程的概念&#xff0c;程序与进程的区别&#xff0c;如何获取进程的标识符-pid 文章重点 1.描述进程——PCB 进程与程序的区别 CPU对进程列表的处理 2.获取进程PID 描述进程-PCB 进程概念 课本概念&#xff1a;程序的一个执行实例或正在执行的程序 内核…

C语言,数据结构指针,结构构体操作符 •,->,*的区别,看这篇就够了

在朋友们学习指针和数据结构这一章的时候&#xff0c;对各种操作符云里雾里。当你看到这么文章之后你就会明白了。 一 • 和 ->运算符 • 运算符&#xff1a;是结构变量访问结构体成员时用的操作符 -> 运算符&#xff1a;这是结构体指针访问结构体成员时调用的运算符。 …

基于java+springboot+vue的游戏道具管理系统

项目介绍 本论文主要论述了如何使用JAVA语言开发一个游戏道具管理系统 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述校园出入管理系统的当前背景以及系统开…

02-React组件与模块

组件与模块 前期准备 安装React官方浏览器调试工具&#xff0c;浏览器扩展搜索即可 比如红色的React就是本地开发模式 开启一个用React写的网站&#xff0c;比如美团 此时开发状态就变成了蓝色 组件也能解析出来 何为组件&模块 模块&#xff0c;简单来说就是JS代…

音视频rtsp rtmp gb28181在浏览器上的按需拉流

按需拉流是从客户视角来看待音视频的产品功能&#xff0c;直观&#xff0c;好用&#xff0c;为啥hls flv大行其道也是这个原因&#xff0c;不过上述存在的问题是延迟没法降到实时毫秒级延迟&#xff0c;也不能随心所欲的控制。通过一段时间的努力&#xff0c;结合自己闭环技术栈…

使用合成数据训练语义分割模型

计算机视觉应用现在在各个科技领域无处不在。 这些模型高效且有效&#xff0c;研究人员每年都会尝试新想法。 新的前沿是试图消除深度学习的最大负担&#xff1a;需要大量的标记数据。 正如本文所述&#xff0c;此问题的解决方案是使用合成数据。 从这些研究中获益最多的计算机…

FMC+DAM驱动LVGL刷屏

前提条件 使用FMC驱动LCD刷屏 LVGL移植 开启DMA 需要开启MEMTOMEMDMA。 开启MPU 有MPU时需要 使能I-cache D-cache时 使用DMA传输数据时要保证数据的完整行和准确性 修改代码 逻辑 等待DMA传输完成然后再刷屏。 修改 在DMA初始化函数中最后添加 注册DMA传输完成调用函…

k8s:二进制搭建 Kubernetes v1.20

目录 1 操作系统初始化配置 2 部署 etcd 集群 2.1 准备签发证书环境 2.2 生成Etcd证书 3 部署 docker引擎 4 部署 Master 组件 5 部署 Worker Node 组件 k8s集群master01&#xff1a;192.168.30.105 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集…

05-流媒体-RTMP协议介绍

05-流媒体-RTMP协议介绍 1.RTMP概述 RTMP是一种常见的流媒体协议,是基于TCP/IP协议模型的应用层协议,工作在TCP协议上,端口是1935。通过TCP三次握手保证传输的可靠。 2.握手 2.1 握手过程 RTMP基于传输层TCP连接后,通过RTMP握手协议来完成RTMP连接。客户端和服务端各发…

将Series中每个值v替换为v在Series中升序排列时的位置值s.rank()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将Series中每个值v 替换为v在Series中 升序排列时的位置值 s.rank() 选择题 下列代码执行三次排名索引a的名次值分别为&#xff1f; import pandas as pd s pd.Series([3,2,0,3],index list…

分享96个工作总结PPT,总有一款适合您

分享96个工作总结PPT&#xff0c;总有一款适合您 工作总结PPT下载链接&#xff1a;https://pan.baidu.com/s/18AriuVGxwINHrmgndX74dw?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

【kafka】记一次kafka基于linux的原生命令的使用

环境是linux&#xff0c;4台机器&#xff0c;版本3.6&#xff0c;kafka安装在node 1 2 3 上&#xff0c;zookeeper安装在node2 3 4上。 安装好kafka&#xff0c;进入bin目录&#xff0c;可以看到有很多sh文件&#xff0c;是我们执行命令的基础。 启动kafka&#xff0c;下面的…

python 深度学习 解决遇到的报错问题9

本篇继python 深度学习 解决遇到的报错问题8-CSDN博客 目录 一、can only concatenate str (not "int") to str 二、cant convert np.ndarray of type numpy.object_. The only supported types are: float64, float32, float16, complex64, complex128, int64, in…