这段时间一直在研究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上面。


二、实验代码

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

 // 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

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

  上面的这张图以及比较清晰的展示了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内存排布如果认识不深,那么在做一些转换的时候,会出现很多的问题,虽然现在也有一些现成的第三方库给我们调用,方便我们进行各种格式的转换,但是基本的原理还是需要懂得的,否则到真正出现问题还是不能很快解决的。

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

  1. YUV RGB 格式转换

    第一个公式是RGB转YUV(范围0-255)时用的,第二个公式是用在YUV转换RGB(范围0-255)时用的.1. Y = ; U = -; V = ; 黑色:Y=16 ,U= V =128 红色:Y ...

  2. 【图像处理与医学图像处理】YUV与RGB格式转换速度几种方法对比

    [视频处理]YUV与RGB格式转换 YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与 ...

  3. 【DSP开发】【VS开发】YUV与RGB格式转换

    [视频处理]YUV与RGB格式转换 YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与 ...

  4. 【视频处理】YUV与RGB格式转换

    YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与YUV的变换公式如下: YUV(25 ...

  5. YUV / RGB 格式及快速转换算法

    1 前言 自然界的颜色千变万化,为了给颜色一个量化的衡量标准,就需要建立色彩空间模型来描述各种各样的颜色,由于人对色彩的感知是一个复杂的生理和心理联合作用 的过程,所以在不同的应用领域中为了更好更准确 ...

  6. YUV与RGB格式转换

    YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与YUV的变换公式如下: YUV(25 ...

  7. rgb格式颜色与#000000格式颜色的转换

    首先,#000000格式的颜色被成为十六进制颜色码: 6位数分为三组,每两位数一组,依次是红.黄.蓝颜色的强度: 而与此对应的,rgb(39,137,202)依次是十进制的红黄蓝颜色: 因此将rgb格 ...

  8. Qt QImag图像保存、格式转换

    图像保存bool QImage::save(const QString &fileName, const char *format = Q_NULLPTR, int quality = -1) ...

  9. Android 音视频编解码——RGB与YUV格式转换

    一.RGB模型与YUV模型 1.RGB模型 我们知道物理三基色分别是红(Red).绿(Green).蓝(Blue).现代的显示器技术就是通过组合不同强度的红绿蓝三原色,来达成几乎任何一种可见光的颜色. ...

随机推荐

  1. vue全局配置----小白基础篇

    今天学习vue全局配置.希望帮助我们去了解vue的全局配置,快速开发. Vue.config是vue的全局配置对象.包含Vue的所有全局属性: silent:boolean(默认值:false)--- ...

  2. 2016四川省赛 Floyd-Warshall

    这题真的有毒 首先你忽略 N-M < 100 的条件你就gg吧 其次就算你知道了怎么做之后 还有可能因为写vector或者各种常数大的原因被卡 #include<iostream> ...

  3. C# 获取一个独一无二的字符串 GUID

    在保存文件,创建目录时,为了保证名称不重复,经常使用Random产生一个随机数,有更简单且不会重复的办法是: Guid.NewGuid().ToString() 就会生成一个类似 37c1acec-4 ...

  4. Eclipse远程debug服务器

    一,找端口号 二,Eclipse配置 三,测试是否成功 四,结束远程debug

  5. JavaScript设计模式(5)-组合模式

    组合模式 1. 适合使用组合模式的条件: 存在一批组织成某种层次体系的对象,如树形结构(具体的结构在开发期间可能无法得知) 希望对这批对象或其中的一部分对象实施一个相同的操作 2. 注意点: 组合对象 ...

  6. [NOI2006]神奇口袋

    题面在这里 题意 开始时袋中有\(t\)种小球,第\(i\)种小球有\(t_i\)个,之后每次等概率取出一个球,第\(i\)次取球时观察这个球的颜色\(c_i\)放回并向袋中加入\(d\)个颜色为\( ...

  7. Django的models实现分析

    1      引子 1.1     神奇的Django中的models 我们先来看一段在Django项目中常用的代码: 设置数据库models代码: class Students(models.Mod ...

  8. 接收JSON类型转成对象

    写个小例子吧: public String getJsonTest(String jsonString){} 参数是json 参数长这样  ===> {  'puser' : {'id' : ' ...

  9. WORD分栏后左右都能编辑

    操作如下: 如果是office的请参照:https://zhidao.baidu.com/question/403577041.html 如果是WPS:1.点击插入,有一个分页,点击之后下面有一个可选 ...

  10. 完全卸载hadoop安装的组件(hdp版本)

    yum remove -y hadoop_* zookeeper* ranger* hbase_* ranger* hbase_* ambari-* hadoop_* zookeeper_* hbas ...