Camera2 YUV420_888--android image plane的解释

2024-05-04 03:18

本文主要是介绍Camera2 YUV420_888--android image plane的解释,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

getPixelStride() 获取行内连续两个颜色值之间的距离(步长)。
getRowStride() 获取行间像素之间的距离。

 

Camera2 YUV420_888
原创lbknxy 发布于2017-01-23 16:06:35 阅读数 7955  收藏
展开

Camera2 YUV420_888转RGB
官网文档介绍
Android PAI 对 YUV420_888的介绍 ,大致意思如下:

它是YCbCr的泛化格式,能够表示任何4:2:0的平面和半平面格式,每个分量用8 bits 表示。带有这种格式的图像使用3个独立的Buffer表示,每一个Buffer表示一个颜色平面(Plane),除了Buffer外,它还提供rowStride、pixelStride来描述对应的Plane。
使用Image的getPlanes()获取plane数组:
Image.Plane[] planes = image.getPlanes();
它保证planes[0] 总是Y ,planes[1] 总是U(Cb), planes[2]总是V(Cr)。并保证Y-Plane永远不会和U/V交叉(yPlane.getPixelStride()总是返回 1 )。U/V-Plane总是有相同的rowStride和 pixelStride()(即有:uPlane.getRowStride() == vPlane.getRowStride() 和 uPlane.getPixelStride() == vPlane.getPixelStride();)。

U/V的平(Planar)面和半平面(Semi-Planar)
U/V的Planar存储(YUV420P)
我测试几个设备没有找到存储格式是Planar的设备,这里使用参考2的例子简单说一下:

               Log.i(TAG,"image format: " +image.getFormat());
                // 从image里获取三个plane
                Image.Plane[] planes = image.getPlanes();

                for (int i = 0; i < planes.length; i++) {
                    ByteBuffer iBuffer = planes[i].getBuffer();
                    int iSize = iBuffer.remaining();
                    Log.i(TAG, "pixelStride  " + planes[i].getPixelStride());
                    Log.i(TAG, "rowStride   " + planes[i].getRowStride());
                    Log.i(TAG, "width  " + image.getWidth());
                    Log.i(TAG, "height  " + image.getHeight());
                    Log.i(TAG, "Finished reading data from plane  " + i);
                    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
getPixelStride() 获取行内连续两个颜色值之间的距离(步长)。
getRowStride() 获取行间像素之间的距离。

输出如下:

image format: 35
pixelStride 1
rowStride 1920
width 1920
height 1080
buffer size 2073600
Finished reading data from plane 0
pixelStride 1
rowStride 960
width 1920
height 1080
buffer size 518400
Finished reading data from plane 1
pixelStride 1
rowStride 960
width 1920
height 1080
buffer size 518400
Finished reading data from plane 2

在ImageFormat中,YUV_420_888格式的数值是35,如上所示,可知当前Preview格式是YUV_420_888,根据image的分辨率是 1920 x 1080 ,像素点个数是2073600 。下面分别对plane[0]、plane[1]、plane[2]作分析。

plane[0]表示Y,rowStride是1920 ,其pixelStride是1 ,说明Y存储时中间无间隔,每行1920个像素全是Y值,buffer size 是 plane[0]的1/4 ,buffer size / rowStride= 1080可知Y有1080行。
plane[1]表示U,rowStride是960 ,其pixelStride也是1,说明连续的U之间没有间隔,每行只存储了960个数据,buffer size 是 plane[0]的1/4 ,buffer size / rowStride = 540 可知U有540行,对于U来说横纵都是1/2采样。
pane[2]和plane[1]相同。、
此时,YUV三个量分离,每一块数据单独存储在独立的plane里。此时的YUV420叫做YUV420P或I420,以分辨率8 x 4 为例其存储结构:


U/V的Semi-Planar存储 (YUV420SP)
Nexus 6P和东东那部三星机都属于此类,采用相同的代码输出如下:

image format: 35
pixelStride 1
rowStride 1920
width 1920
height 1080
buffer size 2073600
Finished reading data from plane 0
pixelStride 2
rowStride 1920
width 1920
height 1080
buffer size 1036800
Finished reading data from plane 1
pixelStride 2
rowStride 1920
width 1920
height 1080
buffer size 1036800
Finished reading data from plane 2

image格式依然是YUV_420_888,分辨率是1920 x 1080 。

plane[0] 是Y数据,从rowStride是1920和 pixelStride是1,可知每行1920个像素且Y数据之间无间隔,从buffer size / rowStride = 1080 Y数据有1080行。
plane[1] 是U数据,rowStride 是1920, rowStride是2 ,说明每行1920个像素中每两个连续的U之间隔了一个像素,buffer中索引为: 0 , 2 , 4, 6, 8 … 是U数据,即步长为2。 每行实际的U数据只占1/2 ,buffer size / rowStride = 540 只有540行,说明纵向采样也是1/2 ,但buffer size 是 plane[0]的 1/2而不是1/4, 连续的U之间到底存储了什么数据,才使得buffer size 变为plane[0]的1/2了?
同plane[1]。
通过如下方法分别打印Y、U、V三个buffer 到文件中(十六进制格式),来看一下plane[1]和plane[2]中存储数据的特点:

                    // Y-buffer
                    ByteBuffer yBuffer = planes[0].getBuffer();
                    int ySize = yBuffer.remaining();
                    byte[] yBytes = new byte[ySize];
                    yBuffer.get(yBytes);

                    // U-buffer
                    ByteBuffer uBuffer = planes[1].getBuffer();
                    int uSize = uBuffer.remaining();
                    byte[] uBytes = new byte[uSize];
                    uBuffer.get(uBytes);

                    // V-buffer
                    ByteBuffer vBuffer = planes[2].getBuffer();
                    int vSize = vBuffer.remaining();
                    byte[] vBytes = new byte[vSize];
                    vBuffer.get(vBytes);

                    String yFileName = "Y";
                    String uFileName = "U";
                    String vFileName = "V";

                    // 保存目录
                    File dir = new File(mRootDir + File.separator + "YUVV");

                    if (!dir.exists()) {
                        dir.mkdir();
                    }

                    // 文件名
                    File yFile = new File(dir.getAbsolutePath() + File.separator + yFileName + ".yuv");
                    File uFile = new File(dir.getAbsolutePath() + File.separator + uFileName + ".yuv");
                    File vFile = new File(dir.getAbsolutePath() + File.separator + vFileName + ".yuv");


                    try {

                        // 以字符方式书写
                        Writer yW = new FileWriter(yFile);
                        Writer uW = new FileWriter(uFile);
                        Writer vW = new FileWriter(vFile);

                        for (int i = 0; i < ySize; i++) {

                            String preValue = Integer.toHexString(yBytes[i]); // 转为16进制
                            // 因为byte[] 元素是一个字节,这里只取16进制的最后一个字节
                            String lastValue = preValue.length() > 2 ? preValue.substring(preValue.length() - 2) : preValue;
                            yW.write(" " + lastValue + " "); // 写入文件
                            if ((i + 1) % 20 == 0) {  // 每行20个
                                yW.write("\n");
                            }
                        }
                        yW.close();


                        for (int i = 0; i < uSize; i++) {
                            String preValue = Integer.toHexString(uBytes[i]);
                            String lastValue = preValue.length() > 2 ? preValue.substring(preValue.length() - 2) : preValue;
                            uW.write(" " + lastValue + " ");
                            if ((i + 1) % 20 == 0) {
                                uW.write("\n");
                            }
                        }
                        uW.close();


                        for (int i = 0; i < vSize; i++) {
                            String preValue = Integer.toHexString(vBytes[i]);
                            String lastValue = preValue.length() > 2 ? preValue.substring(preValue.length() - 2) : preValue;
                            vW.write(" " + lastValue + " ");
                            if ((i + 1) % 20 == 0) {
                                vW.write("\n");
                            }
                        }
                        vW.close();


                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
打开U.yuv和V.yuv :

U.yuv文件 :

 80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7c  80  7c  80  7c 
 ...
1
2
V.yuv文件:

 7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7c  80  7c  80 
 ...
1
2
将V.yuv错开一位 :

U :  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7c  80  7c  80  7c ...
V :      7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7b  80  7c  80  7c  80 ...
1
2
可以发现U和V错开一位后,对应位相等,实际上:

plane[1] : UVUVUVUVUVUVUVUV...
plane[2] : VUVUVUVUVUVUVUVU...
1
2
这就是为什么plane[1]和plane[2]的buffer size 是plane[0]的1/2而不是1/4的原因。

看8 x 4的NV12存储结构(NV21只是UV交错顺序相反):


结论
plane[0] + plane[1] 可得NV12

plane[0] + plane[2] 可得NV21

参考3中获取I420和NV21的方法是:先从plane[0]中提取出Y数据,然后在plane[1]中提取U数据,最后在plane[2]中提取V数据。

两种方法通过Shader解码后都已得到正确的预览图像。

注意


如上两个工具不能打开以上所得的NV12和NV21图像,但若是以plane[0] + plane[1] + plane[2] 顺序写入文件,在上述工具中选择NV12格式可以打开。

参考
参考1 : ImageFormat#YUV_420_888
参考2 : Android: Image类浅析(结合YUV_420_888)
参考3 : Android: YUV_420_888编码Image转换为I420和NV21格式byte数组
————————————————
版权声明:本文为CSDN博主「lbknxy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lbknxy/article/details/54633008

这篇关于Camera2 YUV420_888--android image plane的解释的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

wolfSSL参数设置或配置项解释

1. wolfCrypt Only 解释:wolfCrypt是一个开源的、轻量级的、可移植的加密库,支持多种加密算法和协议。选择“wolfCrypt Only”意味着系统或应用将仅使用wolfCrypt库进行加密操作,而不依赖其他加密库。 2. DTLS Support 解释:DTLS(Datagram Transport Layer Security)是一种基于UDP的安全协议,提供类似于

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动