FFmpeg基础: YUV像素格式介绍和使用

YUV像素格式

在图片中我们一般都是通过RGB(红-绿-蓝)格式来表示一个像素点。而在视频当中为了兼容黑白和彩色视频我们通过YUV来表示一个像素点,YUV中Y表示像素点的灰度(也就是亮度分量),而UV表示色度分量,U称为蓝色投影也就是像素蓝色部分去掉亮度Y之后的值,V称为红色投影也就是像素红色部分去掉亮度Y的值。

如果只有Y亮度分量,没有UV颜色分量,那么图像就会变成黑白色的图像,由于YUV像素的这种特点,YUV像素格式能同时兼容彩色图像和黑白图像。

YUV采样

和RGB像素格式不同,由于人的眼睛对亮度Y敏感,而对颜色分量UV不敏感,所以我们可以在保证图像效果的同时降低色度分量的采样数据,从而实现对画面数据的压缩。目前流行的YUV采样,基本都是降低色度分量的采集。在所有的YUV格式中,UV的采样数据都是相等的,只是在有的YUV格式中需要Y分量之间共享UV分量。

YUV格式分类

YUV4:4:4
YUV4:4:4格式中每个像素点进行YUV全采样。由于每个分量存储占用一个字节,YUV每个像素深度是24位,也就是3个字节,这和RGB占用空间是相同的。

YUV4:2:2
YUV4:2:2水平方向Y分量与UV分量2:1采样,垂直方向不降低采样率。YUV4:2:2一个像素深度是:1x8 + 0.5x8 + 0.5x8 = 16位,也就是占用2个字节。

YUV4:2:0
YUV4:2:0垂直方向和水平方向上Y分量和UV分量的采样比都是2:1。YUV4:2:0一个像素深度是1x8 + 0.5x8 = 12位,也就是占用1.5个字节。

三种像素格式之间的对比图如下图所示:
在这里插入图片描述

YUV存储模式

YUV存储模式分为打包模式和平面模式,在打包模式中,YUV分量存储在单个数组中,YUV分量是顺序交错存储。在平面模式中,YUV分量存储在三个不同的平面数组中。

YUV4:4:4的打包存储模式如下图所示:
在这里插入图片描述

YUV4:2:2采样格式有两种存储方式分别为YUY2和UYVY。YUV2和UYVY两种数据格式基本相同,都是两个Y分量共用一套UV,不同的是字节顺序发生了颠倒。

两种打包存储格式如下图所示:
在这里插入图片描述

YUV4:2:0都采用了平面存储模式,总共分为四种:IMC2、IMC4、YV12、NV12。所有的4:2:0模式中,色度分量无论是水平还是垂直方向上,采样数都是亮度的1/4.

IMC2
平面存储模式中都是先存储视频帧中所有的Y分量,Y分量存储完毕之后才开始存储色度分量。IMC2格式中,是先存储Y分量,然后再存储V分量、最后存储U分量。
存储UV分量的空间分别Y分量空间的1/4,YUV内存比例为4:1:1。

IMC4
IMC4和IMC2的存储模式相同,差异是IMC4中UV分量的存储顺序发生了颠倒。两种模式的存储对比图如下图所示:
在这里插入图片描述

YV12/YUV420P(I420)
YV12和IMC2及IMC4的存储方式不同,存储色度分量的内存步长是亮度分量的一半。同时YUV420P和YV12的UV存储顺序刚好相反。两种模式存储结构如下图所示:
在这里插入图片描述

NV12
NV12格式先存储Y分量平面,之后UV分量间隔存储,存储的空间结构如下图所示:
在这里插入图片描述

FFmpeg读取YUV数据

由于YUV不同格式的存储结构不同,在使用ffmpeg读取YUV数据的时候读取方式也有差异,这里就介绍一下常见格式的YUV数据的读取方式:

读取YUV420P数据

//分配图像内存
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
    1920,
    1080,
    1);
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//读取YUV420P数据
AVFrame *frame = av_frame_alloc();
av_image_fill_arrays(frame->data,
    frame->linesize,
    out_buffer,
    AV_PIX_FMT_YUV420P,
    1920,
    1080,
    1);
frame->format = AV_PIX_FMT_YUV420P;
frame->width = 1920;
frame->height = 1080;

int width = 1920;
int height = 1080;

uint8_t * file_buffer = (uint8_t *)av_malloc(1920 * 1080 * 3 / 2);

FILE *in_file = fopen("input.yuv", "rb");
int pts = 0;
while (1) 
{
    
    
    //读取YUV420P数据
    if (fread(file_buffer, 1, width * height * 3 / 2, in_file) <= 0) 
    {
    
    
        break;
    }
    else if (feof(in_file)) 
    {
    
    
        break;
    }

    //封装yuv帧数据
    frame->data[0] = file_buffer;                         //Y起始数据索引
    frame->data[1] = file_buffer + width * height;        //U数据起始索引
    frame->data[2] = file_buffer + width * height * 5 / 4;//V起始数据索引
    frame->linesize[0] = width;    //Y数据的行宽
    frame->linesize[1] = width / 2;//U数据的行宽
    frame->linesize[2] = width / 2;//V数据的行宽
}

读取NV12数据

//分配图像内存
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_NV12,
    1920,
    1080,
    1);
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//读取NV12数据
AVFrame *frame = av_frame_alloc();
av_image_fill_arrays(frame->data,
    frame->linesize,
    out_buffer,
    AV_PIX_FMT_NV12,
    1920,
    1080,
    1);
frame->format = AV_PIX_FMT_NV12;
frame->width = 1920;
frame->height = 1080;

uint8_t * file_buffer = (uint8_t *)av_malloc(1920 * 1080 * 3 / 2);

FILE *in_file = fopen("input.nv12", "rb");
int pts = 0;
while (1) 
{
    
    
    //读取NV12的数据
    if (fread(file_buffer, 1, width * height * 3 / 2, in_file) <= 0) 
    {
    
    
        break;
    }
    else if (feof(in_file)) 
    {
    
    
        break;
    }

    frame->data[0] = file_buffer;      //Y的起始位置                
    frame->data[1] = file_buffer + 1920 * 1080; //UV的起始位置

    frame->linesize[0] = 1920;    //Y的行宽
    frame->linesize[1] = 1920;//UV的行宽
}

YUV和RGB转换

YUV转RGB

R = Y + 1.403(V - 128)
G = Y - 0.344(U - 128) - 0.714(V - 128)
B = Y + 1.770(U - 128)

RGB转YUV

Y = 0.2990R + 0.5870G + 0.1140B
U =0.1684R − 0.3316G + 0.5B + 128
V = 0.5R - 0.4187G - 0.0813B + 128

猜你喜欢

转载自blog.csdn.net/yang1fei2/article/details/128436615