0%

【视频编码】一文搞懂YUV

yuv 格式详解,已经 yuv 与 rgb 格式间的转换。

RGB vs YUV

RGB颜色编码是使用红色(Red)、绿色(Green)和蓝色(Blue)三原色以不同比例混合,用来表示其他各种颜色。其中,每种颜色占8bit,一个像素点占24bit,也就是3byte。

人体生命科学研究表明,人眼对于明暗的感知要比对色彩的感知更加敏感。

因此,可以在颜色编码中将亮度色度分离开,再将部分色度息忽略,进而压缩图像,这就是YUV颜色编码。其中,Y表示亮度,U、V表示色度。

由于图像在采集后和显示时均使用RGB模型表示,而为了方便传输,对图像进行压缩传输时需使用YUV模型表示。这就需要图像在采集后由RGB模型转换为YUV模型,传输到显示端后(假设需要传输),再由YUV模型转换为RGB模型。二者相互转换的公式如下所示:

YUV采样格式

  1. YUV4:4:4采样

    假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

    那么采样的码流为:Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3

    最后映射出的像素点依旧为 [Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

  2. YUV4:2:2采样

    假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

    那么采样的码流为:Y0 U0 Y1 V1 Y2 U2 Y3 V3

    其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一个采集一个。

    最后映射出的像素点为 [Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]

  3. YUV4:2:0采样

    假设图像像素为:

    [Y0 U0 V0]、[Y1 U1 V1]、 [Y2 U2 V2]、 [Y3 U3 V3]

    [Y5 U5 V5]、[Y6 U6 V6]、 [Y7 U7 V7] 、[Y8 U8 V8]

    那么采样的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8

    其中,每采样过一个像素点,都会采样其 Y 分量,而 U、V 分量就会间隔一行按照 2 : 1 进行采样。

    最后映射出的像素点为:

    [Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]

    [Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]

YUV存储格式

YUV 的存储格式有三种:

  • planar 三平面格式

    • 指先连续存储所有像素点的 Y 分量,然后存储 U 分量,最后是 V 分量。
  • semi-planar 两平面格式

    • 先存储所有的 Y 分量,然后交替存储 U、V 分量。
  • packed 打包模式

    • 指每个像素点的 Y、U、V 分量是连续交替存储的。

基于YUV4:2:2的存储格式

  1. YUYV格式packed 打包模式

    每两个像素点使用 4 个分量存储,排列顺序如下:

    Y0 U0 Y1 V1 ···

  2. UYVY格式packet 打包模式

    每两个像素点使用 4 个分量存储,排列顺序如下:

    U0 Y0 V1 Y1 ···

  3. YUV422p格式planar 三平面格式

    先存储所有的 Y 分量,再存储所有的 U 分量,再存储所有的 V 分量。

基于YUV4:2:0的存储格式

  1. YUV420p格式planar 三平面格式

    先存储所有的 Y 分量,再存储所有的 U,V 分量,根据存储 U、V 分量的先后顺序分为 YU12格式(又名I420格式) 和 YV12格式

  2. YUV420sp格式semi-planar 两平面格式

    先存储所有的 Y 分量,再交替存储 U,V 分量,存储 U、V 分量时,使用UVUVUV···方式存储的格式被称为NV12格式,使用VUVUVU···方式存储的格式被称为NV21格式

更加全面的存储格式 ,见表格总结:

采样形式 像素组织形式 格式名称 第一平面 第二平面 第三平面
YUV4:4:4 Planar I444 YYYYYY… UUUUU… VVVVVV…
YUV4:2:2 YUYV
YUV4:2:2 UYVY
YUV4:2:2 YUV422p
YUV4:2:0 YU12 / I420
YUV4:2:0 YV12
YUV4:2:0 NV12
YUV4:2:0 NV21

YUV像素数据处理

  • 分离YU12/I420存储格式文件的 Y、U、V 分量

    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
    /**
    * 将 YUV420P(YU12/I420) 文件中的Y、U、V分量分离出来
    */
    void yuv420p_splite (const char *filename, int height, int width, int frames)
    {
    FILE *f1 = fopen(filename, "rb+");
    FILE *f2 = fopen("yuv420_y.y", "wb+");
    FILE *f3 = fopen("yuv420_u.y", "wb+");
    FILE *f4 = fopen("yuv420_v.y", "wb+");

    int size_y = height * width;
    int size_u = height * width / 4;
    int size_v = height * width / 4;
    int size_frame = size_y + size_u + size_v;
    unsigned char *buffer = (unsigned char *)malloc(size_frame);

    if (buffer == NULL)
    return;

    for (int i = 0; i < frames; i++)
    {
    fread(buffer, 1, size_frame, f1);

    fwrite(buffer, 1, size_y, f2);

    fwrite(buffer + size_y, 1, size_u, f3);

    fwrite(buffer + size_y + size_u, 1, size_v, f4);
    }

    free(buffer);
    fclose(f1);
    fclose(f2);
    fclose(f3);
    fclose(f4);
    }
  • 分离I444存储格式文件的 Y、U、V 分量

    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
    /**
    * 将 YUV444P 文件中的 Y、U、V 分量分离出来
    */
    void yuv444p_splite (const char* filename, int height, int width, int frames)
    {
    FILE* f1 = fopen(filename, "rb+");
    FILE* f2 = fopen("yuv444p_y.y", "wb+");
    FILE* f3 = fopen("yuv444p_u.y", "wb+");
    FILE* f4 = fopen("yuv444p_v.y", "wb+");

    int size_y = height * width;
    int size_u = height * width;
    int size_v = height * width;
    int size_f = size_y + size_u + size_v;
    unsigned char* buffer = (unsigned char*)malloc(size_f);

    if (buffer == NULL)
    {
    return;
    }

    for (int i = 0; i < frames; i++)
    {
    fread(buffer, 1, size_f, f1);

    fwrite(buffer, 1, size_y, f2);
    fwrite(buffer + size_y, 1, size_u, f3);
    fwrite(buffer + size_y + size_u, 1, size_v, f4);
    }

    free(buffer);
    fclose(f1);
    fclose(f2);
    fclose(f3);
    fclose(f4);
    }