颜色模型-YUV
[TOC]
简介
YUV是一种颜色编码方法。Y'UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。
Y表示明亮度(Luminance、Luma)。U和V则是色度、浓度(Chrominance、Chroma),
可以理解为
- U(Cb)代表蓝色色度分量
- V(Cr)代表红色色度分量
组合在一起表示色彩。
U-V color plane示例,Y value = 0.5,代表RGB色域(color gamut)
将Y、U、V分量拆解,可得下图,自上而下为原图、Y、U、V分量
参考
格式
Packed formats 紧缩格式
将Y、U、V值存储成Macro Pixels数组,每个像素点的 Y,U,V 是连续交替存储的,类似RGB的存储方式
紧缩格式(packed format)中的YUV是混合在一起的,对于YUV4:4:4格式而言,用紧缩格式很合适的,因此就有了UYVY、YUYV等。
举例
- UYVY
- YUYV
Planar formats 平面格式
将Y、U、V的三个分量分别存放在不同的矩阵中。
平面格式(planar formats)是指每Y分量,U分量和V分量都是以独立的平面组织的,也就是说所有的U分量必须在Y分量后面,而V分量在所有的U分量后面,此一格式适用于采样(subsample)。平面格式(planar format)有I420(4:2:0)、YV12、IYUV等。
举例
- I420(4:2:0)
- YV12
- IYUV
抽样方式
分类
- 4:4:4表示完全取样。
- 每一个Y对应一组UV分量。
- 4:2:2表示2:1的水平取样,垂直完全采样。
- 每两个Y共用一组UV分量。
- YV16、NV16
- 4:2:0表示2:1的水平取样,垂直2:1采样。
- 每四个Y共用一组UV分量。
- YUV4:2:0并不是说只有U(即Cb), V(即Cr)一定为0,而是指U:V互相援引,时见时隐,也就是说对于每一个行,只有一个U或者V分量,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0...以此类推
- YV12,YU12、NV12、NV21、YUV420SP、I420
- 4:1:1表示4:1的水平取样,垂直完全采样。
采样方式
色度抽样方式用J:A:B表示
J:最小水平抽样的的宽度,一般为4
A:最小水平抽样区域第一行的色度抽样
B:最小水平抽样区域第二行的色度抽样
- 4:4:4表示完全取样。
- 4:2:2表示2:1的水平取样,垂直完全采样。
- 4:2:0表示2:1的水平取样,垂直2:1采样。
- 4:1:1表示4:1的水平取样,垂直完全采样。
对比RGB数据量
YUV的数据量会比RGB小很多,例如:
- RGB24 每个像素占用3字节
- ARGB32 每个像素占用4字节
而
- YUV444 每个像素占用3字节
- YUV422 每个像素占用2字节
- YUV420 每个像素占用3字节1.5字节
常见格式
NV21、NV12
NV12 is a biplanar format with a full sized Y plane followed by a single chroma plane with weaved U and V values. NV21 is the same but with weaved V and U values.
The 12 in NV12 refers to 12 bits per pixel. NV12 has a half width and half height chroma channel, and therefore is a 420 subsampling. NV16 is 16 bits per pixel, with half width and full height. aka 422. NV24 is 24 bits per pixel with full sized chroma channel. aka 444.
NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。
NV12
NV21
YV12、YU12

YV12,YU12格式(属于YUV420)是一种Plane模式。
YV12
YU12
I420P
和YU12是一样的
YUV420SP、YUV420P
SP : Semi-Planar UV交错存放
NV12和NV21就是我们常见的YUV420SP、YV12和YU12都属于YUV420p
可参考What is difference between planar, semi planar and interleaved format.?
YUYV

YUYV为YUV422采样的存储格式中的一种,相邻的两个Y共用其相邻的两个Cb、Cr,分析,对于像素点Y'00、Y'01 而言,其Cb、Cr的值均为 Cb00、Cr00,其他的像素点的YUV取值依次类推。
YUY2和YUYV类似,UYVY同理
推荐资料
-
强烈推荐雷霄骅的这篇文章视音频数据处理入门:RGB、YUV像素数据处理,操作之后会对YUV、RGB的存储格式有更深的理解
-
圖解YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P、YUV444P的區別
Stride 跨距
我们都知道现在计算机的cpu都是32位或者64位的cpu,他们一次最少读取4、8个字节,如果少于这些,反而要做一些额外的工作,会花更长的时间。所有会有一个概念叫做内存对齐,将结构体的长度设为4、8的倍数。
跨距也是因为同样的理由出现的。因为图像的操作通常按行操作的,如果图像的所有数据都紧密排列,那么会发生非常多次的读取非对齐内存。会影响效率。而图像的处理本就是一个分秒必争的操作,所以为了性能的提高就引入了跨距这个概念。
跨距就是指图像中的一行图像数据所占的存储空间的长度,它是一个大于等于图像宽度的内存对齐的长度。这样每次以行为基准读取数据的时候就能内存对齐,虽然可能会有一点内存浪费,但是在内存充裕的今天已经无所谓了。
pixel stride

Camera2 ImageReader YUV420_888,PreviewSize设置为17001080,返回预览流stride为1600,Image的size为16001200,返回的ysize为1920000,等于1600*1200,如果直接调用下面的代码,
YuvImage yuv = new YuvImage(bytes, ImageFormat.NV21, mPreviewSize.getWidth(), mPreviewSize.getHeight(), new int[y_stride, uv_stride]); yuv.compressToJpeg(new Rect(0, 0, mPreviewSize.getWidth(), mPreviewSize.getHeight()), 90, fos);
保存的图片会大部分灰色,约1/3是绿色,因为实际上从CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP获得的rosolution是不支持17001080,所以系统自动选择了16001200,如果将PreviewSize设置为1600*1200,保存图片则能得到正确结果。
分离Y、U、V分量
以Camera2 ImageReader为例
ByteBuffer yData = image.getPlanes()[0].getBuffer();
ByteBuffer uData = image.getPlanes()[1].getBuffer();
ByteBuffer vData = image.getPlanes()[2].getBuffer();
int ySize = yData.remaining();
int uSize = uData.remaining();
int vSize = vData.remaining();
mCurrentImageData = new byte[ySize + ySize / 2];
yData.get(mCurrentImageData, 0, ySize);
for (int i = ySize; i < ySize + ySize / 2; i++) {
mCurrentImageData[i] = (byte)128;
}
也就是除了y分量,uv全部赋值为128(0x80)
最后使用YUVImage编码成JPEG
YuvImage yuv = new YuvImage(bytes, ImageFormat.NV21,
mPreviewSize.getWidth(), mPreviewSize.getHeight(),
new int[y_stride, uv_stride]);
yuv.compressToJpeg(new Rect(0, 0,
mPreviewSize.getWidth(), mPreviewSize.getHeight()), 90, fos);
最终得到灰度图。