• 图像视频编码和FFmpeg(2)-----YUV格式介绍和应用


            本文不讲FFmpeg,而是讲YUV图像格式。因为摄像头拍摄出来的原始图像一般都是YUV格式。在FFmpeg中,视频是通过多张YUV图像而得到。

             YUV图像格式是什么,这个可以看一下维基百科。这个超链接打开即可,无需细看。因为看了也不会懂YUV格式是什么。不信的话,我问你,对于耳熟能详的RGB格式,你懂了吗?你除了“用红绿蓝表示各种颜色,并且R、G、B一般用一个字节来存储”还懂其他吗?估计不能再说东西了吧。对于YUV也是这样,所以没必要看了。

            YUV中的Y、U、V三个分量分别表示明亮度、色度、浓度,每一个分量也是用一个字节来存放的。我们在学习一样新东西时,总是喜欢拿之前学习过的东西作类比,或者想知道新旧东西的关联,下面就给出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模式

            

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

            

             (3)YUV422P: planner模式

            

             (4)YUV420P: planner模式

            

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

            对于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播放图像,可以达到视频的效果。

      1 #include<stdio.h>
      2 #include<highgui.h> //for OpenCV
      3 
      4 //转换函数
      5 void YUV2RGB(int y, int u, int v, int* r, int* g, int* b)
      6 {
      7     assert( r != NULL && g != NULL && b != NULL);
      8 
      9     *r = y + 1.13983 * (v - 128);
     10     *g = y - 0.39465 * (u - 128) - 0.58060 * (v - 128);
     11     *b = y + 2.03211 * (u - 128);
     12 
     13     *r = *r > 255 ? 255 : *r;
     14     *r = *r < 0   ? 0   : *r;
     15 
     16     *g = *g > 255 ? 255 : *g;
     17     *g = *g < 0   ? 0   : *g;
     18 
     19     *b = *b > 255 ? 255 : *b;
     20     *b = *b < 0   ? 0   : *b;
     21 }
     22 
     23 
     24 //把RGB数据填充到OpenCV的IplImage结构体成员imageData中
     25 //imageData是一个数组,其用来存放每一个像素点的BGR。
     26 //其排列的形式很简单(BGR)(BGR)(BGR)(BGR)(BGR)
     27 void fillImage(IplImage* pimg, int r, int g, int b)
     28 {
     29     static int h = 0, w = 0;
     30 
     31     //这里是BGR的顺序。因为OpenCV存放像素的顺序的BGRBGRBGR
     32     pimg->imageData[h*pimg->widthStep + w++] = b;
     33     pimg->imageData[h*pimg->widthStep + w++] = g;
     34     pimg->imageData[h*pimg->widthStep + w++] = r;
     35 
     36     //这部分代码是和OpenCV的一些性质有关,如果看不懂,可以忽略。
     37     //上面的pimg->widthStep也是与OpenCV的性质有关
     38     if( w/3 >= pimg->width )
     39     {
     40         w = 0;
     41         if( h == pimg->height - 1  )
     42             h = 0;
     43         else
     44             ++h;
     45     }
     46 }
     47 
     48 
     49 void convertImage(IplImage* pimg, unsigned char* yuv_buff, int len)
     50 {
     51     int i;
     52     int r, g, b;
     53     int y0, y1, u, v;
     54 
     55     for(i = 0; i < len; i += 4)
     56     {
     57         //其排列方式是y0 u y1 v
     58         //直接提取出来y、u、v三个分量,然后使用公式转成RGB即可
     59         //因为两个y共享一对uv。故y0 u y1 v能提取出两组(y, u, v)
     60         y0 = yuv_buff[i + 0];
     61         u  = yuv_buff[i + 1];
     62         y1 = yuv_buff[i + 2];
     63         v  = yuv_buff[i + 3];
     64 
     65         YUV2RGB(y0, u, v, &r, &g, &b);
     66         //将RGB分量填充到OpenCV的IplImage中
     67         fillImage(pimg, r, g, b);
     68 
     69         YUV2RGB(y1, u, v, &r, &g, &b);
     70         fillImage(pimg, r, g, b);
     71     }
     72 }
     73 
     74 
     75 int main(int argc, char** argv)
     76 {
     77     const char* filename = argc > 1 ? argv[1] : "waterfall_yuyv422.yuv";
     78 
     79     FILE* fin = fopen(filename, "rb");
     80     if( fin == NULL )
     81     {
     82         printf("can't open the file
    ");
     83         return -1;
     84     }
     85 
     86     int width = 352;
     87     int height = 288;
     88 
     89     //一张完整的图像 对应在 yuv文件中 占据的字节数
     90     //因为是yuyv格式的yuv,所以其排列方式是y0 u y1 v y2 u v y3
     91     //因为是两个y共享一对u和v。所以y0 u y1 v代表两个像素(y0, u, v)
     92     //和(y1, u, v),对应地会有两个RGB像素。
     93     //也就是说y0 u y1 v这4个字节的内容等于2个像素, 2比1的关系。
     94     //所以有width * height个像素,就应该要width * height * 2个字节
     95     //这个关系是yuv422特有的。对于yuv444和yuv420会有不同的比例关系
     96     int frame_size = width * height * 2;
     97 
     98 
     99     unsigned char* buff = new unsigned char[frame_size];
    100 
    101     IplImage* pimg = cvCreateImage(cvSize(width, height),
    102                                    IPL_DEPTH_8U, 3);
    103     cvNamedWindow("1.jpg");
    104 
    105     //这里用图像做成一个视频播放器
    106     while( 1 )
    107     {
    108         int ret = fread(buff, 1, frame_size, fin);
    109         if( ret != frame_size )
    110         {
    111             break;
    112         }
    113 
    114         convertImage(pimg, buff, frame_size);
    115         cvShowImage("1.jpg", pimg);
    116         cvWaitKey(33);
    117     }
    118 
    119 
    120     cvReleaseImage(&pimg);
    121     cvDestroyWindow("1.jpg");
    122 
    123     delete [] buff;
    124 
    125     return 0;
    126 }

     http://blog.csdn.net/luotuo44/article/details/26402273?utm_source=tuicool&utm_medium=referral

  • 相关阅读:
    Delphi的idhttp报508 Loop Detected错误的原因
    Delphi的idhttp报IOHandler value is not valid错误的原因
    华为S5700S-52P-LI-AC千兆网管交换机web登录界面配置
    解决win2003/2008下注册机或破解补丁程序无法运行问题
    SQL拆分(转)
    1602四线驱动
    ADC取样
    Delphi AES加密(转)
    使用Qt开发中国象棋(一):概述
    清除当前文件夹下.svn文件的方法
  • 原文地址:https://www.cnblogs.com/eustoma/p/6664907.html
Copyright © 2020-2023  润新知