对于一张图片,对其进行yuv采样存放会有几种格式,常见的有YUV444 、YUV422 、YUV420

这些格式的显著的区别就是对每个像素的信息采样方式的不同

比如如下的一张图片,每个格子代表一个像素

P0 P1 P2 P3
P4 P5 P6 P7
P8 P9 P10 P11
P12 P13 P14 P15

yuv444采样方式进行采样:

采样每个像素的y、u、v分量,这样导致内存中占用的空间是 width * height *3 的大小

Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
Y4 U4 V4 Y5 U5 V5 Y6 U6 V6 Y7 U7 V7
Y8 U8 V8 Y9 U9 V9 Y10 U10 V10 Y11 U11 V11
Y12 U12 V12 Y13 U13 V13 Y14 U14 V14 Y15 U15 V15

如果是YUV422的采样方式:

采样每个像素的y分量,然后每两个像素采样一次u和v分量,于是u和v分量占用的空间大小分别是原来像素数的一半

那么一帧图象被YUV422采样以后占用的总大小就是 width * height * 2

Y0 U0 Y1 V1 Y2 U2 Y3  V3
Y4 U4 Y5 V5 Y6 U6 Y7 V7
Y8 U8 Y9 V9 Y10 U10 Y11 V11
Y12 U12 Y13 V13 Y14 U14 Y15 V15

YUV420的采样方式:

y分量的采样方式与上两种一样,对每个像素进行采样;而u和v分量则变成了每2 * 2个像素取样一次。

比如第一行像素,采样每一个像素的y分量,并每两个像素采样一次u分量,本行不采样v分量。

而下一行像素,采样每一个像素的y分量,并每两个像素采样一次v分量,本行不采样u分量。

于是u和v分量占用的空间大小分别是原来像素数的1/4,占用的总大小就是 width * height * (3 / 2)

Y0 U0 Y1 Y2 U2 Y3
Y4 V4 Y5 Y6 V6 Y7
Y8 U8 Y9 Y10 U10 Y11
Y12 V12 Y13 Y14 V14 Y15

可以看出,对于这三种采样方式,每个像素的亮度信息(y分量)都被完整保存。不同的地方就是对色差信息(uv分量)的取舍。

通过上面三种方式对每个像素进行了信息的采样,接下来要考虑如何存储。

YUV的存储方式也分为打包(packed)格式存储和平面(plane)格式存储两种。

这就导致了和上面三种采样方式组合出了很多种令人痛苦的yuv格式。

我的理解,打包格式是以 像素点 为单位,连续存储每个像素点的yuv分量;

而平面格式是以 分量 为单位,将三个分量分别存储在三个数组中;

特别的,平面格式中还有一种半平面(semi-plane)的储存方式,这种方式的uv分量被交替存储在同一个数组里。

先来回顾一下yuv422采样方式:

Y0 U0 Y1 V1 Y2 U2 Y3  V3
Y4 U4 Y5 V5 Y6 U6 Y7 V7
Y8 U8 Y9 V9 Y10 U10 Y11 V11
Y12 U12 Y13 V13 Y14 U14 Y15 V15

对于YUV422采样,有以下几种常见的存储方式

YUYV格式(packed):

这是一种打包存储YUV422采出数据的方式

Y0 U0 Y1 V1 Y2 U2 Y3 V3
Y4 U4 Y5 V5 Y6 U6 Y7 V7
Y8 U8 Y9 V9 Y10 U10 Y11 V11
Y12 U12 Y13 V13 Y14 U14 Y15 V15

UYVY格式(packed):

与YUYV一样,也是一种打包存储YUV422采出数据的方式,只是分量的顺序不同

U0 Y0 V1 Y1 U2 Y2 V3 Y3
U4 Y4 V5 Y5 U6 Y6 V7 Y7
U8 Y8 V9 Y9 U10 Y10 V11 Y11
U12 Y12 V13 Y13 U14 Y14 V15 Y15

YUV422Pplane)格式

这是针对422采样的平面存储格式

Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
Y8 Y9 Y10 Y11 Y12 Y13 Y14 Y15
U0 U2 U4 U6 U8 U10 U12 U14
V1 V3 V5 V7 V9 V11 V13 V15

如上这种先U后V的存储方式又称为YU16,而先V后U的方式就称为YV16

再来回顾一下yuv420采样方式:

Y0 U0 Y1 Y2 U2 Y3
Y4 V4 Y5 Y6 V6 Y7
Y8 U8 Y9 Y10 U10 Y11
Y12 V12 Y13 Y14 V14 Y15

对于420采样的存储格式主要有:

YUV420P格式:

这是针对420采样的平面存储格式

Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
Y8 Y9 Y10 Y11 Y12 Y13 Y14 Y15
U0 U2 U8 U10        
V4 V6 V12 V14        

如上这种先U后V的存储方式又称为YU12,而先V后U就称为YV12

YUV420SP格式:

这是针对420采样的半平面(semi-planer)存储格式

Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
Y8 Y9 Y10 Y11 Y12 Y13 Y14 Y15
U0 V4 U2 V6 U8 V12 U10 V14

有了上面比较直观的图片解释,可以来尝试几个简单的图片格式转换

void YUV422toYUV420(unsigned char *yuv420, unsigned char *yuv422, int Y_width, int Y_height)
{

    int UV_width_422 = Y_width >> 1;   //422采样的U和V信号在行方向是隔一个采样一次
    int UV_height_422 = Y_height;      //422采样的U和V信号在列方向是全采样的

    int UV_width_420 = Y_width >> 1;   //420采样的U和V信号在行方向是隔一个采样一次
    int UV_height_420 = Y_height >> 1; //420采样的U和V信号在列方向是隔一个采样一次

    int Ylen = Y_width * Y_height;     //有多少像素就有多少个Y分量

    /* YUV422P的存储方式:
       YYYYYYYY
       YYYYYYYY
       UUUUUUUU
       VVVVVVVV
    */
    unsigned char *p_Y422 = yuv422 ;
    unsigned char *p_U422 = p_Y422 + Ylen ;
    unsigned char *p_V422 = p_U422 + Ylen / 2 ;
    
    /* YUV420P的存储方式:
       YYYYYYYY
       YYYYYYYY
       UUUU
       VVVV
    */
    unsigned char *p_Y420 = yuv420 ;
    unsigned char *p_U420 = p_Y420 + Ylen ;
    unsigned char *p_V420 = p_U420 + Ylen / 4;


    //Y分量都是一样的,直接复制
    memcpy(p_Y420, p_Y422, Ylen);


    /* 对于一个4*4的YUV422来说,U分量的采样结果是:
     * U0  X  U2  X
     * U4  X  U6  X
     * U8  X  U10 X
     * U12 X  U14 X
     *
     * 而同样大小的YUV420,U分量的采样结果:
     * U0  X  U2  X
     * X   X  X   X
     * U8  X  U10 X
     * X   X  X   X
     * 
     * 在plane的存储方式下,这两种采样U分量的存放区别是:
     * YUV422P:
     * U0 U2|U4 U6|U8 U10|U12 U14
     * 
     * YUV420P:
     * U0 U2|U8 U10
     * 用|隔开不表示在存储中被隔开,仅表示这是一个UV_width_422宽度
     */
     
     
    int k = 0 ;
    unsigned char u1, u2 , v1, v2 ;
    for ( int i = 0 ; i < UV_height_422 ; i += 2)
    {
        for ( int j = 0 ; j < UV_width_422 ; j++)
        {
            // 取422p相邻行的两个U分量的平均,作为420取样的U分量
            u1 = *(p_U422 + i * UV_width_422 + j);
            u2 = *(p_U422 + (i + 1) * UV_width_422 + j);
            // 取422p相邻行的两个V分量的平均,作为420取样的V分量
            v1 = *(p_V422 + i * UV_width_422 + j);
            v2 = *(p_V422 + (i + 1) * UV_width_422 + j);

            *(p_U420 + k * UV_width_420 + j) = ((u1 + u2) >> 1);
            *(p_V420 + k * UV_width_420 + j) = ((v1 + v2) >> 1);
        }
        k++;
    }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注