YUV444、YUV422、YUV420知识存档
对于一张图片,对其进行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 |
YUV422P(plane)格式
这是针对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++;
}
}