• 【总结】关于YUV-RGB格式转换的一些个人理解


      这段时间一直在研究YUV的格式问题例如YUV422、YUV420,在网上搜索了很多这方面的资料,发现很多资料讲的东西是重复的,没有比较深入的讲解,所以看了之后印象不是很深,过了一段时间之后又对它们有了困惑。所以就有了一个想法,打算自己写一个c语言小程序,通过对BMP文件的RGB数据读取,然后将得到的RGB数据转换成为YUV格式的文件,然后用YUV的播放器打开,查看是否解析正确。(在这里,BMP文件我选择了24bpp的格式,为了方便。)通过这样的方式,正向和逆向学习来加深YUV文件的认识。

           经过了2天的努力,从想法到实践,终于完成了YUV422和YUV420的生成,基本上对YUV的几种常见格式有了一些深入的看法,由于本人的水平有限,如果理解有出入,还请各位网友能够指出。


     一、基本思路

    先附上主要程序的流程图:

        

    在正式讲解代码前,我们先来了解YUV的分类 :

    • packed: 打包模式,即Y、U、V数据是连续排布的
    • planar:  平面模式,即Y、U、V是各自分开存放的
    • semi-planar: 半平面模式,即Y单独存放, UV一起存放

    上面的3种是主要内存排布的分类,至于详细的排布如下(主要是YUV422、YUV420):(我们使用一张分辨率为 height*width的图片为例子)

    YUV422 planar:

    整个YUV文件中,Y、U、V是一个大数组,而其中我们可以看成是三个小数组,分别是Y数组(长度为Height*Width)、U数组(长度为Height*Width/2)、V数组(长度为Height*Width/2)。

    YUV422 Semi planar:

    YUV420 planar :

    整个YUV文件中,Y、U、V是一个大数组,而其中我们可以看成是三个小数组,分别是Y数组(长度为Height*Width)、U数组(长度为Height*Width/4)、V数组(长度为Height*Width/4)。

     YUV420 semi planar:

      

     420planar和420semi-planar的区别:

     

       通过上面的示例,我们就可以知道Y、U、V数据在内存中的排布。那么我们怎么将RGB数据转换成为YUV数据呢?先别急,我们需要获取RGB数据,那么问题来了,怎么样得到RGB数据呢?我们使用的方法是读取BMP文件中的RGB数据(BMP是一种未经过压缩的图片格式)。所以接下来,我们就需要通过读取BMP文件,来得到RGB数据。关于BMP的文件格式,本文不讲解,网上有很多讲解的实例,我们会在后面的代码贴出我们的获取RGB的方式。至于后面RGB转换成为YUV的部分,我们也通过代码进行讲解。整个工程中使用了一些常用的C语言的技巧,包括文件操作、函数指针+回调、指针运算、字节对齐+编译器指令+倒序读取BMP等,同时后面会将工程放在github上面。


     二、实验代码

     先来看一些宏定义,这些宏定义主要是为了方便读代码和灵活性: 

     1 // PIX_SIZE 表示的是图片的像素大小,例如一张分辨率为320*240的图片,它的(PIX_SIZE)=320*240
     2 // 下面的宏表示 3种YUV格式 所占用的内存空间大小
     3 #define YUV444MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*3 )
     4 #define YUV422MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*2 )
     5 #define YUV420MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*3/2 )
     6 
     7 /**< 下面的宏是基于planar格式的 */
     8 // yuv420中的U分量起始的偏移量
     9 #define YUV420MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
    10 // yuv420中的V分量起始的偏移量
    11 #define YUV420MEMORY_V_OFFSET(PIX_SIZE) ( YUV420MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE)*1/4 )
    12 
    13 // yuv422中的U分量起始的偏移量
    14 #define YUV422MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
    15 // yuv422中的V分量起始的偏移量
    16 #define YUV422MEMORY_V_OFFSET(PIX_SIZE) ( YUV422MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE)*1/2 )
    17 
    18 // yuv444中的U分量起始的偏移量
    19 #define YUV444MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
    20 // yuv444中的V分量起始的偏移量
    21 #define YUV444MEMORY_V_OFFSET(PIX_SIZE) ( YUV444MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE) )
    YUV相关宏定义

    再看看关于BMP的相关定义及对外的接口函数: 

     1 #ifndef _BMP_H
     2 #define _BMP_H
     3 
     4 #include "common.h"
     5 
     6 #pragma pack(1)
     7 /**< BMP文件头 */
     8 typedef  struct  BMPHEADER_S {
     9         WORD    bfType;            /* 说明文件的类型 */
    10         DWORD   bfSize;            /* 说明文件的大小,用字节为单位*/
    11                                    /* 注意此处的字节序问题*/
    12         WORD    bfReserved1;       /* 保留,设置为0 */
    13         WORD    bfReserved2;       /* 保留,设置为0 */
    14         DWORD   bfOffBits;         /* 说明从BMPHEADER_S结构开始到实际的图像数据之间的字节偏移量 */
    15 }BMPHEADER_S;
    16 /**< BMP文件信息头 */
    17 typedef struct BMPINFOHEADER_S {
    18         DWORD    biSize;            /* 说明结构体所需字节数*/
    19         DWORD    biWidth;           /* 以像素为单位说明图像的宽度*/
    20         DWORD    biHeight;          /* 以像素为单位说明图像的高速*/
    21         WORD     biPlanes;          /* 说明位面数,必须为1 */
    22         WORD     biBitCount;        /* 说明位数/像素,1、2、4、8、24 */
    23         DWORD    biCompression;     /* 说明图像是否压缩及压缩类型BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
    24         DWORD    biSizeImage;       /* 以字节为单位说明图像大小,必须是4的整数倍*/
    25         DWORD    biXPelsPerMeter;   /* 目标设备的水平分辨率,像素/米 */
    26         DWORD    biYPelsPerMeter;   /* 目标设备的垂直分辨率,像素/米 */
    27         DWORD    biClrUsed;         /* 说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
    28         DWORD    biClrImportant;    /* 说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
    29 }BMPINFOHEADER_S;
    30 
    31 /**< 调色板信息 */
    32 typedef  struct  RGBQUAD_S {
    33        BYTE   rgbBlue;       /* 指定蓝色分量*/
    34        BYTE    rgbGreen;     /* 指定绿色分量*/
    35        BYTE    rgbRed;       /* 指定红色分量*/
    36        BYTE   rgbReserved;   /* 保留,指定为0*/
    37 }RGBQUAD_S;
    38 
    39 #pragma pack()
    40 
    41 bool ReadBMPHeader(FILE *fpBMP, BMPHEADER_S *psBMPHeader);
    42 bool ReadBMPInfoHeader(FILE *fpBMP, BMPINFOHEADER_S *psBMPInfoHeader);
    43 unsigned long GetBMPPixSize( BMPINFOHEADER_S  sBMPInfoHeader);
    44 bool ReadRGBFromBMP(FILE *fpBMP, BMPHEADER_S sBMPHeader, BMPINFOHEADER_S sBMPInfoHeader , unsigned char *pucRGBRead);
    45 
    46 #endif // _BMP_H
    bmp.h

    还有YUV的数据类型和相关函数定义:

     1 typedef enum YUV_E{
     2     YUV444 = 0 ,
     3     YUV422 ,
     4     YUV420
     5 }YUV_E;
     6 
     7 /**< RGB24转换YUV格式的函数指针 */
     8 typedef int (*FP_RGB24ToYUVFormat)(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
     9 
    10 int sample_yuv420_split(const char *url, int w, int h,int num);
    11 int RGB24_TO_YUV444(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
    12 int RGB24_TO_YUV420(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
    13 int RGB24_TO_YUV422(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
    14 
    15 bool RGB24_SaveYUVFile(YUV_E eYUV , const char *bmpUrl);
    YUV格式和函数
     

    下面,我们通过一个RGB24转换YUV420的函数进行示例讲解: 

     1 int RGB24_TO_YUV420(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer)
     2 {
     3     unsigned char y, u, v; /**< 一个像素点对应的YUV分量 */
     4     unsigned char *pucY, *pucU, *pucV; /**< Y、U、V数组对应的指针 */
     5     unsigned char r, g, b; /**< 一个像素点对应的RGB分类 */
     6     unsigned char *pucRGB; /**< RGB24对应的指针 */
     7 
     8     int iY = 0 ;
     9     int iX = 0 ;
    10     //存储空间大小 Y : width*height ; U : width/2 * height/2 ; V : width/2 * height/2
    11     pucY = pucYUVBuffer;
    12     pucU = pucYUVBuffer + width*height; /**< U数组在YUV数组的偏移量 */
    13     pucV = pucU + (width*height*1/4);   /**< V数组在YUV数组的偏移量 */
    14 
    15     for (iY = 0; iY < height;iY++){
    16         pucRGB = pucRGBBuffer + width*iY*3 ;
    17 
    18         for ( iX = 0;iX < width ; iX++){
    19             /**< 读取像素点的RGB分量 */
    20             r = *(pucRGB++);
    21             g = *(pucRGB++);
    22             b = *(pucRGB++);
    23             /**< 通过公式将RGB转换成为YUV */
    24             y = (unsigned char)( ( 66 * r + 129 * g +  25 * b + 128) >> 8) + 16  ;
    25             u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;
    26             v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;
    27             /**< Y分量直接写入Y数组 */
    28             *(pucY++) = clip_value(y,0,255);
    29 
    30             // 采样: 水平一半,垂直一半,实验证明这两种方式都可以
    31             if ( iY%2==0 && iX%2 == 0){
    32                 /**< 偶数行偶数列时将U分量写入U数组,即一个4*4方块的左上角 */
    33                 /*
    34                  *     0   1   2   3
    35                  *   ————————————————————
    36                  *0  | U |   | U |   |
    37                  *   -------------------
    38                  *1  |   |   |   |   |
    39                  *   ————————————————————
    40                  */
    41 
    42                 *(pucU++) = clip_value(u,0,255);
    43             }
    44             else{ /**< else表示奇数行的情况 */
    45                 /**<  奇数行偶数列时将U分量写入U数组,即一个4*4方块的左下角*/
    46                  /*
    47                  *     0   1   2   3
    48                  *   ————————————————————
    49                  *0  |   |   |   |   |
    50                  *   -------------------
    51                  *1  | V |   | V |   |
    52                  *   ————————————————————
    53                  */
    54                 if ( iX%2 == 0 ){
    55                     *(pucV++) = clip_value(v,0,255);
    56                 }
    57             }
    58         }
    59     }
    60     return 1;
    61 }
    RGB24_TO_YUV420

     为了方便理解,我画了一张图:

      上面的这张图以及比较清晰的展示了RGB转换成为YUV的过程,先是从RGB数组中读取一个像素的R、G、B分量,然后通过公式转换成为对应的Y、U、V分量,直接将Y分量写入对应的Y数组;当满足偶数行偶数列时(即一个4*4方格)取一个U分量,写入U数组;当满足奇数行偶数列时(4*4方格的左下角)取V分量,写入V数组。经过二重循环,就可以完成整个RGB到YUV420的转换。至于RGB转换YUV的公式可以去网上找。

      通过上面的例子,如果是RGB24转换成为YUV422,我觉得就应该好理解了,和转换成为YUV420的过程类似,但是什么时候取U分量和什么时候取V分量不同而已,具体的可以看最后的工程来验证你的想法。  附上github地址:

    https://github.com/qibaowei-guet/RGB24-To-YUV.git (注意:源工程是通过codeblock创建的)

     三、总结

       实验效果:(通过7yuv软件打开,地址:http://datahammer.de/

    只显示Y分量:

      

    YUV分量全部显示:

      

    原始的BMP文件:

      

     通过比对,可以看到YUV文件的显得比较淡,这主要是因为我们选取的转换公式的原因,公式的细节不深究。毕竟我们不是专门研究这些公式的专家。

      最后,因为YUV格式很多,名称也很多,也不是每一种格式我们在工作的时候都会用到,所以,我们只需要掌握一些基本的就够了。个人觉得,如果对这些格式的YUV内存排布如果认识不深,那么在做一些转换的时候,会出现很多的问题,虽然现在也有一些现成的第三方库给我们调用,方便我们进行各种格式的转换,但是基本的原理还是需要懂得的,否则到真正出现问题还是不能很快解决的。

  • 相关阅读:
    reids 入门
    关于字典的那些事儿.
    更新阿里源repo出的问题
    关于-编码进阶
    *** 数据类型之间的转化
    关于列表那些事:12.24
    while else 结构体(自测)
    关于切片的步长为正负值 的问题(自测)
    if 嵌套if 的先后顺序的区别 (自测)
    二: python基础数据类型(int,
  • 原文地址:https://www.cnblogs.com/qiabaowei/p/8687487.html
Copyright © 2020-2023  润新知