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

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

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


一、基本思路

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

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

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

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

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

YUV422 planar:

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

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

YUV422 Semi planar:

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

YUV420 planar :

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

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

 YUV420 semi planar:

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

420planar和420semi-planar的区别:

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

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


二、实验代码

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

 // PIX_SIZE 表示的是图片的像素大小,例如一张分辨率为320*240的图片,它的(PIX_SIZE)=320*240
// 下面的宏表示 3种YUV格式 所占用的内存空间大小
#define YUV444MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*3 )
#define YUV422MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*2 )
#define YUV420MEMORY_SIZE(PIX_SIZE) ( (PIX_SIZE)*3/2 ) /**< 下面的宏是基于planar格式的 */
// yuv420中的U分量起始的偏移量
#define YUV420MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
// yuv420中的V分量起始的偏移量
#define YUV420MEMORY_V_OFFSET(PIX_SIZE) ( YUV420MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE)*1/4 ) // yuv422中的U分量起始的偏移量
#define YUV422MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
// yuv422中的V分量起始的偏移量
#define YUV422MEMORY_V_OFFSET(PIX_SIZE) ( YUV422MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE)*1/2 ) // yuv444中的U分量起始的偏移量
#define YUV444MEMORY_U_OFFSET(PIX_SIZE) ( (PIX_SIZE) )
// yuv444中的V分量起始的偏移量
#define YUV444MEMORY_V_OFFSET(PIX_SIZE) ( YUV444MEMORY_U_OFFSET(PIX_SIZE) + (PIX_SIZE) )

YUV相关宏定义

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

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

bmp.h

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

 typedef enum YUV_E{
YUV444 = ,
YUV422 ,
YUV420
}YUV_E; /**< RGB24转换YUV格式的函数指针 */
typedef int (*FP_RGB24ToYUVFormat)(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer); int sample_yuv420_split(const char *url, int w, int h,int num);
int RGB24_TO_YUV444(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
int RGB24_TO_YUV420(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer);
int RGB24_TO_YUV422(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer); bool RGB24_SaveYUVFile(YUV_E eYUV , const char *bmpUrl);

YUV格式和函数

 

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

 int RGB24_TO_YUV420(unsigned char *pucRGBBuffer,int width,int height,unsigned char *pucYUVBuffer)
{
unsigned char y, u, v; /**< 一个像素点对应的YUV分量 */
unsigned char *pucY, *pucU, *pucV; /**< Y、U、V数组对应的指针 */
unsigned char r, g, b; /**< 一个像素点对应的RGB分类 */
unsigned char *pucRGB; /**< RGB24对应的指针 */ int iY = ;
int iX = ;
//存储空间大小 Y : width*height ; U : width/2 * height/2 ; V : width/2 * height/2
pucY = pucYUVBuffer;
pucU = pucYUVBuffer + width*height; /**< U数组在YUV数组的偏移量 */
pucV = pucU + (width*height*/); /**< V数组在YUV数组的偏移量 */ for (iY = ; iY < height;iY++){
pucRGB = pucRGBBuffer + width*iY* ; for ( iX = ;iX < width ; iX++){
/**< 读取像素点的RGB分量 */
r = *(pucRGB++);
g = *(pucRGB++);
b = *(pucRGB++);
/**< 通过公式将RGB转换成为YUV */
y = (unsigned char)( ( * r + * g + * b + ) >> ) + ;
u = (unsigned char)( ( - * r - * g + * b + ) >> ) + ;
v = (unsigned char)( ( * r - * g - * b + ) >> ) + ;
/**< Y分量直接写入Y数组 */
*(pucY++) = clip_value(y,,); // 采样: 水平一半,垂直一半,实验证明这两种方式都可以
if ( iY%== && iX% == ){
/**< 偶数行偶数列时将U分量写入U数组,即一个4*4方块的左上角 */
/*
* 0 1 2 3
* ————————————————————
*0 | U | | U | |
* -------------------
*1 | | | | |
* ————————————————————
*/ *(pucU++) = clip_value(u,,);
}
else{ /**< else表示奇数行的情况 */
/**< 奇数行偶数列时将U分量写入U数组,即一个4*4方块的左下角*/
/*
* 0 1 2 3
* ————————————————————
*0 | | | | |
* -------------------
*1 | V | | V | |
* ————————————————————
*/
if ( iX% == ){
*(pucV++) = clip_value(v,,);
}
}
}
}
return ;
}

RGB24_TO_YUV420

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

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

  上面的这张图以及比较清晰的展示了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-RGB格式转换的一些个人理解

YUV分量全部显示:

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

原始的BMP文件:

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

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

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

上一篇:makefile中一些编译器选项


下一篇:创建一个servlet