转载(跳到原文),发现这篇blog写的不错,尤其是针对常错的yuv颜色的部分很很少提及。
做视频图像之流,YUV格式是最基本的知识,原不必细说的。只是现在发现自己还是会遇到很多的细节问题,现在自己对于这个的经验也越多,不免就想总结下。
这个问题居然熟悉到我以为自己已经写过这个话题了。。原来自己曾经总结过程一个wiki,不过这个是干货中的干货,就像吃压缩饼干,还没有水喝一样,囫囵吞枣,不小心还会噎着。
YUV其实是一个颜色空间,RGB是大家非常熟悉的另外一个颜色空间。
常用的颜色空间有这些:
- YUV,用于视频图像的压缩、显示等。
- RGB/RGBA,用于graphic上的显示等,最为常见。
- HSL/HSV,同样用于显示。
- CMYK,用于印刷之类。
其中YUV颜色空间,又有具体不同的标准。
YUV空间有3个分量:
- Y 亮度
- U/V, 两个色度分量
之所以压缩显示摄像领域采用YUV格式,最主要的原因是人的眼睛的物理特性。人的眼睛对亮度信息最为敏感,颜色信息相对没有那么明显。因此降低对色度的采样,可以基本保持视觉效果不变。
比特深度/位深
每个数据至采用几个比特表示。
一般最为常见的是8bit,也就是一个像素。现在已经有10bit视频流。医用显示器上显示位深可以达到12bit,不过据说很贵啦。位深越大,表示数据的阶数越多。早年彩屏手机出来的时候,喜欢号称自己有一千六百万色,其实就是(2 ^ 8) ^ 3 = 16777216啦。其实就是位深为8,RGB三色显示的意思。
采样率
YUV格式有4:4:4,4:2:2,4:2:0三种常见的采样方式。
- 4:4:4,每个像素采样YUV三个分量
- 4:2:2,每个像素采样Y分量,UV分量水平方向每两个像素采样一次
-
4:2:0,每个像素采样Y分量,UV分量水平数值方向每4个像素采样一次
image.png
image.png
结合采样率和位深概念,有bit per pixel的概念,就是平均每个像素需要多少比特。
根据上面采样率的定义,结果如下:
sample bit per pixel
- 444 24
- 422 16
- 420 12
由此,也印证了最初关于YUV方式的讨论。
示例
了解RGB的,可以很直观的理解其作用机制,就是RGB三原色,同时RGB相同,可以简单降为灰度图。 #000000 表示黑色, #ffffff 表示白色,也非常易于理解。
YUV在这点上就相对没有那么直观(可能还是接触较少的缘故)。YUV的意义前面已经介绍过。其中UV两个分量是有符号的。或者对于8比特的情况下,可以认为是有bias = - 2 ^ 7 的。因此Y的取值范围为[0,255], UV的取值范围为[-127,128]。当UV为0的时候,表示没有颜色,颜色为灰色。而UV为0的时候,其二进制表示为0x80。因为 0x80 - bias = 0。所以 0x008080 为黑色, 0xff8080 为白色。
那么0x000000呢,为绿色。
注意,并不是强度为0x00,就是灰色或者白到黑色了。在YUV里,不是这样的。这里UV分量都是0x00,就决定了其颜色为绿色,强度分量为0x00表示是深绿,0xff0000,则是浅绿。这里的亮度,理解为强度更为合适啊。
那么0xffffff呢,为浅粉红色。
这两个颜色的特殊意义,在于一般程序中会将数据清空为全零数据,那么这些区域显示出来就是绿色。解码出现错误的时候,也很有可能是绿色的块。对于只有有效的Y数据,但是没有有效的UV数据的时候,所有的内容都会看上去蒙上一层绿色。
同理显示的时候,也有可能遇到粉屏。
从这里截个图,因为实在是太直观了。
YUV分布
对比RGBA,一般每个像素各个分量一般都是在一起的。而YUV则有更丰富的分布方式。
- packed,YUV数据放在一起,类似于RGBA。(默认类型)
- planar,YUV三个分量的数据在一起,对应于三个planar,也就是所有Y数据放在一起,所有的U数据在一起,所有的V数据在一起。
- semi-planar,这种方式的特点之处就在于Y数据在一起,UV数据交错放置在一起。
- tiled(vs. rastered), rastered就是光栅也就是数据按照每个像素进行划分,tiled则将数据首先按照44或者66的块进行组织,然后每个块,则进一步分布。
这部分信息量有点大。还需要更多图演示一下。
以YUV422为例,packed的方式数据如下分布:
+--+--+--+--+--+--+--+--+--+--+--+--
|U0|Y0|V0|Y1|U2|Y2|V2|Y3| | | |
+--+--+--+--+--+--+--+--+--+--+--+--
注意,这里因为YUV422水平方向UV采样率只有2:1,因此UV分量只有偶数的。而packed中,还有分布顺序的区别,上面示意图中分布顺序为UYVY,显然还可以分布为VYUY,以及其他方式。
planar的方式呢,则比较简单,仍然考虑为YUV422 planar的情况
+-------------------------------+
+ |
+ Y |
+ |
+----------------+--------------+
+ +
+ U +
+ +
+----------------+
+ +
+ V +
+ +
+----------------+
这里要注意,UV的每行长度只有Y的一般,因为2:1采样的缘故。但是二者的行数是完全一样的。
那么YUV422 semi-planar呢,
+-------------------------------+
+ |
+ Y |
+ |
+-------------------------------+
+U0|V0|U2|V2| |
+ UV |
+ |
+-------------------------------+
UV分量联合起来,UV数据的分布大小刚刚好和Y是一样的。其中UV也同样有顺序的问题。
YUV420也有类似的区分,planar或者semi-planar,其中YUV420 semi-planar的情况下,UV的stride和Y相等,但是UV的高是Y部分高的一半。
+-------------------------------+
+ |
+ Y |
+ |
+-------------------------------+
+U0|V0|U2|V2| UV |
+ |
+-------------------------------+
Tiled的情况,没有那么常见,暂时不谈,以后更新。如果采用了tiled方式,但是格式配置错误的话,可能有块状的马赛克出现。
好像也没有那么复杂吧,还有最后一点,这点经常容易出现问题。
alignment/stride
在计算机处理的时候,对数据的对齐alignment往往有着特殊的要求,例如对齐到8字节或者64字节,当图像的宽度不符合这个对齐值的时候,就需要每行额外多一些,来达到对齐值的整数倍。
因此在水平方向上就有stride和width的区别,stride表示对齐之后的宽度,width则仅仅表示有效数据的宽度。
类似的,在垂直方向上,可能也有对齐的问题。这里记为hstride,height。
同样的,对于变分辨率的视频(adaptive playback会遇到),可能还会碰到over allocate和crop的问题。也就是可能申请一块1920x1080的内存,但是这时候视频流的内容分辨率仅仅有640x480,那么这个时候数据要按照stride为1920,hstride为1080,来进行填充,这样可以直接从11920x1080的内存区域中直接从左上角crop出640x480的数据来。
width x height的数据是可用的,但是其他地方则有可能为全零数据,或者任意的值,或者上一次写入的数据。
如果stride值不对,那么图像会有斜条纹状的马赛克,这里的斜条纹是因为stride’和stride的差,在每行上都会累积的结果。
对于YUV420SP,如果UV数据的起始地址不对,那么UV分量上则会有有问题。这时可以仅仅查看Y分量来检查问题。如果错误的UV分量位置上刚好是零,则图像会有一层绿色。
之后碰到其他问题,再补充。
网友评论