博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

YUV

Posted on 2017-05-04 17:53  bw_0927  阅读(794)  评论(0)    收藏  举报

 

http://www.360doc.com/content/16/0517/16/496343_559909505.shtml

http://blog.csdn.net/luotuo44/article/details/26402273

http://ticktick.blog.51cto.com/823160/555791

 

做视频采集与处理,自然少不了要学会分析YUV数据。因为从采集的角度来说,一般的视频采集芯片输出的码流一般都是YUV数据流的形式,而从视频处理(例如H.264、MPEG视频编解码)的角度来说,也是在原始YUV码流进行编码和解析.

 

 

如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白灰度图像。因此用YUV格式由彩色转黑白信号相当简单. 

在技术文档里,YUV经常有另外的名字, YCbCr ,其中Y与YUV 中的Y含义一致,Cb , Cr 同样都指色彩,,只是在表示方法上不同而已,Cb Cr 就是本来理论上的“分量/色差”的标识。C代表分量(是component的缩写),Cr、Cb分别对应r(红)、b(蓝)分量信号,Y除了g(绿)分量信 号,还叠加了亮度信号。
 
还有一种格式是YPbPr格式,它与YCbPr格式的区别在于,其中YCbCr是隔行信号,YPbPr是逐行信号
数字信号都是YCbCr ,其应用领域很广泛,JPEG、MPEG均采用此格式。在后文中,如无特别指明,讲的YUV都是指YCbCr格式。
 
而YPbPr一般是模拟信号
 
 
与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽
 
YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0
 

用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

 

 

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

  1. YUV 4:4:4采样,每一个Y对应一组UV分量。
  2. YUV 4:2:2采样,每两个Y共用一组UV分量。 
  3. YUV 4:2:0采样,每四个Y共用一组UV分量。 

 

2.  存储方式

    下面我用图的形式给出常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法,其中,Cb、Cr的含义等同于U、V。

(1) YUVY 格式 (属于YUV422)

 

YUYV为YUV422采样的存储格式中的一种,相邻的两个Y共用其相邻的两个Cb、Cr,分析,对于像素点Y'00、Y'01 而言,其Cb、Cr的值均为 Cb00、Cr00,其他的像素点的YUV取值依次类推。 
(2) UYVY 格式 (属于YUV422)

 

UYVY格式也是YUV422采样的存储格式中的一种,只不过与YUYV不同的是UV的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。
(3) YUV422P(属于YUV422)
 
(4)YV12,YU12格式(属于YUV420)

 


YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。注意,上图中,Y'00、Y'01、Y'10、Y'11共用Cr00、Cb00,其他依次类推。

 

(5)NV12、NV21(属于YUV420)

 

NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上一种类似,即Y'00、Y'01、Y'10、Y'11共用Cr00、Cb00

YUV420 planar数据, 以720×488大小图象YUV420 planar为例,

其存储格式是: 共大小为(720×480×3>>1)字节,

分为三个部分:Y,U和V

Y分量:    (720×480)个字节  

U(Cb)分量:(720×480>>2)个字节

V(Cr)分量:(720×480>>2)个字节

三个部分内部均是行优先存储,三个部分之间是Y,U,V 顺序存储。

即YUV数据的0--720×480字节是Y分量值,         

720×480--720×480×5/4字节是U分量    

720×480×5/4 --720×480×3/2字节是V分量。

 

4 :2: 2 和4:2:0 转换:

最简单的方式:

YUV4:2:2 ---> YUV4:2:0  Y不变,将U和V信号值在行(垂直方向)在进行一次隔行抽样。 YUV4:2:0 ---> YUV4:2:2  Y不变,将U和V信号值的每一行分别拷贝一份形成连续两行数据。

 

YV12和I420的区别 

一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Bit,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Bit。      

在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)

但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:

YV12 : 亮度(行×列) + U(行×列/4) + V(行×列/4)

I420 : 亮度(行×列) + V(行×列/4) + U(行×列/4)

可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。

 

继续我们的话题,经过第一次数据压缩后RGB24->YUV(I420)。这样,数据量将减少一半,为什么呢?呵呵,这个就太基础了,我就不多写了。同样,如果是RGB24->YUV(YV12),也是减少一半。

但是,虽然都是一半,如果是YV12的话效果就有很大损失。然后,经过X264编码后,数据量将大大减少。将编码后的数据打包,通过RTP实时传送。

到达目的地后,将数据取出,进行解码。完成解码后,数据仍然是YUV格式的,所以,还需要一次转换,这样windows的驱动才可以处理,就是YUV2RGB24。

YUY2  是 4:2:2  [Y0 U0 Y1 V0]

yuv420p  (planar)和 YUV420的区别 在存储格式上有区别
yuv420p:yyyyyyyy uuuuuuuu vvvvv
yuv420: yuv yuv yuv

 

YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。
YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。
I420: YYYYYYYY UU VV    =>YUV420P
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

 

 

============

http://blog.csdn.net/panda1234lee/article/details/48782577

 

2.视频YUV格式简介

 

YUV格式,一般用Y,U,V三者的比率来表示不同格式,比如YUV444 表示三者是比值此是 4:4:4,即Y,U,V的空间都是一样大小。目前主要有如下比例,注意所有格式中Y比值都是4,占一个字节,表示没有减少采样。不同格式中,减小只是UV的采样值.

 

4:4:4:表示色度值(UV)没有减少采样。即Y,U,V各占一个字节,总共占3字节。 

4:2:2:表示UV分量采样减半,比如第一个像素采样Y,U,第二个像素采样Y,V,依次类推,这样每个点占用2个字节。二个像素组成一个宏像素.

4:2:0:这种采样并不意味着只有Y、Cb,而没有Cr分量,这里的0说的U,V分量隔行才采样一次比如第一行采样 4:2:0,第二行采样 4:0:2 ,依次类推……在这种采样方式下,平均每一个像素占用1.5字节.

 

4:2:2示例

如果原始数据三个像素是 [Y0 U0 V0] ,[Y1 U1 V1],[Y2 U2 V2],[Y3 U3 V3]

经过4:2:2采样后,数据变成了 Y0 U0 ,Y1 V1 ,Y2 U2,Y3 V3

如果还原后,因为某一些数据丢失就补成 [Y0 U0 V1],[Y1 U0 V1],[Y2 U2 V3] ,[Y3 U3 V2]

 

4:2:0示例

下面八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] [Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]

存放的码流为:  Y0 U0, Y1, Y2 U2, Y3,Y5 V5, Y6,Y7 V7, Y8

映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7] [Y5 U0 V5] [Y6 U0 V5] [Y7 U2 V7] [Y8 U2 V7]

 

除了4:4:4采样,其余采样后信号重新还原显示后,会丢失部分UV数据,只能用相临的数据补齐,但人眼对UV不敏感,因此总体感觉损失不大。

 

 

图1 YUV420sp存储格式

 

图2 YUV420p存储格式

ps: ffmpeg中,图像原始数据包括两种:planar和packed。planar就是将几个分量分开存,比如YUV420中,data[0]专门存Y,data[1]专门存U,data[2]专门存V。而packed则是打包存,所有数据都存在data[0]中.

3.YUV格式示例【顺时针旋转90°YUV420图像】:

[java] view plain copy
 
  1. public static void rotateYUV420(byte[] src,byte[] des,int width,int height, String type){  
  2. // 原图按行序存储  
  3.         int wh = width * height;  
  4.           
  5.         int k = 0;  
  6.         //旋转Y分量(按原图的列序存储)  
  7.         for(int i=0;i<width;i++) {  
  8.             for(int j=0;j<height;j++) {  
  9.                   des[k] = src[width*j + i];              
  10.                   k++;  
  11.             }  
  12.         }  
  13.   
  14. if(type.equals(“YUV420SP”)){  
  15. // YUV420SP是UV分量交错依次存储的情况  
  16.         // 旋转UV分量(按原图的列序存储)  
  17.         for(int i=0;i<width;i+=2) {  
  18.             for(int j=0;j<height/2;j++) {      
  19.                   des[k] = src[wh+ width*j + i];      
  20.                   des[k+1]=src[wh + width*j + i+1];  
  21.                   k+=2;  
  22.             }  
  23.         }  
  24.   
  25. }  
  26. else if(type.equals(“YUV420P”)){  
  27. // YUV420P是UV分量分开存储的情况  
  28. // 旋转U分量(按原图的列序存储)  
  29.         for(int i=0;i<width;i++) {  
  30.             for(int j=0;j<height/4;j++) {      
  31.                   des[k] = src[wh + width*j + i];     
  32.                   k++;  
  33.             }  
  34.         }  
  35.   
  36. // 旋转V分量(按原图的列序存储)  
  37.         for(int i=0;i<width;i++) {  
  38.             for(int j=0;j<height/4;j++) {      
  39.                   des[k] = src[(1+1/4.0f)*wh + width*j + i];      
  40.                   k++;  
  41.             }  
  42.           }  
  43. }  
  44.     }  
 
ps: ffmpeg库的接口都是c函数,在cpp文件里调用ffmpeg函数要注意了,由于C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同(mangled name),一个用C写成的库如果想被C/C++同时可以使用,那在头文件应该加上以下代码

#ifdef __cplusplus
extern "C" {
#endif 

...
#ifdef __cplusplus
}
#endif

 

 

=======================

http://blog.csdn.net/luotuo44/article/details/26402273

RGB格式: 用红绿蓝表示各种颜色,并且R、G、B一般用一个字节来存储

YUV中的Y、U、V三个分量分别表示明亮度、色度、浓度,每一个分量也是用一个字节来存放的(只有Y分量就是黑白画面)

http://www.cnblogs.com/my_life/articles/6756167.html

http://ticktick.blog.51cto.com/823160/d-15

 

YUV和RGB两者相互转换的公式:

         

 

         

 

        从公式中可以看到,两者进行转换的时候,可能会发生溢出。这个在计算的时候需要注意。处理的方法是截断。比如大于255的,就将之设255.小于0的,其值为0.

 YUV格式是有很多种的。比较常见的有YUV4:4:4、YUV4:2:0、YUV4:2:2。后两种又可以细分成几种的,比如YUV422有YUYV422、UYVY422、YUV422P等。晕了吧~~

        其实只需记得:

  1. YUV 4:4:4采样,每一个Y对应一组UV分量。 
  2. YUV 4:2:2采样,每两个Y共用一组UV分量。 
  3. YUV 4:2:0采样,每四个Y共用一组UV分量。

        维基百科关于他们的布局说明:

  • 4:4:4表示完全取样。
  • 4:2:2表示2:1的水平取样,垂直完全采样。
  • 4:2:0表示2:1的水平取样,2:1垂直采样。
  • 4:1:1表示4:1的水平取样,垂直完全采样。

        布局示意图可以参考链接1链接2

 

上面的只是逻辑布局,但作为码农更想知道他们的物理布局(存储方式),因为要读取数据。

这要先了解两个概念:planar和packed。

在RGB格式中,假如有3个像素点,那么既可以这样存:RGBRGBRGB,也可以RRRGGGBBB。前面一种称为packed(打包模式),后面一种称为planner(平行模式)。同样对应YUV也是有这两种模式的

 

对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。对于packed的YUV格式,每个像素点的Y,U,V是连续交叉存储的。

        在FFmpeg中,已经定义了这两种模式。可以查看pixfmt.h文件。每一种图像格式,都会对应的说明是哪种模式的。

 

        (1)YUYV422:  packed模式   (422:每两个Y共用一组UV分量;  YUYV:按照此顺序进行存储,两个不同的Y公共一组相同的UV, Y00 U00 Y01 V00, Y02 U01 Y03 V01)

        

 

 

        (2)UYVY422:  packed模式(只是排列方式和前面的不同)

        

 

        (3)YUV422P: planner模式

        

 

        (4)YUV420P: planner模式

        

 

 

这里还有一个问题要注意:对于一个width * height的YUV图像,占有多大的字节。这关乎到读取一个YUV文件时,要读多少字节才能把一张图像完全读进内存。对于不同的YUV格式,有不同的大小。

无论RGB还是YUV,都是每个分量占用一个字节(Y分量一个自己,U分量一个字节,V分量一个字节)

//YUV格式中有一个很重要的等量关系,那就是有多少个像素就有多少个y  

 

 

对于YUV422来说,是两个y共享一对u和v。所以y0  u y1  v(YUYV422)4个字节存储的内存代表了两个像素(y0, u, v) 和(y1, u, v)。所以,4比2的关系,所以一个像素会占两个字节,所以对应YUV422来说要width * height * 2个字节。

在FFmpeg中,有一个函数,可以直接算这个大小的,函数的输入参数就是图像的格式、图像的width和height。下一篇博文会说到这个函数。

 

好了,扯了这么多,要写些代码才能满足码农。

这个例子先在YUV文件中读取一个图像,然后转换成RGB24格式,最后用OpenCV播放图像,可以达到视频的效果。

 

 

#include<stdio.h>
#include<highgui.h> //for OpenCV

//转换函数
void YUV2RGB(int y, int u, int v, int* r, int* g, int* b)
{
    assert( r != NULL && g != NULL && b != NULL);

    *r = y + 1.13983 * (v - 128);
    *g = y - 0.39465 * (u - 128) - 0.58060 * (v - 128);
    *b = y + 2.03211 * (u - 128);

    *r = *r > 255 ? 255 : *r;
    *r = *r < 0   ? 0   : *r;

    *g = *g > 255 ? 255 : *g;
    *g = *g < 0   ? 0   : *g;

    *b = *b > 255 ? 255 : *b;
    *b = *b < 0   ? 0   : *b;
}


//把RGB数据填充到OpenCV的IplImage结构体成员imageData中
//imageData是一个数组,其用来存放每一个像素点的BGR。
//其排列的形式很简单(BGR)(BGR)(BGR)(BGR)(BGR)
void fillImage(IplImage* pimg, int r, int g, int b)
{
    static int h = 0, w = 0;

    //这里是BGR的顺序。因为OpenCV存放像素的顺序的BGRBGRBGR
    pimg->imageData[h*pimg->widthStep + w++] = b;
    pimg->imageData[h*pimg->widthStep + w++] = g;
    pimg->imageData[h*pimg->widthStep + w++] = r;

    //这部分代码是和OpenCV的一些性质有关,如果看不懂,可以忽略。
    //上面的pimg->widthStep也是与OpenCV的性质有关
    if( w/3 >= pimg->width )
    {
        w = 0;
        if( h == pimg->height - 1  )
            h = 0;
        else
            ++h;
    }
}


void convertImage(IplImage* pimg, unsigned char* yuv_buff, int len)
{
    int i;
    int r, g, b;
    int y0, y1, u, v;

    for(i = 0; i < len; i += 4)
    {
        //其排列方式是y0 u y1 v
        //直接提取出来y、u、v三个分量,然后使用公式转成RGB即可
        //因为两个y共享一对uv。故y0 u y1 v能提取出两组(y, u, v)
        y0 = yuv_buff[i + 0];
        u  = yuv_buff[i + 1];
        y1 = yuv_buff[i + 2];
        v  = yuv_buff[i + 3];

        YUV2RGB(y0, u, v, &r, &g, &b);
        //将RGB分量填充到OpenCV的IplImage中
        fillImage(pimg, r, g, b);

        YUV2RGB(y1, u, v, &r, &g, &b);
        fillImage(pimg, r, g, b);
    }
}


int main(int argc, char** argv)
{
    const char* filename = argc > 1 ? argv[1] : "waterfall_yuyv422.yuv";

    FILE* fin = fopen(filename, "rb");
    if( fin == NULL )
    {
        printf("can't open the file\n");
        return -1;
    }

    int width = 352;
    int height = 288;

    //一张完整的图像 对应在 yuv文件中 占据的字节数
    //因为是yuyv格式的yuv,所以其排列方式是y0 u y1 v y2 u v y3
    //因为是两个y共享一对u和v。所以y0 u y1 v代表两个像素(y0, u, v)
    //和(y1, u, v),对应地会有两个RGB像素。
    //也就是说y0 u y1 v这4个字节的内容等于2个像素, 2比1的关系。
    //所以有width * height个像素,就应该要width * height * 2个字节
    //这个关系是yuv422特有的。对于yuv444和yuv420会有不同的比例关系
    int frame_size = width * height * 2;


    unsigned char* buff = new unsigned char[frame_size];

    IplImage* pimg = cvCreateImage(cvSize(width, height),
                                   IPL_DEPTH_8U, 3);
    cvNamedWindow("1.jpg");

    //这里用图像做成一个视频播放器
    while( 1 )
    {
        int ret = fread(buff, 1, frame_size, fin);
        if( ret != frame_size )
        {
            break;
        }

        convertImage(pimg, buff, frame_size);
        cvShowImage("1.jpg", pimg);
        cvWaitKey(33);
    }


    cvReleaseImage(&pimg);
    cvDestroyWindow("1.jpg");

    delete [] buff;

    return 0;
}