# YUVConvert **Repository Path**: caigp/yuvconvert ## Basic Information - **Project Name**: YUVConvert - **Description**: 提供Java类操作YUV - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2023-03-21 - **Last Updated**: 2026-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # YUVConvert ## 介绍 提供 Java 类操作 YUV,经过测试,在RK3399 Android7.1 1280*720的分辨率,nv21转abgr使用libyuv在8ms左右, Java使用转换公式在60ms,可以根据场景选择要使用的方法。libyuv只用Java写了部分转换代码 ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/img.png) **1. YUV 模型分类: 是根据一个亮度(Y 分量)和两个色度(UV 分量)来定义颜色空间,常见的 YUV 格式描述有 YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV422、YUV420 等,** 其中常见的 YUV422 包含 YUVY ,UYVY,YUV422P 等 比较常见的 YUV420 分为两种:YUV420P 和 YUV420SP 在 DVD 中,UV 色度信号被存储成 Cb 和 Cr(C 代表颜色,b 代表蓝色,r 代表红色) **2. YUV 采样比例: YUV444 4:4:4 采样,每一个 Y 对应一组 UV 分量,一个 YUV 占 8+8+8 = 24bits 3 个字节。** YUV422 4:2:2 采样,每两个 Y 共用一组 UV 分量,一个 YUV 占 8+4+4 = 16bits 2 个字节。 YUV420 4:2:0 采样,每四个 Y 共用一组 UV 分量,一个 YUV 占 8+2+2 = 12bits 1.5 个字节 下面用三个图来直观地表示采集的方式,以黑点表示采样该像素点的 Y 分量,以空心圆圈表示采用该像素点的 UV 分量: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-99102bc7d428614c1eb4f30a0d65d994_720w.webp) **3. YUV422 格式:常见的 YUVY ,UYVY,YUV422P 都是基于 4:2:2 采样的,** 对于 4:2:2 采样的图,如果图片的宽为 width,高为 heigth,在内存中占的空间为 width _ height _ 2,其中 width _ height 的空间存放 Y 分量, width _ height/ 2 的空间存放 U 分量,width \* height / 2 的空间存放 V 分量 3.1 YUYV422 (YUVY422)格式 : 相邻的两个 Y 共用其相邻的两个 Cb、Cr,分析,对于像素点 Y'00、Y'01 而言,其 Cb、Cr 的值均为 Cb00、Cr00,其他的像素点的 YUV 取值依次类推,如下图所示(Cb、Cr 的含义等同于 U、V,下同): ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-8658385445cf3b7a567c0107951a60c8_720w.webp) 3.2 UYVY422 格式 : UYVY 格式也是 YUV422 采样的存储格式中的一种,只不过与 YUYV 不同的是 UV 的排列顺序不一样而已,如下图所示,还原其每个像素点的 YUV 值的方法与上面一样: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-e9c57fd4cad2c237b2b8afa582a1a641_720w.webp) 3.3 YUV422P 格式 : YUV422P 也属于 YUV422 的一种,它是一种 Plane 模式,即平面模式,并不是将 YUV 数据交错存储,而是先存放所有的 Y 分量,然后存储所有的 U(Cb)分量,最后存储所有的 V(Cr)分量,如下图所示。 其每一个像素点的 YUV 值提取方法也是遵循 YUV422 格式的最基本提取方法,即两个 Y 共用一个 UV。比如,对于像素点 Y'00、Y'01 而言,其 Cb、Cr 的值均为 Cb00、Cr00: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-3650cdb57bed3462f2851c3d6308907b_720w.webp) **4. YUV420 格式: 常见的 YUV420P 和 YUV420SP 都是基于 4:2:0 采样的,** 对于 4:2:0 采样的图,如果图片的宽为 width,高为 heigth,在内存中占的空间为 width _ height _ 3 / 2,其中前 width _ height 的空间存放 Y 分量,接着 width _ height / 4 存放 U 分量,最后 width \* height / 4 存放 V 分量 4.1 YUV420P 格式: YUV420P 又叫 plane 平面模式,Y , U , V 分别在不同平面,也就是有三个平面,它是 YUV 标准格式 4:2:0, YUV420P 主要分为:YU12 和 YV12: 4.1.1 YU12(I420)格式: YU12 在 android 平台下也叫 I420 格式,数据存储方式,首先是所有 Y 值,然后是所有 U 值,最后是所有 V 值,亮度(行 × 列) + U(行 × 列/4) + V(行 × 列/4),如下图所示: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-821c824193e63e717092a4d0529a01e1_720w.webp) 4.1.2 YV12 格式: YV12 格式与 YU12 基本相同,数据存储方式,首先是所有 Y 值,然后是所有 V 值,最后是所有 U 值。只要注意从适当的位置提取 U 和 V 值,YU12 和 YV12 都可以使用相同的算法进行处理,亮度 Y(行 × 列) + V(行 × 列/4) + U(行 × 列/4),如下图所示: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-773ffcb76dd1275a0a5d5c6988dd4f60_720w.webp) 4.2 YUV420SP 格式: YUV420SP 格式的图像阵列,数据存储方式,首先是所有 Y 值,然后是 UV 或者 VU 交替存储,NV12 和 NV21 属于 YUV420SP 格式,是一种 two-plane 模式,即 Y 和 UV 分为两个 plane,但是 UV(CbCr)为交错存储,而不是分为三个平面。 主要分为:NV12 和 NV21: 4.2.1 NV21 格式: android 手机从摄像头采集的预览数据一般都是 NV21,存储顺序是先存 Y,再 VU 交替存储,NV21 存储顺序是先存 Y 值,再 VU 交替存储:YYYYVUVUVU,以 4 X 4 图片为例子,占用内存为 4 X 4 X 3 / 2 = 24 个字节,如下图所示: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-65f926fd1ce4e1aaa5c0ce01c4ba7ad2_720w.webp) 4.2.2 NV12 格式: NV12 与 NV21 类似,也属于 YUV420SP 格式,NV12 存储顺序是先存 Y 值,再 UV 交替存储:YYYYUVUVUV,以 4 X 4 图片为例子,占用内存为 4 X 4 X 3 / 2 = 24 个字节,如下图所示: ![](https://gitee.com/caigp/yuvconvert/raw/master/pic/v2-0fd2b7e84847c332685d2c17464608c6_720w.webp) ## YUV和RGB的转换 YUV与RGB互转的公式有很多,不同的色彩空间的转换公式是不一样的 YUV有多种表现形式 除了色彩空间,还需要注意YUV的多种表现形式,比如:YUV:YUV是一种模拟型号,Y∈[0,1] U,V∈[-0.5,0.5] YCbCr:也叫YCC或者Y'CbCr YCbCr是数字信号,它包含两种形式,分别为TV range 和 full range,TV range主要是广播电视采用的标准,full range主要是pc端采用的标准,所以full range有时也叫pc range TV range 的各个分量的范围为:YUV Y∈[16,235] Cb∈[16-240] Cr∈[16-240] full range 的各个分量的范围均为: 0-255 这里提供一套Android平台测试可行的转换公式 ``` r = y + ((360 * (v - 128)) >> 8); g = y - (((88 * (u - 128) + 184 * (v - 128))) >> 8); b = y + ((455 * (u - 128)) >> 8); y = (77 * r + 150 * g + 29 * b) >> 8; u = ((-44 * r - 87 * g + 131 * b) >> 8) + 128; v = ((131 * r - 110 * g - 21 * b) >> 8) + 128; ``` ## 使用libyuv ``` public class Test { static { System.loadLibrary("yuv"); } public final static int FORMAT_ARGB_8888 = 32; public final static int FORMAT_NV21 = 12; public static native int ABGRToNV21(int width, int height, byte[] abgr, byte[] nv21); public static native int BGRAToARGB(int width, int height, byte[] bgra, byte[] argb); public static native int ARGBToBGRA(int width, int height, byte[] argb, byte[] bgra); public static native int NV21ToABGR(int width, int height, byte[] nv21, byte[] abgr); public static native int NV21ToI420(int width, int height, byte[] nv21, byte[] I420); public static native int I420Rotate(int width, int height, byte[] src, byte[] dst, int rotate); public static native int I420ToABGR(int width, int height, byte[] I420, byte[] abgr); public static native int bitmapToNv21(Bitmap bitmap, byte[] nv21); public static native int ARGBRotate(int width, int height, byte[] src, byte[] dst, int rotate); public static byte[] createByteBuff(int width, int height, int format) { return new byte[width * height * format / 8]; } } ``` ## java使用公式 ``` public class Test2 { public static void NV21ToABGR(int width, int height, byte[] nv21, byte[] abgr) { int nvOff = width * height; int i, j, yIndex = 0; int y, u, v; int r, g, b, nvIndex; for (i = 0; i < height; i++) { for (j = 0; j < width; j++, ++yIndex) { nvIndex = (i / 2) * width + j - j % 2; y = nv21[yIndex] & 0xff; u = nv21[nvOff + nvIndex] & 0xff; v = nv21[nvOff + nvIndex + 1] & 0xff; r = y + ((360 * (v - 128)) >> 8); g = y - (((88 * (u - 128) + 184 * (v - 128))) >> 8); b = y + ((455 * (u - 128)) >> 8); r = r > 255 ? 255 : (Math.max(r, 0)); g = g > 255 ? 255 : (Math.max(g, 0)); b = b > 255 ? 255 : (Math.max(b, 0)); abgr[yIndex * 4] = (byte) b; abgr[yIndex * 4 + 1] = (byte) g; abgr[yIndex * 4 + 2] = (byte) r; abgr[yIndex * 4 + 3] = (byte) 0; } } } public static void ABGRToNV21(int width, int height, byte[] abgr, byte[] nv21) { int nvOff = width * height; int i, j, yIndex = 0; int y, u, v; int r, g, b, nvIndex; for (i = 0; i < height; i++) { for (j = 0; j < width; j++, ++yIndex) { nvIndex = (i / 2) * width + j - j % 2; r = abgr[yIndex * 4] & 0xff; g = abgr[yIndex * 4 + 1] & 0xff; b = abgr[yIndex * 4 + 2] & 0xff; y = (77 * r + 150 * g + 29 * b) >> 8; u = ((-44 * r - 87 * g + 131 * b) >> 8) + 128; v = ((131 * r - 110 * g - 21 * b) >> 8) + 128; nv21[yIndex] = (byte) y; nv21[nvOff + nvIndex] = (byte) v; nv21[nvOff + nvIndex + 1] = (byte) u; } } } } ``` ## 用法 bitmap转nv21,nv21转ABGR ``` Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a); int w = bitmap.getWidth(); int h = bitmap.getHeight(); byte[] nv21 = Test.createByteBuff(w, h, Test.FORMAT_NV21); Test.bitmapToNv21(bitmap, nv21); bitmap.recycle(); byte[] abgr = Test.createByteBuff(w, h, Test.FORMAT_ARGB_8888); Test.NV21ToABGR(w, h, nv21, abgr); Bitmap _bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); _bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(abgr)); runOnUiThread(() -> { imageView.setImageBitmap(_bitmap); }); ``` ``` Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a); int w = bitmap.getWidth(); int h = bitmap.getHeight(); byte[] array = new byte[bitmap.getByteCount()]; bitmap.copyPixelsToBuffer(ByteBuffer.wrap(array)); bitmap.recycle(); byte[] nv21 = Test.createByteBuff(w, h, Test.FORMAT_NV21); Test2.ABGRToNV21(w, h, array, nv21); byte[] abgr = Test.createByteBuff(w, h, Test.FORMAT_ARGB_8888); Test2.NV21ToABGR(w, h, nv21, abgr); Bitmap _bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); _bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(abgr)); runOnUiThread(() -> { imageView.setImageBitmap(_bitmap); }); ``` ## ARGB旋转,旋转就是像素的位置变动了,90和270的时候图像宽高会变化。 ``` Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a); int w = bitmap.getWidth(); int h = bitmap.getHeight(); byte[] array = new byte[bitmap.getByteCount()]; bitmap.copyPixelsToBuffer(ByteBuffer.wrap(array)); bitmap.recycle(); byte[] abgr = Test.createByteBuff(w, h, Test.FORMAT_ARGB_8888); Test.ARGBRotate(w, h, array, abgr, 90); Bitmap _bitmap = Bitmap.createBitmap(h, w, Bitmap.Config.ARGB_8888); _bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(abgr)); runOnUiThread(() -> { imageView.setImageBitmap(_bitmap); }); ```