YUV格式的说明

写在最前的碎碎念

YUV格式接触了好多年了,一直没有对它去做一个深入的,系统的认识,最近项目需要,对它有重新认识了一把,顺手把知识点汇总一下,方便之后编写YUV图像的处理函数库.本篇章大部分为摘抄引用,汇总自用.

YUV含义

  • YUV,分为三个分量,
    Y表示明亮度(Luminance或Luma),也就是灰度值
    U和V表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色.
  • YUV :即 YCbCr, 两者是等价的.

    与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽

YUV采样方式

主流的采样方式有三种,

  • YUV4:4:4
  • YUV4:2:2
  • YUV4:2:0

    这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。

    用三个图来直观地表示采集的方式,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

1
先记住下面这段话,以后提取每个像素的YUV分量会用到。

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

YUV封装格式

YUV格式有两大类:planarpacked

  • planar: 先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
  • packed: 每个像素点的Y,U,V是连续交错存储的。

其中,planar格式还分为SEMI PLANARPLANAR

  • semi planar:先连续存储所有的Y, 然后UV交错存储.
  • planar:先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
    以YUV420为例,如下图:

YUV420P

YUV420SP

YUV存储方式

其实存储方式就是采样方式和封装格式的各种组合. 但是这一节的内容,我觉得才是重中之重,因为充分了解了YUV的存储方式,之后对不同YUV格式做读取/写入操作时,才会更加的得心应手.

内存消耗

  1. YUV按照内存消耗量总体上分为YUV420YUV422两种
    • YUV420—–其Y:U:V或者Y:UV或者Y:V:U的总量为4:2:0
    • YUV422—–其Y:U:V比例为4:2:2
  2. RGB内存比例为1:1:1

==一幅图,宽W高H,则显示一副图像所需内存==

  • ==YUV420 = W H (4+2+0) / 4 = W H 3/2 BYTE==
  • ==YUV422 = W H (4+2+2) / 4 = W H 2 BYTE==
  • ==RGB = W H (1+1+1) / 1 = W H 3 BYTE==

内存消耗

另外,以1920*1080的图片具体举例:
1

存储方式

本节内容主要参考自 YUV图解 (YUV444, YUV422, YUV420, YV12, NV12, NV21),然后自己稍微整理了下.
大概看看就好,我觉得不一定是准确的,这是这篇参考性比较好,可以大体指导下YUV的各种格式.

下面我用图的形式给出常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法,
其中,Cb = U, Cr = V

  1. YUVY 格式 (属于YUV422),也称 ++YUYV++ 格式, 或 ++YUY2++ 格式

yuyv

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

  1. UYVY 格式 (属于YUV422)

uyvy

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

  1. YUV422P(属于YUV422)

yuv422p

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

  1. YV12,YU12格式(属于YUV420)

yv12

YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。注意,上图中,Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00,其他依次类推。

  1. NV12、NV21(属于YUV420)

nv12

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

总结

  1. 代码中使用的图片像素格式的枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /* YUV420P */
    YUV_420_PLANAR_I420, //YYYYYYYY UU VV (又简称 420P) - Y(w×h) + U(w×h/4) + V(w×h/4)
    YUV_420_PLANAR_YV12, //YYYYYYYY VV UU - Y(w×h) + V(w×h/4) + U(w×h/4)
    /* YUV420SP */
    YUV_420_SEMIPLANAR_NV12,//YYYYYYYY UV UV - Y(w×h) + UV(w×h/4)
    YUV_420_SEMIPLANAR_NV21,//YYYYYYYY VU VU - Y(w×h) + VU(w×h/4)

    /* YUV422P */
    YUV_422_PLANAR, //YYYYYYYY UUUU VVVV - Y(w×h) + U(w×h/2) + V(w×h/2)
    /* YUV422SP */
    YUV_422_SEMIPLANAR, //YYYYYYYY UVUV UVUV - Y(w×h) + UV(w×h/2)

    /* YUV422 PACKAGE */
    YUV_422_PACKAGE_YUYV, //YUYV YUYV YUYV (又称 YUVY 或 YUY2)
    YUV_422_PACKAGE_UYVY, //UYVY UYVY UYVY
    YUV_422_PACKAGE_VYUY, //VYUY VYUY VYUY

    /* YUV444 */
    YUV_444_PLANAR, //YYYYYYYY UUUUUUUU VVVVVVVV - Y(w×h) + U(w×h) + V(w×h)
    YUV_444_SEMIPLANAR, //YYYYYYYY UV UV UV UV UV UV UV UV - Y(w×h) + UV(w×h)
  2. 表格总结

采样方式 格式 具体名称 说明
YUV420 PLANAR I420 / 420P Y(w×h) + U(w×h/4) + V(w×h/4)
YV12 Y(w×h) + V(w×h/4) + U(w×h/4)
SEMI PLANAR NV12 Y(w×h) + UV(w×h/4)
NV21 Y(w×h) + VU(w×h/4)
YUV422 PLANAR 422P Y(w×h) + U(w×h/2) + V(w×h/2)
SEMI PLANAR 422SP Y(w×h) + UV(w×h/2)
PACKAGE YUYV / YUVY / YUY2 YUYV YUYV YUYV
UYVY UYVY UYVY UYVY
VYUY VYUY VYUY VYUY
YUV444 PLANAR 444P Y(w×h) + U(w×h) + V(w×h)
SEMI PLANAR 444SP Y(w×h) + UV(w×h)

YUV与RGB的色值转换

YUV色域

6
通过坐标图我们可以看到UV并不会包含整个坐标系,而是呈一个旋转了一定角度的八边形.
U越大,蓝色越蓝,V越大,红色越红。

与RGB相互转换公式

这部分内容的话,网上也有很多资料,很多公式,有些经过我测试发现居然是错的….
所以我只归纳以下两种:

  1. RGB 转 YUV

    1
    2
    3
    Y = (( 66  * R + 129 * G + 25 * B + 128) >> 8) + 16;
    U = (( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
    V = (( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
  2. YUV 转 RGB

    1
    2
    3
    R = (Y << 20 + 1475871 * (V - 128)) >> 20
    G = (Y << 20 - 362283 * (U - 128) - 751724 * (V - 128)) >> 20
    B = (Y << 20 + 1865417 * (U - 128)) >> 20

或者使用查表方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int V1475871[256] = {0};
int V751724[256] = {0};
int U362283[256] = {0};
int U1865417[256] = {0};

for (i = 0; i < 256; i++)
{
V1475871[i] = (i - 128) * 1475871;
V751724[i] = (i - 128) * 751724;
U362283[i] = (i - 128) * 362283;
U1865417[i] = (i - 128) * 1865417;
}

/* 使用局部查表法,公式转成如下形式: */
R = (Y << 20 + V1475871[V]) >> 20;
G = (Y << 20 - U362283[U] - V751724[V]) >> 20;
B = (Y << 20 + U1865417[U]) >> 20;

介绍几个搜集到的说明RGB与YUV相互转换关系的网址

  1. yuv转bmp说明 其中YUV转RGB的说明部分:

YUV和BMP都是非压缩格式,不存在解码过程,只需要做像素点到像素点的转换,YUV转bmp公式较多,应用到不同场景,下面是ITU-R BT.601建议在数字视频领域使用的公式(带有伽马校正,图像增强效果):

1
2
3
R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y’-16) + 2.017*(Cb'-128)

效果肯定是最好的,但运算量也是最大的

如果在嵌入式设备上使用,可以使用下面的简化公式:

1
2
3
R = Y + 1.4075 *(V-128)
G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)
B = Y + 1.779 *(U – 128)

在代码中实现该公式时,为提高计算效率,浮点运算需转为整形运算,公式可以转化为如下形式(统一先左移20,再右移20):

1
2
3
R = (Y << 20 + 1475871 * (V - 128)) >> 20
G = (Y << 20 - 362283 * (U - 128) - 751724 * (V - 128)) >> 20
B = (Y << 20 + 1865417 * (U - 128)) >> 20

由于YUV每个分量都使用1个字节来存储(YUV420格式UV分量会重复使用),每个值的取值范围为(0~255),为减少乘法运算,可以把乘法部分用局部查表法实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int V1475871[256] = {0};
int V751724[256] = {0};
int U362283[256] = {0};
int U1865417[256] = {0};

for (i = 0; i < 256; i++)
{
V1475871[i] = (i - 128) * 1475871;
V751724[i] = (i - 128) * 751724;
U362283[i] = (i - 128) * 362283;
U1865417[i] = (i - 128) * 1865417;
}

/* 使用局部查表法,公式转成如下形式: */
R = (Y << 20 + V1475871[V]) >> 20;
G = (Y << 20 - U362283[U] - V751724[V]) >> 20;
B = (Y << 20 + U1865417[U]) >> 20;

  1. YUV与RGB互转各种公式 (YUV与RGB的转换公式有很多种,请注意区别!!!)
  • 值得注意的是,这个网址中的2.整数形式(减少计算量)未量化部分中的RGB转YUV部分的公式,我测试下来,并不正确,应该使用我本节一开始写的那个公式来计算. 至于这个网址中介绍的其他公式,我并没有验证.
  1. 总结各种RGB转YUV的转换公式
  • 介绍了两种不通标准(BT709(高清)BT601(标清国际定义))下的转换公式.

YUV转BMP图 和 BMP图转YUV

这部分内容基本就是上一节内容的延伸了,或者说是使用了.直接丢两篇我参考的博文连接吧,之后我会更新我自己封装的yuv和bmp互相转换的代码的.

  1. BMP 转 YUV (BMP2YUV)
  2. YUV 转 BMP 说明

YUV裁剪

终于到了这次去深入了解YUV的根本原因所在了, 公司项目中需要对YUV图像进行任意指定位置的裁剪….但是和上一节一样,并不打算详细说明,丢几个博文自己体会.因为看完上面对YUV格式的各种介绍,这个裁剪就没有难度了.

  1. YUV420P格式图像处理分割,复制,合并
  2. 基于FFmpeg的YUV多图像拼接方法(附代码)
  3. 对YUV数据进行裁剪
    例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    yuv420p 取整幅图像左上角1/4图像的代码如下

    int y_siz=picref->video->w*picref->video->h;
    int i, j;
    for(i = 0; i < picref->video->h/2; i++)
    fwrite(picref->data[0]+i*picref->video->w, 1, picref->video->w/2, fp_yuv);

    for(i = 0; i < picref->video->h/4; i++)
    fwrite(picref->data[1]+i*picref->video->w/2, 1, picref->video->w/4, fp_yuv);

    for(i = 0; i < picref->video->h/4; i++)
    fwrite(picref->data[2]+i*picref->video->w/2, 1, picref->video->w/4, fp_yuv);

最后在附一段我自己写的裁剪代码的初稿,不过见到测试过也没有问题.
功能是将一副YUV图片裁剪一部分(stCroprect)出来, 再贴到一张固定底图的指定位置(stDstpoint).稍微改改就能变回单纯的裁剪了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
bool  YUVHandler::ImageCropMerge(IMAGE_TYPE  enType,  RECT_Z  stCroprect,  POINT_Z  stDstpoint)
{
/*
* copy width:
* stCroprect.x + stCroprect.width <= basemap_.w ---> stCroprect.width
* stCroprect.x + stCroprect.width > basemap_.w ---> basemap_.w - stCroprect.x
*/
U32 copywidth = ((stDstpoint.x + stCroprect.width) <= basemap_.w) ?
stCroprect.width : basemap_.w - stDstpoint.x;
U32 copyheight = ((stDstpoint.y + stCroprect.height) <= basemap_.h) ?
stCroprect.height : basemap_.h - stDstpoint.y;
U32 scaleratio = 1;

for(U32 h = stDstpoint.y; h < copyheight; h++){
memcpy(basemap_.Y + (stDstpoint.y + h) * basemap_.w + stDstpoint.x,
pendingimage_.Y + (stCroprect.y + h) * pendingimage_.w + stCroprect.x,
copywidth);
}

switch (enType) {
case YUV_420_PLANAR_I420:{
scaleratio = 2;
POINT_Z stUvdstpoint = {stDstpoint.x / scaleratio,
stDstpoint.y / scaleratio};
RECT_Z stUvcroprect = {stCroprect.x / scaleratio,
stCroprect.y / scaleratio,
stCroprect.width / scaleratio,
stCroprect.height / scaleratio};

copywidth /= scaleratio;
copyheight /= scaleratio;

for(U32 h = stUvdstpoint.y; h < copyheight; h++){
memcpy(basemap_.U + (stUvdstpoint.y + h) * basemap_.w / scaleratio + stUvdstpoint.x,
pendingimage_.U + (stUvcroprect.y + h) * pendingimage_.w / scaleratio + stUvcroprect.x,
copywidth);

memcpy(basemap_.V + (stUvdstpoint.y + h) * basemap_.w / scaleratio + stUvdstpoint.x,
pendingimage_.V + (stUvcroprect.y + h) * pendingimage_.w / scaleratio + stUvcroprect.x,
copywidth);
}
break;
}
default:
break;
}

return true;
}

YUV缩放

最后一节就记录下,还没有仔细了解,之后很可能需要去了解的对YUV图像的放大缩小部分吧.
这部分之后打算仔细看下FFMPEG中的sws_scale函数

  1. ffmpeg sws_scale函数详解
  2. FFmpeg缩放swscale详解 <转>
  3. sws_scale函数的用法-具体应用

食用链接

  1. YUV格式初探
  2. YUV格式全解
您的支持将鼓励我继续创作!
0%