最近项目中遇到YUV->RGB的转换,顺便对YUV进行了一下简单了解。解决了以下疑问。
(1)什么是YUV。
(2)YUVxxx采样格式是什么含义。
(3)为什么可以存在xxx采样格式。
(4)YUV的存储格式。
话归正题。
概述
YUV是颜色编码方式,常用于视频及图像处理中。
其中的YUV是三个分量。“Y”表示明亮度(Luminance或Luma),也就是灰度值。“U”和“V” 表示的是彩色信息,分别为色度和浓度(Chrominance和Chroma)。
由于相邻的两个像素,数据差异不大,所以,丢弃相邻像素的部分数据对于整体影响不大。同时,丢弃数据,还节省了空间便于存储。人对亮度比较敏感,而对色彩不怎么敏感。所以,每个像素的亮度Y数据是绝对不动的,而色差数据UV可以进行丢弃。没有UV信息,一样可以显示完整的图像,只不过是黑白的。因此在数据的存储上,根据数据丢弃方式(采样方式)的不同,YUV又出现了不同的格式。
采样方式
采样是将4个像素作为一组进行的。原因在于:图像每行所占字节数必须是4的倍数,才能保证无误的转换。
YUV主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0。而Android中常用的YUV420格式,即指YUV4:2:0。
YUV的分量数据各占用8位(各占1字节),在不丢失数据的情况下,4个像素(12字节)的数据依次为,
Y0U0V0 Y1U1V1 Y2U2V2 Y3U3V3
(1)YUV444
存储时,数据为Y0U0V0 Y1U1V1 Y2U2V2 Y3U3V3。
即YUV444,4个像素里的数据有4个Y,4个U, 4个V,未丢弃任何数据。
(2)YUV422
存储时,数据为Y0U0 Y1U1 Y2V2 Y3V3,即YUV422,4个像素里的数据有4个Y,2个U, 2个V。
YUV422的采样方式为:奇数像素丢弃V,偶数像素丢弃U。
YUV422为横向丢弃数据的采样方式。由于邻近像素的相似性不仅局限于横向,数据还可以进一步纵向丢弃,以减少存储空间。因此,YUV420便出现了。
(3)YUV420
YUV420为横向纵向同时丢弃数据的采样方式。以一个YUV444格式的4x4数据为例
Y00U00V00 Y01U01V01 Y02U02V02 Y03U03V03
Y10U10V10 Y11U11V11 Y12U12V12 Y13U13V13
Y20U20V20 Y21U21V21 Y22U22V22 Y23U23V23
Y30U30V30 Y31U31V31 Y32U32V32 Y33U33V33
以YUV420格式丢弃数据后,将变为
Y00U00 Y01 Y02U02 Y03
Y10V10 Y11 Y12V12 Y13
Y20U20 Y21 Y22U22 Y23
Y30V30 Y31 Y32V32 Y33
YUV420的采样方式为:
(i)偶数像素丢弃UV。
(ii)在(i)的基础上,奇数行进一步丢弃V,偶数行进一步丢弃U。
存储格式
YUV有两种存储格式。
(1)紧缩格式(packed formats):将Y、U、V值存储成Macro Pixels数组。
(2)平面格式(planar formats):将Y、U、V的三个分量分别存放在不同的矩阵中。
需要说明的是,Android NDK使用平面格式存储YUV数据,可以调用AImage_getPlaneData分别获取Y、U、V的数据。其planeIdx分别为0,1,2。
media_status_t AImage_getPlaneData(
const AImage* image, int planeIdx,
/*out*/uint8_t** data, /*out*/int* dataLength);
题外话:Rotate
之所以说Rotate,是因为在使用googlesample ndk camer时被坑了,所以跟同事特意理了一下思路。耗时较长,因此特意一并记录。
图像的0/90/180/270角度旋转(android图像的旋转,为顺时针旋转),即为矩阵坐标的转换。
将2行3列的像素矩阵,且旋转角度为270图像A摆正为图像B。
A00 | A01 | A02 |
A10 | A11 | A12 |
B00 | B01 |
B10 | B11 |
B20 | B21 |
即,将图A顺时针旋转90度,得到图B
A00->B01,A01->B11,A02->B21,
A10->B00,A11->B10,A12->B20
若用r,c,w,h分别表示B图的行索引,列索引,图像宽和高,则坐标转换公式应为:B[r][c]=A[w-1-c][r]。