YUV

YUV

YUV是一种颜色编码方方式,通常由彩色摄像机进行取像,然后把取得的彩色图像信号经过分色、分别放大校正后得到RGB,再经过矩阵变换得到亮度信号Y和两个色差信号B-Y(即U、R-Y(即V,最后将亮度和色差三个信号分别编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性就是它的亮度信号Y和色度信号UV是分离的。

相对于RGB颜色空间,设计YUV的目的就是为了编码、传输的方便,减少带宽占用和信息出错。人眼的视觉特点是对亮度更铭感,对位置、色彩相对来说不铭感。在视频编码系统中为了降低带宽,可以保存更多的亮度信息(luma),保存较少的色差信息(chroma)。Y’UV、YUV、YCbCr、YPbPr几个概念其实是一回事儿。由于历史关系,Y’UV、YUV主要是用在彩色电视中,用于模拟信号表示。YCbCr是用在数字视频、图像的压缩和传输,如MPEG、JPEG。今天大家所讲的YUV其实就是指YCbCrY表示亮度(lumaCbCr表示色度(chroma

YUV中“Y”表示明亮度(LuminanceLuma,也就是灰阶值;而“U”和“V” 表示的则是色度(ChrominanceChroma,作用是描述影像色彩及饱和度,用于指定像素的颜色“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起“色度”则定义了颜色的两个方面 ─ 色调与饱和度,分别用CrCb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。

YUV采样

对于一个w宽、h高的像素图,在水平方向,一行有w个像素;在垂直方向,一列有h个像素,整个图形有 w * h 个像素。我们把这个像素叫做图形像素。如果用YCbCr像素格式来表示像素图,那么要搞清楚亮度和图形像素的关系,色度和图形像素的关系。

yuv 数字解释

如上图中所示,左侧一列,每一个小矩形是图形像素表示,黑框矩形是色度像素表示,小黑点是表示色度像素值(Cb+Cr),表示图形像素和色度像素在水平和垂直方向的比例关系。比如,

  • 4:4:0水平方向是1/1,垂直方向是1/2,表示一个色度像素对应了两个图形像素。
  • 4:2:2水平方向是1/2,垂直方向是1/1,表示一个色度像素对应了两个图形像素。
  • 4:2:0水平方向是1/2,垂直方向是1/2,表示一个色度像素对应了四个图形像素。

右侧一列是二次采样模式记号表示,J:a:b模式,实心黑色圆圈表示包含色度像素(Cb+Cr,空心圆圈表示不包含色度像素。对于J:a:b模式,主要是围绕参考块的概念定义的,这个参考块是一个J x 2的矩形,J通常是4。这样,此参考块就是宽度有4个像素、高度有2个像素的矩形。a表示参考块的第一行包含的色度像素样本数,b表示在参考块的第二行包含的色度像素样本数。

  • 4:4:0参考块第一行包含四个色度样本,第二行没有包含色度样本。
  • 4:2:2参考块第一行包含两个色度样本,第二行也包含两个色度样本,他们是交替出现。
  • 4:2:0参考块第一行包含两个色度样本,第二行没有包含色度样本。

现在我们发现yuv444,yuv422,yuv420 yuv等像素格式的本质是:每个图形像素都会包含亮度值,但是某几个图形像素会共用一个色度值,这个比例关系就是通过4 x 2的矩形参考块来定的。这样很容易理解类似yuv440yuv420这样的格式了。

  • 4:4:4表示没有对色度通道进行缩减采样。
  • 4:2:2意味着21的水平缩减采样,没有垂直下采样。每扫描一行,每两个UV采样包含四个Y采样。
  • 4:2:0表示21水平缩减采样,2:1垂直缩减采样。
  • 4:1:1表示41水平缩减采样,没有垂直下采样。每个扫描线对于每个UV采样包含四个Y采样。4:1:1采样比其他格式少见。

换句话说来说,可以理解为:

  • YUV 4:4:4采样,每一个Y对应一组UV分量。
  • YUV 4:2:2采样,每两个Y共用一组UV分量。
  • YUV 4:2:0采样,每四个Y共用一组UV分量。

下面三个图分别是各自的采集方式:

YUV 采集方式

YUV存储

下面的图给出了常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法,其中,Cb、Cr的含义等同于U、V。

YUVY格式(属于YUV422

img

YUYVYUV422采样的存储格式中的一种,相邻的两个Y共用其相邻的两个Cb、Cr,分析,对于像素点Y'00Y'01而言,其CbCr的值均为Cb00、Cr00,其他的像素点的YUV取值依次类推。

UYVY格式(属于YUV422

img

UYVY格式也是YUV422采样的存储格式中的一种,只不过与YUYV不同的是UV的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。

YUV422P(属于YUV422

img

YUV422P也属于YUV422的一种,它是一种Plane模式,即打包模式,并不是将YUV数据交错存储,而是先存放所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量,如上图所示。其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y'00Y'01而言,其CbCr的值均为Cb00、Cr00。

平面格式是指用三个不同的数组来表示YCbCr的三个Component,每一个Component都是通过不同的平面表示。为此,每一个Component会对应一个planeyuv420p也叫i420就是yuv420 planar表示。yuv420p一共有三个平面分别是Y,U,V,每一个平面都是用8 bit二进制数字表示,我们把8 bit称作位深度。

如果用yuv420p来表示分辨率为 1280 * 720 的图片,需要占用多少存储空间呢?

  • 每一个像素都需要一个luma值,即y。那么总共需要 1280 * 720 = 921600 bytes
  • 每四个像素需要一个chroma u值,那么总共需要 1280 * 720 / 4 = 230400 bytes
  • 每四个像素需要一个chroma v值,那么总共需要 1280 * 720 / 4 = 230400 bytes

y、u、v三个plane加起来就是:921600 + 230400 + 230400 = 1382400 bytes。

YV12,YU12格式(属于YUV420

img

NV12、NV21(属于YUV420

img

NV12NV21属于YUV420格式,是一种two-plane模式,即YUV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上一种类似,即Y'00、Y'01、Y'10、Y'11共用Cr00、Cb00

yuv420pyuv420的区别

二者在存储格式上有区别:

  • yuv420p:yyyyyyyy uuuuuuuu vvvvv
  • yuv420:yuv yuv yuv

yuv420P,Y,U,V三个分量都是平面格式,分为I420YV12I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV;但YV12则是相反(即:YVUYUV420SP, Y分量平面格式,UV打包格式,NV12NV12NV21类似,UV交错排列,不同在于UV顺序。

I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP

YUV格式与RGB格式的换算

  • RGB转换成YUV
Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
Cr = V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
Cb = U = -( 0.148 * R) - (0.291 * G) + (0.439 * B) + 128
  • YUV转换成RGB
B = 1.164(Y - 16) + 2.018(U - 128)
G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
R = 1.164(Y - 16) + 1.596(V - 128)

RGB取值范围均为0255,Y=0255,U=-122+122,V=-157+157,简化后的换算公式

  • RGBYUV
Y = 0.299R + 0.587G + 0.114B
U'= (BY)*0.565
V'= (RY)*0.713
  • YUVRGB
R = Y + 1.403V'
G = Y - 0.344U' - 0.714V'
B = Y + 1.770U'

压缩格式(Packed formats)

压缩格式是指用一个数组表示YCbCr,每一个component是交替出现的。

ffmpeg中对yuv420p像素格式大小计算

yuv420p的格式描述在libavutil/pixdesc.c173行。

173 static const AVPixFmtDescriptor av_pix_fmt_descriptors[AV_PIX_FMT_NB] = {
174     [AV_PIX_FMT_YUV420P] = {
175         .name = "yuv420p", // 像素格式名称
176         .nb_components = 3, // 表示有三个 component,也是三个 plane
177         .log2_chroma_w = 1, // 表示色度(chroma) 像素和图形像素的水平比例关系
178         .log2_chroma_h = 1, // 表示色度(chroma) 像素和图形像素的垂直比例关系
179         .comp = {
180             { 0, 1, 0, 0, 8, 0, 7, 1 },        /* Y 平面,step 是 1,位深度是8 bit */
181             { 1, 1, 0, 0, 8, 0, 7, 1 },        /* U 平面,step 是 1,位深度是8 bit */
182             { 2, 1, 0, 0, 8, 0, 7, 1 },        /* V 平面,step 是 1,位深度是8 bit */
183         },
184         .flags = AV_PIX_FMT_FLAG_PLANAR,
185     },

所有的YUV像素格式表示都在av_pix_fmt_descriptors表中完成,我可以把这叫做像素格式描述表。

yuv420p像素格式在水平方向()大小计算在libavutil/imgutils.c54行。

53 static inline
54 int image_get_linesize(int width, int plane,
55                        int max_step, int max_step_comp,
56                        const AVPixFmtDescriptor *desc)
57 {
58     int s, shifted_w, linesize;
59
60     if (!desc)
61         return AVERROR(EINVAL);
62
63     if (width < 0)
64         return AVERROR(EINVAL);
    // max_step_comp 的取值:0:y,1:u,2:v。对于 y 平面,每一个图形像素需要一个亮度值,
    // 所以这里比例因子是 0;对于 u、v 平面来说,色度像素和图形像素在水平和垂直方向都是 2/1 的关系,
    // 所以计算行的时候,比例因子取像素格式描述表中的 log2_chroma_w。对于 yuv420p 来说,取值是 1,
    // 因为是通过移位运算完成的,右移 1 位,相当于是除以 2。
65     s = (max_step_comp == 1 || max_step_comp == 2) ? desc->log2_chroma_w : 0;
66     shifted_w = ((width + (1 << s) - 1)) >> s;
67     if (shifted_w && max_step > INT_MAX / shifted_w)
68         return AVERROR(EINVAL);
69     linesize = max_step * shifted_w;
70
    // 如果像素描述表中的单位是 bit,那么这里转换成 bytes,右移 3 位,就是除以 8。
71     if (desc->flags & AV_PIX_FMT_FLAG_BITSTREAM)
72         linesize = (linesize + 7) >> 3;
73     return linesize;
74 }

yuv420p像素格式在垂直方向()大小计算在libavutil/imgutils.c111行。

111 int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,
112                            uint8_t *ptr, const int linesizes[4])
113 {
114     int i, total_size, size[4] = { 0 }, has_plane[4] = { 0 };
115
116     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
117     memset(data    , 0, sizeof(data[0])*4);
118
119     if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
120         return AVERROR(EINVAL);
121
122     data[0] = ptr;
123     if (linesizes[0] > (INT_MAX - 1024) / height)
124         return AVERROR(EINVAL);
125     size[0] = linesizes[0] * height;
126
127     if (desc->flags & AV_PIX_FMT_FLAG_PAL ||
128         desc->flags & FF_PSEUDOPAL) {
129         data[1] = ptr + size[0]; /* palette is stored here as 256 32 bits words */
130         return size[0] + 256 * 4;
131     }
132
133     for (i = 0; i < 4; i++)
134         has_plane[desc->comp[i].plane] = 1;
135
136     total_size = size[0];
137     for (i = 1; i < 4 && has_plane[i]; i++) {
        // i 的取值:0:y,1:u,2:v。对于 y 平面,每一个图形像素需要一个亮度值,
        // 所以这里比例因子是 0;对于 u、v 平面来说,色度像素和图形像素在水平和垂直方向都是 2/1 的关系,
        // 所以计算列的时候,比例因子取像素格式描述表中的 log2_chroma_h。对于 yuv420p 来说,取值是 1,
        // 因为是通过移位运算完成的,右移 1 位,相当于是除以 2。
138         int h, s = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
139         data[i] = data[i-1] + size[i-1];
140         h = (height + (1 << s) - 1) >> s;
141         if (linesizes[i] > INT_MAX / h)
142             return AVERROR(EINVAL);
        // 每一平面的行和列做乘法,就是像素总数。
143         size[i] = h * linesizes[i];
144         if (total_size > INT_MAX - size[i])
145             return AVERROR(EINVAL);
        // 每一个平面的像素数相加,就是图片占用的像素总数。
146         total_size += size[i];
147     }
148
149     return total_size;
150 }