常见视频原始数据格式分析 — YUV

1. YUV 简介

YUV 是比较常用的原始视频数据数据格式,视频采集芯片输出的码流大部分都是 YUV 数据流形式,而视频处理(如 H264、H265编码等),也是在原始 YUV 码流进行编码和解析。所以,了解熟悉 YUV 数据流对于做视频领域的人而言,至关重要。

YUV,分为三个分量,Y:表示明亮度(Luminance 或 Luma),也就是灰度值;而 U 和 V :表示的则是色度(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。这样设计分开的主要原因是,人眼对色度的敏感程度要低于对亮度的敏感程度。

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

2. YUV 采样方式

YUV 码流有多种不同的格式,要分析 YUV 码流,就必须搞清楚你面对的到底是哪一种格式,并且必须搞清楚这种格式的 YUV 采样和分布情况。

大分类:packed & planar

YUV 按像素分布规律被分为两大类,packed 打包格式和 plannar 平面格式,

  1. – 将 YUV 分量存放在同一数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel)
  2. – 将 YUV 分量分别存储在三个数组中,先连续存储所有像素点的 Y,紧接着存储所有像素点的 U,随后是所有像素点的 V

小分类:YUV444 & YUV422 & YUV420

目前比较主流的采样方式有:YUV444、YUV422、YUV420,下面会对这三种格式存储方式加以介绍,并说明如何根据其采样格式来从码流中还原每个点的 YUV 值。

常讲的的 YUV A:B:C 的意思一般是指基于 4 个象素来讲,其中 Y 采样了 A 次,U 采样了 B 次,V 采样了 C 次

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

常见视频原始数据格式分析 — YUV_第1张图片

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

YUV 4:4:4 采样,每一个 Y 对应一组 UV 分量,每像素 24 位

YUV 4:2:2 采样,每两个 Y 共用一组 UV 分量,每像素 16 位

YUV 4:2:0 采样,每四个 Y 共用一组 UV 分量,每像素 12 位

3. 存储方式

YUV 的优点之一是基于人眼对色度的敏感程度要低于对亮度的敏感程度,可以适当降低色度的采样率,同时不会明显降低视觉质量。因此,常见的 YUV422、YUV420 都适当降低了色度分量。

下面用图的形式给出常见的 YUV 码流的存储方式,其中,Cb、Cr 的含义等同于 U、V。

1) YUVY 格式(属于 YUV422)

常见视频原始数据格式分析 — YUV_第2张图片

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

2) UYVY 格式 (属于YUV422)

常见视频原始数据格式分析 — YUV_第3张图片

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

3) YUV422P(属于YUV422)

常见视频原始数据格式分析 — YUV_第4张图片

YUV422P 是一种 plannar 模式,即平面模式。与前面其他 YUV422 采访方式不同,它并不是将 YUV 数据交错存储,而是先存放所有的 Y 分量,然后存储所有的 U分量, 最后存储所有的 V 分量。

如上图所示。其每一个像素点的 YUV 值提取方法也是遵循 YUV422 格式的最基本提取方法,即两个 Y 共用一个 UV。比如, 对于像素点 Y’00、Y’01 而言,其 Cb、Cr 的值均为 Cb00、Cr00。

4) YV12,YU12格式(属于YUV420)

常见视频原始数据格式分析 — YUV_第5张图片

YU12 和 YV12 属于 YUV420 采样格 式之一,也是一种 plannar
模式,将 Y、U、V 分量分别打包,依次存储。

其每一个像素点的 YUV 数据提取遵循 YUV420 格式的提取方式,即 4 个 Y 分量共用一 组 UV,即上图中,Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00,其他依次类推。

5) NV12、NV21(属于YUV420)

常见视频原始数据格式分析 — YUV_第6张图片

NV12 和 NV21 属于 YUV420 采样格式之一,也是一种 plannar 模式,但是与 YV12 不同的是,将 Y 和 UV 分为两个 plane,Y 连续存储,但是 UV 交错存储。

其提取方式与上一种类似,即 Y’00、Y’01、Y’10、Y’11 共用 Cr00、Cb00。

这种采样存储方式也被称为 YUV420SP,即 UV 交错存储,作为区分,上文 4) 介绍的存储方式被称为 YUV420P。

下面用一种表说明几种常见存储方式所占用的内存,以 w*h 大小的图像为例:

采样方式 占用
YUV 4:4:4 Y(w * h) + U(w * h) + V(W * h) = 3 * w * h
YUV 4:2:2 Y(w * h) + U(w * h * 1/2) + V(W * h * 1/2) = 2 * w * h
YUV 4:2:0 Y(w * h) + U(w * h * 1/4) + V(W * h * 1/4) = 1.5 * w * h

4. 几种常见 YUV 视频像素处理

4.1 分离 YUV420P 像素数据中的 Y、U、V 分量

由于 YUV420P 各分量的连续存储性质,可以很方便快捷的直接提取出三个分量。

下面的程序将 YUV420P 中的 Y、U、V 三个分量分离出来。代码如下:

/** 
 * Split Y, U, V planes in YUV420P file. 
 * @param url  Location of Input YUV file. 
 * @param w    Width of Input YUV file. 
 * @param h    Height of Input YUV file. 
 * @param num  Number of frames to process. 
 * 
 */  
void simplest_yuv420_split(char *url, int w, int h)
{  
    FILE *fp = fopen(url, "rb+");  
    FILE *fp1 = fopen("output_420_y.y", "wb+");  
    FILE *fp2 = fopen("output_420_u.y", "wb+");  
    FILE *fp3=fopen("output_420_v.y", "wb+");  

    unsigned char *pic = (unsigned char*) malloc(w * h * 3 / 2);  

    fread(pic, 1, w * h * 3 / 2, fp);  
    //Y  
    fwrite(pic, 1, w * h, fp1);  
    //U  
    fwrite(pic + w * h, 1, w * h / 4, fp2);  
    //V  
    fwrite(pic + w * h * 5 / 4, 1, w * h / 4, fp3);  

    free(pic);  
    
    fclose(fp);  
    fclose(fp1);  
    fclose(fp2);  
    fclose(fp3);  
}  

从代码可以看出, 如果视频帧的宽和高分别为 w 和 h,那么一帧 YUV420P 像素数据一共占用 (w * h * 3 / 2) Byte 的数据。其中前 (w * h) Byte存储 Y,接着的 (w * h * 1 / 4) Byte 存储 U,最后 (w * h * 1 / 4) Byte 存储V。

上述调用函数的代码运行后,将会把一张分辨率为 256x256 分辨率 的 YUV420P 格式的像素数据文件分离成为三个文件:

output_420_y.y:纯 Y 数据,分辨率为 256x256 
output_420_u.y:纯 U 数据,分辨率为 128x128 
output_420_v.y:纯 V 数据,分辨率为 128x128

下面是一个运行实例:

输出原图:

常见视频原始数据格式分析 — YUV_第7张图片

程序输出三个分量:

1). output_420_y.y

常见视频原始数据格式分析 — YUV_第8张图片

2). output_420_u.y

常见视频原始数据格式分析 — YUV_第9张图片

3). output_420_v.y

常见视频原始数据格式分析 — YUV_第10张图片

4.2 将 YUV420P 像素数据的亮度减半

本程序中的函数可以通过将 YUV 数据中的亮度分量 Y 的数值减半的方法,降低图像的亮度。函数代码如下所示

/** 
 * Halve Y value of YUV420P file 
 * @param url     Location of Input YUV file. 
 * @param w       Width of Input YUV file. 
 * @param h       Height of Input YUV file. 
 * @param num     Number of frames to process. 
 */  
int simplest_yuv420_halfy(char *url, int w, int h)
{  
    FILE *fp = fopen(url,"rb+");  
    FILE *fp1 = fopen("output_half.yuv", "wb+");  

    unsigned char *pic = (unsigned char*)malloc(w * h * 3 / 2);  

    fread(pic, 1, w * h * 3 / 2, fp);  
    //Half  
    for (int j = 0; j < w * h; j++) {  
        unsigned char temp = pic[j] / 2;  
        pic[j] = temp;  
    }  
    fwrite(pic, 1, w * h * 3 / 2, fp1);  

    free(pic);  
    fclose(fp);  
    fclose(fp1);  

    return 0;  
} 

从代码可以看出,如果打算将图像的亮度减半,只要将图像的每个像素的 Y 值取出来分别进行除以 2 的工作就可以了。

输入原图:

常见视频原始数据格式分析 — YUV_第11张图片

处理后图片:

常见视频原始数据格式分析 — YUV_第12张图片

4.3 将 YUV420SP 图片旋转 90 度

void YUV420spRotate90(uchar *des, uchar *src, int width, int height)
{
    int wh = width * height;
    //旋转Y
    int k = 0;
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
            des[k] = src[width * j + i];
            k++;
        }
    }

    for (int i = 0; i < width; i += 2) {
        for (int j = 0; j < height / 2; j++) {
            des[k] = src[wh+ width * j + i];
            des[k+1] = src[wh + width * j + i + 1];
            k += 2;
        }
    }
}

其他待添加…

你可能感兴趣的:(android,multimedia)