当前位置: 首页 > 编程日记 > 正文

视音频数据处理入门:RGB、YUV像素数据处理【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/50534150

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

视音频数据处理入门系列文章:

视音频数据处理入门:RGB、YUV像素数据处理

视音频数据处理入门:PCM音频采样数据处理

视音频数据处理入门:H.264视频码流解析

视音频数据处理入门:AAC音频码流解析

视音频数据处理入门:FLV封装格式解析

视音频数据处理入门:UDP-RTP协议解析

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

有段时间没有写博客了,这两天写起博客来竟然感觉有些兴奋,仿佛找回了原来的感觉。前一阵子在梳理以前文章的时候,发现自己虽然总结了各种视音频应用程序,却还缺少一个适合无视音频背景人员学习的“最基础”的程序。因此抽时间将以前写过的代码整理成了一个小项目。这个小项目里面包含了一系列简单的函数,可以对RGB/YUV视频像素数据、PCM音频采样数据、H.264视频码流、AAC音频码流、FLV封装格式数据、UDP/RTP协议数据进行简单处理。这个项目的一大特点就是没有使用任何的第三方类库,完全借助于C语言的基本函数实现了功能。通过对这些代码的学习,可以让初学者迅速掌握视音频数据的基本格式。有关上述几种格式的介绍可以参考文章《[总结]视音频编解码技术零基础学习方法》。

从这篇文章开始打算写6篇文章分别记录上述6种不同类型的视音频数据的处理方法。本文首先记录第一部分即RGB/YUV视频像素数据的处理方法。视频像素数据在视频播放器的解码流程中的位置如下图所示。

本文分别介绍如下几个RGB/YUV视频像素数据处理函数:
分离YUV420P像素数据中的Y、U、V分量
分离YUV444P像素数据中的Y、U、V分量
将YUV420P像素数据去掉颜色(变成灰度图)
将YUV420P像素数据的亮度减半
将YUV420P像素数据的周围加上边框
生成YUV420P格式的灰阶测试图
计算两个YUV420P像素数据的PSNR
分离RGB24像素数据中的R、G、B分量
将RGB24格式像素数据封装为BMP图像
将RGB24格式像素数据转换为YUV420P格式像素数据
生成RGB24格式的彩条测试图

本文中的RGB/YUV文件需要使用RGB/YUV播放器才能查看。YUV播放器种类比较多,例如YUV Player Deluxe,或者开源播放器(参考文章《修改了一个YUV/RGB播放器》)等。

函数列表

(1) 分离YUV420P像素数据中的Y、U、V分量

本程序中的函数可以将YUV420P数据中的Y、U、V三个分量分离开来并保存成三个文件。函数的代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Split Y, U, V planes in YUV420P file. 
  3.  * @param url  Location of Input YUV file. 
  4.  * @param w    Width of Input YUV file. 
  5.  * @param h    Height of Input YUV file. 
  6.  * @param num  Number of frames to process. 
  7.  * 
  8.  */  
  9. int simplest_yuv420_split(char *url, int w, int h,int num){  
  10. FILE *fp=fopen(url,"rb+");  
  11. FILE *fp1=fopen("output_420_y.y","wb+");  
  12. FILE *fp2=fopen("output_420_u.y","wb+");  
  13. FILE *fp3=fopen("output_420_v.y","wb+");  
  14. unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  15. for(int i=0;i<num;i++){  
  16. fread(pic,1,w*h*3/2,fp);
  17. //Y  
  18. fwrite(pic,1,w*h,fp1);
  19. //U  
  20. fwrite(pic+w*h,1,w*h/4,fp2);
  21. //V  
  22. fwrite(pic+w*h*5/4,1,w*h/4,fp3);
  23. }
  24. free(pic);
  25. fclose(fp);
  26. fclose(fp1);
  27. fclose(fp2);
  28. fclose(fp3);
  29. return 0;  
  30. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_split("lena_256x256_yuv420p.yuv",256,256,1);  

从代码可以看出,如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据。其中前w*h Byte存储Y,接着的w*h*1/4 Byte存储U,最后w*h*1/4 Byte存储V。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件分离成为三个文件:

output_420_y.y:纯Y数据,分辨率为256x256。

output_420_u.y:纯U数据,分辨率为128x128。

output_420_v.y:纯V数据,分辨率为128x128。

注:本文中像素的采样位数一律为8bit。由于1Byte=8bit,所以一个像素的一个分量的采样值占用1Byte。

程序输入的原图如下所示。

lena_256x256_yuv420p.yuv

程序输出的三个文件的截图如下图所示。在这里需要注意输出的U、V分量在YUV播放器中也是当做Y分量进行播放的。

output_420_y.y

          

output_420_u.y和output_420_v.y

(2)分离YUV444P像素数据中的Y、U、V分量

本程序中的函数可以将YUV444P数据中的Y、U、V三个分量分离开来并保存成三个文件。函数的代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Split Y, U, V planes in YUV444P file. 
  3.  * @param url  Location of YUV file. 
  4.  * @param w    Width of Input YUV file. 
  5.  * @param h    Height of Input YUV file. 
  6.  * @param num  Number of frames to process. 
  7.  * 
  8.  */  
  9. int simplest_yuv444_split(char *url, int w, int h,int num){  
  10. FILE *fp=fopen(url,"rb+");  
  11. FILE *fp1=fopen("output_444_y.y","wb+");  
  12. FILE *fp2=fopen("output_444_u.y","wb+");  
  13. FILE *fp3=fopen("output_444_v.y","wb+");  
  14. unsigned char *pic=(unsigned char *)malloc(w*h*3);  
  15. for(int i=0;i<num;i++){  
  16. fread(pic,1,w*h*3,fp);
  17. //Y  
  18. fwrite(pic,1,w*h,fp1);
  19. //U  
  20. fwrite(pic+w*h,1,w*h,fp2);
  21. //V  
  22. fwrite(pic+w*h*2,1,w*h,fp3);
  23. }
  24. free(pic);
  25. fclose(fp);
  26. fclose(fp1);
  27. fclose(fp2);
  28. fclose(fp3);
  29. return 0;  
  30. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv444_split("lena_256x256_yuv444p.yuv",256,256,1);  

从代码可以看出,如果视频帧的宽和高分别为w和h,那么一帧YUV444P像素数据一共占用w*h*3 Byte的数据。其中前w*h Byte存储Y,接着的w*h Byte存储U,最后w*h Byte存储V。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv444p.yuv的YUV444P格式的像素数据文件分离成为三个文件:

output_444_y.y:纯Y数据,分辨率为256x256。
output_444_u.y:纯U数据,分辨率为256x256。
output_444_v.y:纯V数据,分辨率为256x256。

输入的原图如下所示。

输出的三个文件的截图如下图所示。

output_444_y.y

output_444_u.y

output_444_v.y


(3) 将YUV420P像素数据去掉颜色(变成灰度图)

本程序中的函数可以将YUV420P格式像素数据的彩色去掉,变成纯粹的灰度图。函数的代码如下。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Convert YUV420P file to gray picture 
  3.  * @param url     Location of Input YUV file. 
  4.  * @param w       Width of Input YUV file. 
  5.  * @param h       Height of Input YUV file. 
  6.  * @param num     Number of frames to process. 
  7.  */  
  8. int simplest_yuv420_gray(char *url, int w, int h,int num){  
  9. FILE *fp=fopen(url,"rb+");  
  10. FILE *fp1=fopen("output_gray.yuv","wb+");  
  11. unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  12. for(int i=0;i<num;i++){  
  13. fread(pic,1,w*h*3/2,fp);
  14. //Gray  
  15. memset(pic+w*h,128,w*h/2);
  16. fwrite(pic,1,w*h*3/2,fp1);
  17. }
  18. free(pic);
  19. fclose(fp);
  20. fclose(fp1);
  21. return 0;  
  22. }

调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_gray("lena_256x256_yuv420p.yuv",256,256,1);  


从代码可以看出,如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_gray.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。

处理后的图像如下所示。

(4)将YUV420P像素数据的亮度减半

本程序中的函数可以通过将YUV数据中的亮度分量Y的数值减半的方法,降低图像的亮度。函数代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Halve Y value of YUV420P file 
  3.  * @param url     Location of Input YUV file. 
  4.  * @param w       Width of Input YUV file. 
  5.  * @param h       Height of Input YUV file. 
  6.  * @param num     Number of frames to process. 
  7.  */  
  8. int simplest_yuv420_halfy(char *url, int w, int h,int num){  
  9. FILE *fp=fopen(url,"rb+");  
  10. FILE *fp1=fopen("output_half.yuv","wb+");  
  11. unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  12. for(int i=0;i<num;i++){  
  13. fread(pic,1,w*h*3/2,fp);
  14. //Half  
  15. for(int j=0;j<w*h;j++){  
  16. unsigned char temp=pic[j]/2;  
  17. //printf("%d,\n",temp);  
  18. pic[j]=temp;
  19. }
  20. fwrite(pic,1,w*h*3/2,fp1);
  21. }
  22. free(pic);
  23. fclose(fp);
  24. fclose(fp1);
  25. return 0;  
  26. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_halfy("lena_256x256_yuv420p.yuv",256,256,1);  


从代码可以看出,如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255,对应C语言中的unsigned char数据类型。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_half.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。

处理后的图像如下所示。


(5)将YUV420P像素数据的周围加上边框

本程序中的函数可以通过修改YUV数据中特定位置的亮度分量Y的数值,给图像添加一个“边框”的效果。函数代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Add border for YUV420P file 
  3.  * @param url     Location of Input YUV file. 
  4.  * @param w       Width of Input YUV file. 
  5.  * @param h       Height of Input YUV file. 
  6.  * @param border  Width of Border. 
  7.  * @param num     Number of frames to process. 
  8.  */  
  9. int simplest_yuv420_border(char *url, int w, int h,int border,int num){  
  10. FILE *fp=fopen(url,"rb+");  
  11. FILE *fp1=fopen("output_border.yuv","wb+");  
  12. unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  13. for(int i=0;i<num;i++){  
  14. fread(pic,1,w*h*3/2,fp);
  15. //Y  
  16. for(int j=0;j<h;j++){  
  17. for(int k=0;k<w;k++){  
  18. if(k<border||k>(w-border)||j<border||j>(h-border)){  
  19. pic[j*w+k]=255;
  20. //pic[j*w+k]=0;  
  21. }
  22. }
  23. }
  24. fwrite(pic,1,w*h*3/2,fp1);
  25. }
  26. free(pic);
  27. fclose(fp);
  28. fclose(fp1);
  29. return 0;  
  30. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_border("lena_256x256_yuv420p.yuv",256,256,20,1);  


从代码可以看出,图像的边框的宽度为border,本程序将距离图像边缘border范围内的像素的亮度分量Y的取值设置成了亮度最大值255。上述调用函数的代码运行后,将会把一张分辨率为256x256的名称为lena_256x256_yuv420p.yuv的YUV420P格式的像素数据文件处理成名称为output_border.yuv的YUV420P格式的像素数据文件。输入的原图如下所示。

处理后的图像如下所示。


(6) 生成YUV420P格式的灰阶测试图

本程序中的函数可以生成一张YUV420P格式的灰阶测试图。函数代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Generate YUV420P gray scale bar. 
  3.  * @param width    Width of Output YUV file. 
  4.  * @param height   Height of Output YUV file. 
  5.  * @param ymin     Max value of Y 
  6.  * @param ymax     Min value of Y 
  7.  * @param barnum   Number of bars 
  8.  * @param url_out  Location of Output YUV file. 
  9.  */  
  10. int simplest_yuv420_graybar(int width, int height,int ymin,int ymax,int barnum,char *url_out){  
  11. int barwidth;  
  12. float lum_inc;  
  13. unsigned char lum_temp;  
  14. int uv_width,uv_height;  
  15. FILE *fp=NULL;  
  16. unsigned char *data_y=NULL;  
  17. unsigned char *data_u=NULL;  
  18. unsigned char *data_v=NULL;  
  19. int t=0,i=0,j=0;  
  20. barwidth=width/barnum;
  21. lum_inc=((float)(ymax-ymin))/((float)(barnum-1));  
  22. uv_width=width/2;
  23. uv_height=height/2;
  24. data_y=(unsigned char *)malloc(width*height);  
  25. data_u=(unsigned char *)malloc(uv_width*uv_height);  
  26. data_v=(unsigned char *)malloc(uv_width*uv_height);  
  27. if((fp=fopen(url_out,"wb+"))==NULL){  
  28. printf("Error: Cannot create file!");  
  29. return -1;  
  30. }
  31. //Output Info  
  32. printf("Y, U, V value from picture's left to right:\n");  
  33. for(t=0;t<(width/barwidth);t++){  
  34. lum_temp=ymin+(char)(t*lum_inc);  
  35. printf("%3d, 128, 128\n",lum_temp);  
  36. }
  37. //Gen Data  
  38. for(j=0;j<height;j++){  
  39. for(i=0;i<width;i++){  
  40. t=i/barwidth;
  41. lum_temp=ymin+(char)(t*lum_inc);  
  42. data_y[j*width+i]=lum_temp;
  43. }
  44. }
  45. for(j=0;j<uv_height;j++){  
  46. for(i=0;i<uv_width;i++){  
  47. data_u[j*uv_width+i]=128;
  48. }
  49. }
  50. for(j=0;j<uv_height;j++){  
  51. for(i=0;i<uv_width;i++){  
  52. data_v[j*uv_width+i]=128;
  53. }
  54. }
  55. fwrite(data_y,width*height,1,fp);
  56. fwrite(data_u,uv_width*uv_height,1,fp);
  57. fwrite(data_v,uv_width*uv_height,1,fp);
  58. fclose(fp);
  59. free(data_y);
  60. free(data_u);
  61. free(data_v);
  62. return 0;  
  63. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_graybar(640, 360,0,255,10,"graybar_640x360.yuv");  


从源代码可以看出,本程序一方面通过灰阶测试图的亮度最小值ymin,亮度最大值ymax,灰阶数量barnum确定每一个灰度条中像素的亮度分量Y的取值。另一方面还要根据图像的宽度width和图像的高度height以及灰阶数量barnum确定每一个灰度条的宽度。有了这两方面信息之后,就可以生成相应的图片了。上述调用函数的代码运行后,会生成一个取值范围从0-255,一共包含10个灰度条的YUV420P格式的测试图。测试图的内容如下所示。

从程序也可以得到从左到右10个灰度条的Y、U、V取值,如下所示。

Y

U

V

0

128

128

28

128

128

56

128

128

85

128

128

113

128

128

141

128

128

170

128

128

198

128

128

226

128

128

255

128

128


(7)计算两个YUV420P像素数据的PSNR

PSNR是最基本的视频质量评价方法。本程序中的函数可以对比两张YUV图片中亮度分量Y的PSNR。函数的代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Calculate PSNR between 2 YUV420P file 
  3.  * @param url1     Location of first Input YUV file. 
  4.  * @param url2     Location of another Input YUV file. 
  5.  * @param w        Width of Input YUV file. 
  6.  * @param h        Height of Input YUV file. 
  7.  * @param num      Number of frames to process. 
  8.  */  
  9. int simplest_yuv420_psnr(char *url1,char *url2,int w,int h,int num){  
  10. FILE *fp1=fopen(url1,"rb+");  
  11. FILE *fp2=fopen(url2,"rb+");  
  12. unsigned char *pic1=(unsigned char *)malloc(w*h);  
  13. unsigned char *pic2=(unsigned char *)malloc(w*h);  
  14. for(int i=0;i<num;i++){  
  15. fread(pic1,1,w*h,fp1);
  16. fread(pic2,1,w*h,fp2);
  17. double mse_sum=0,mse=0,psnr=0;  
  18. for(int j=0;j<w*h;j++){  
  19. mse_sum+=pow((double)(pic1[j]-pic2[j]),2);  
  20. }
  21. mse=mse_sum/(w*h);
  22. psnr=10*log10(255.0*255.0/mse);
  23. printf("%5.3f\n",psnr);  
  24. fseek(fp1,w*h/2,SEEK_CUR);
  25. fseek(fp2,w*h/2,SEEK_CUR);
  26. }
  27. free(pic1);
  28. free(pic2);
  29. fclose(fp1);
  30. fclose(fp2);
  31. return 0;  
  32. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_yuv420_psnr("lena_256x256_yuv420p.yuv","lena_distort_256x256_yuv420p.yuv",256,256,1);  


对于8bit量化的像素数据来说,PSNR的计算公式如下所示。

上述公式中mse的计算公式如下所示。

其中M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。本程序输入的两张图像的对比图如下图所示。其中左边的图像为原始图像,右边的图像为受损图像。

经过程序计算后得到的PSNR取值为26.693。PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。

(8) 分离RGB24像素数据中的R、G、B分量

本程序中的函数可以将RGB24数据中的R、G、B三个分量分离开来并保存成三个文件。函数的代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Split R, G, B planes in RGB24 file. 
  3.  * @param url  Location of Input RGB file. 
  4.  * @param w    Width of Input RGB file. 
  5.  * @param h    Height of Input RGB file. 
  6.  * @param num  Number of frames to process. 
  7.  * 
  8.  */  
  9. int simplest_rgb24_split(char *url, int w, int h,int num){  
  10. FILE *fp=fopen(url,"rb+");  
  11. FILE *fp1=fopen("output_r.y","wb+");  
  12. FILE *fp2=fopen("output_g.y","wb+");  
  13. FILE *fp3=fopen("output_b.y","wb+");  
  14. unsigned char *pic=(unsigned char *)malloc(w*h*3);  
  15. for(int i=0;i<num;i++){  
  16. fread(pic,1,w*h*3,fp);
  17. for(int j=0;j<w*h*3;j=j+3){  
  18. //R  
  19. fwrite(pic+j,1,1,fp1);
  20. //G  
  21. fwrite(pic+j+1,1,1,fp2);
  22. //B  
  23. fwrite(pic+j+2,1,1,fp3);
  24. }
  25. }
  26. free(pic);
  27. fclose(fp);
  28. fclose(fp1);
  29. fclose(fp2);
  30. fclose(fp3);
  31. return 0;  
  32. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_rgb24_split("cie1931_500x500.rgb", 500, 500,1);  


从代码可以看出,与YUV420P三个分量分开存储不同,RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用w*h*3 Byte的存储空间。RGB24格式规定首先存储第一个像素的R、G、B,然后存储第二个像素的R、G、B…以此类推。类似于YUV420P的存储方式称为Planar方式,而类似于RGB24的存储方式称为Packed方式。上述调用函数的代码运行后,将会把一张分辨率为500x500的名称为cie1931_500x500.rgb的RGB24格式的像素数据文件分离成为三个文件:

output_r.y:R数据,分辨率为256x256。
output_g.y:G数据,分辨率为256x256。

output_b.y:B数据,分辨率为256x256。

输入的原图是一张标准的CIE 1931色度图。该色度图右下为红色,上方为绿色,左下为蓝色,如下所示。

R数据图像如下所示。

G数据图像如下所示。

B数据图像如下所示。


(9)将RGB24格式像素数据封装为BMP图像

BMP图像内部实际上存储的就是RGB数据。本程序实现了对RGB像素数据的封装处理。通过本程序中的函数,可以将RGB数据封装成为一张BMP图像。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Convert RGB24 file to BMP file 
  3.  * @param rgb24path    Location of input RGB file. 
  4.  * @param width        Width of input RGB file. 
  5.  * @param height       Height of input RGB file. 
  6.  * @param url_out      Location of Output BMP file. 
  7.  */  
  8. int simplest_rgb24_to_bmp(const char *rgb24path,int width,int height,const char *bmppath){  
  9. typedef struct   
  10. {
  11. long imageSize;  
  12. long blank;  
  13. long startPosition;  
  14. }BmpHead;
  15. typedef struct  
  16. {
  17. long  Length;  
  18. long  width;  
  19. long  height;  
  20. unsigned short  colorPlane;  
  21. unsigned short  bitColor;  
  22. long  zipFormat;  
  23. long  realSize;  
  24. long  xPels;  
  25. long  yPels;  
  26. long  colorUse;  
  27. long  colorImportant;  
  28. }InfoHead;
  29. int i=0,j=0;  
  30. BmpHead m_BMPHeader={0};
  31. InfoHead  m_BMPInfoHeader={0};
  32. char bfType[2]={'B','M'};  
  33. int header_size=sizeof(bfType)+sizeof(BmpHead)+sizeof(InfoHead);  
  34. unsigned char *rgb24_buffer=NULL;  
  35. FILE *fp_rgb24=NULL,*fp_bmp=NULL;  
  36. if((fp_rgb24=fopen(rgb24path,"rb"))==NULL){  
  37. printf("Error: Cannot open input RGB24 file.\n");  
  38. return -1;  
  39. }
  40. if((fp_bmp=fopen(bmppath,"wb"))==NULL){  
  41. printf("Error: Cannot open output BMP file.\n");  
  42. return -1;  
  43. }
  44. rgb24_buffer=(unsigned char *)malloc(width*height*3);  
  45. fread(rgb24_buffer,1,width*height*3,fp_rgb24);
  46. m_BMPHeader.imageSize=3*width*height+header_size;
  47. m_BMPHeader.startPosition=header_size;
  48. m_BMPInfoHeader.Length=sizeof(InfoHead);   
  49. m_BMPInfoHeader.width=width;
  50. //BMP storage pixel data in opposite direction of Y-axis (from bottom to top).  
  51. m_BMPInfoHeader.height=-height;
  52. m_BMPInfoHeader.colorPlane=1;
  53. m_BMPInfoHeader.bitColor=24;
  54. m_BMPInfoHeader.realSize=3*width*height;
  55. fwrite(bfType,1,sizeof(bfType),fp_bmp);  
  56. fwrite(&m_BMPHeader,1,sizeof(m_BMPHeader),fp_bmp);  
  57. fwrite(&m_BMPInfoHeader,1,sizeof(m_BMPInfoHeader),fp_bmp);  
  58. //BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2  
  59. //It saves pixel data in Little Endian  
  60. //So we change 'R' and 'B'  
  61. for(j =0;j<height;j++){  
  62. for(i=0;i<width;i++){  
  63. char temp=rgb24_buffer[(j*width+i)*3+2];  
  64. rgb24_buffer[(j*width+i)*3+2]=rgb24_buffer[(j*width+i)*3+0];
  65. rgb24_buffer[(j*width+i)*3+0]=temp;
  66. }
  67. }
  68. fwrite(rgb24_buffer,3*width*height,1,fp_bmp);
  69. fclose(fp_rgb24);
  70. fclose(fp_bmp);
  71. free(rgb24_buffer);
  72. printf("Finish generate %s!\n",bmppath);  
  73. return 0;  
  74. return 0;  
  75. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_rgb24_to_bmp("lena_256x256_rgb24.rgb",256,256,"output_lena.bmp");  


通过代码可以看出,改程序完成了主要完成了两个工作:
1)将RGB数据前面加上文件头。
2)将RGB数据中每个像素的“B”和“R”的位置互换。

BMP文件是由BITMAPFILEHEADER、BITMAPINFOHEADER、RGB像素数据共3个部分构成,它的结构如下图所示。

BITMAPFILEHEADER

BITMAPINFOHEADER

RGB像素数据


其中前两部分的结构如下所示。在写入BMP文件头的时候给其中的每个字段赋上合适的值就可以了。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. typedef  struct  tagBITMAPFILEHEADER  
  2. {
  3. unsigned short int  bfType;       //位图文件的类型,必须为BM   
  4. unsigned long       bfSize;       //文件大小,以字节为单位  
  5. unsigned short int  bfReserverd1; //位图文件保留字,必须为0   
  6. unsigned short int  bfReserverd2; //位图文件保留字,必须为0   
  7. unsigned long       bfbfOffBits;  //位图文件头到数据的偏移量,以字节为单位  
  8. }BITMAPFILEHEADER;
  9. typedef  struct  tagBITMAPINFOHEADER   
  10. {
  11. long biSize;                        //该结构大小,字节为单位  
  12. long  biWidth;                     //图形宽度以象素为单位  
  13. long  biHeight;                     //图形高度以象素为单位  
  14. short int  biPlanes;               //目标设备的级别,必须为1   
  15. short int  biBitcount;             //颜色深度,每个象素所需要的位数  
  16. short int  biCompression;        //位图的压缩类型  
  17. long  biSizeImage;              //位图的大小,以字节为单位  
  18. long  biXPelsPermeter;       //位图水平分辨率,每米像素数  
  19. long  biYPelsPermeter;       //位图垂直分辨率,每米像素数  
  20. long  biClrUsed;            //位图实际使用的颜色表中的颜色数  
  21. long  biClrImportant;       //位图显示过程中重要的颜色数  
  22. }BITMAPINFOHEADER;


BMP采用的是小端(Little Endian)存储方式。这种存储方式中“RGB24”格式的像素的分量存储的先后顺序为B、G、R。由于RGB24格式存储的顺序是R、G、B,所以需要将“R”和“B”顺序作一个调换再进行存储。

下图为输入的RGB24格式的图像lena_256x256_rgb24.rgb。

下图分封装为BMP格式后的图像output_lena.bmp。封装后的图像使用普通的看图软件就可以查看。


(10)将RGB24格式像素数据转换为YUV420P格式像素数据

本程序中的函数可以将RGB24格式的像素数据转换为YUV420P格式的像素数据。函数的代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char  max_val){  
  2. if(x>max_val){  
  3. return max_val;  
  4. }else if(x<min_val){  
  5. return min_val;  
  6. }else{  
  7. return x;  
  8. }
  9. }
  10. //RGB to YUV420  
  11. bool RGB24_TO_YUV420(unsigned char *RgbBuf,int w,int h,unsigned char *yuvBuf)  
  12. {
  13. unsigned char*ptrY, *ptrU, *ptrV, *ptrRGB;  
  14. memset(yuvBuf,0,w*h*3/2);
  15. ptrY = yuvBuf;
  16. ptrU = yuvBuf + w*h;
  17. ptrV = ptrU + (w*h*1/4);
  18. unsigned char y, u, v, r, g, b;  
  19. for (int j = 0; j<h;j++){  
  20. ptrRGB = RgbBuf + w*j*3 ;
  21. for (int i = 0;i<w;i++){  
  22. r = *(ptrRGB++);
  23. g = *(ptrRGB++);
  24. b = *(ptrRGB++);
  25. y = (unsigned char)( ( 66 * r + 129 * g +  25 * b + 128) >> 8) + 16  ;            
  26. u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;            
  27. v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;  
  28. *(ptrY++) = clip_value(y,0,255);
  29. if (j%2==0&&i%2 ==0){  
  30. *(ptrU++) =clip_value(u,0,255);
  31. }
  32. else{  
  33. if (i%2==0){  
  34. *(ptrV++) =clip_value(v,0,255);
  35. }
  36. }
  37. }
  38. }
  39. return true;  
  40. }
  41. /** 
  42.  * Convert RGB24 file to YUV420P file 
  43.  * @param url_in  Location of Input RGB file. 
  44.  * @param w       Width of Input RGB file. 
  45.  * @param h       Height of Input RGB file. 
  46.  * @param num     Number of frames to process. 
  47.  * @param url_out Location of Output YUV file. 
  48.  */  
  49. int simplest_rgb24_to_yuv420(char *url_in, int w, int h,int num,char *url_out){  
  50. FILE *fp=fopen(url_in,"rb+");  
  51. FILE *fp1=fopen(url_out,"wb+");  
  52. unsigned char *pic_rgb24=(unsigned char *)malloc(w*h*3);  
  53. unsigned char *pic_yuv420=(unsigned char *)malloc(w*h*3/2);  
  54. for(int i=0;i<num;i++){  
  55. fread(pic_rgb24,1,w*h*3,fp);
  56. RGB24_TO_YUV420(pic_rgb24,w,h,pic_yuv420);
  57. fwrite(pic_yuv420,1,w*h*3/2,fp1);
  58. }
  59. free(pic_rgb24);
  60. free(pic_yuv420);
  61. fclose(fp);
  62. fclose(fp1);
  63. return 0;  
  64. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_rgb24_to_yuv420("lena_256x256_rgb24.rgb",256,256,1,"output_lena.yuv");  


从源代码可以看出,本程序实现了RGB到YUV的转换公式:

Y= 0.299*R+0.587*G+0.114*B

U=-0.147*R-0.289*G+0.463*B

V= 0.615*R-0.515*G-0.100*B

在转换的过程中有以下几点需要注意:
1) RGB24存储方式是Packed,YUV420P存储方式是Packed。
2) U,V在水平和垂直方向的取样数是Y的一半

转换前的RGB24格式像素数据lena_256x256_rgb24.rgb的内容如下所示。

转换后的YUV420P格式的像素数据output_lena.yuv的内容如下所示。


(11)生成RGB24格式的彩条测试图

本程序中的函数可以生成一张RGB24格式的彩条测试图。函数代码如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Generate RGB24 colorbar. 
  3.  * @param width    Width of Output RGB file. 
  4.  * @param height   Height of Output RGB file. 
  5.  * @param url_out  Location of Output RGB file. 
  6.  */  
  7. int simplest_rgb24_colorbar(int width, int height,char *url_out){  
  8. unsigned char *data=NULL;  
  9. int barwidth;  
  10. char filename[100]={0};  
  11. FILE *fp=NULL;  
  12. int i=0,j=0;  
  13. data=(unsigned char *)malloc(width*height*3);  
  14. barwidth=width/8;
  15. if((fp=fopen(url_out,"wb+"))==NULL){  
  16. printf("Error: Cannot create file!");  
  17. return -1;  
  18. }
  19. for(j=0;j<height;j++){  
  20. for(i=0;i<width;i++){  
  21. int barnum=i/barwidth;  
  22. switch(barnum){  
  23. case 0:{  
  24. data[(j*width+i)*3+0]=255;
  25. data[(j*width+i)*3+1]=255;
  26. data[(j*width+i)*3+2]=255;
  27. break;  
  28. }
  29. case 1:{  
  30. data[(j*width+i)*3+0]=255;
  31. data[(j*width+i)*3+1]=255;
  32. data[(j*width+i)*3+2]=0;
  33. break;  
  34. }
  35. case 2:{  
  36. data[(j*width+i)*3+0]=0;
  37. data[(j*width+i)*3+1]=255;
  38. data[(j*width+i)*3+2]=255;
  39. break;  
  40. }
  41. case 3:{  
  42. data[(j*width+i)*3+0]=0;
  43. data[(j*width+i)*3+1]=255;
  44. data[(j*width+i)*3+2]=0;
  45. break;  
  46. }
  47. case 4:{  
  48. data[(j*width+i)*3+0]=255;
  49. data[(j*width+i)*3+1]=0;
  50. data[(j*width+i)*3+2]=255;
  51. break;  
  52. }
  53. case 5:{  
  54. data[(j*width+i)*3+0]=255;
  55. data[(j*width+i)*3+1]=0;
  56. data[(j*width+i)*3+2]=0;
  57. break;  
  58. }
  59. case 6:{  
  60. data[(j*width+i)*3+0]=0;
  61. data[(j*width+i)*3+1]=0;
  62. data[(j*width+i)*3+2]=255;
  63. break;  
  64. }
  65. case 7:{  
  66. data[(j*width+i)*3+0]=0;
  67. data[(j*width+i)*3+1]=0;
  68. data[(j*width+i)*3+2]=0;
  69. break;  
  70. }
  71. }
  72. }
  73. }
  74. fwrite(data,width*height*3,1,fp);
  75. fclose(fp);
  76. free(data);
  77. return 0;  
  78. }


调用上面函数的方法如下所示。

[cpp] view plaincopy 在CODE上查看代码片派生到我的代码片
  1. simplest_rgb24_colorbar(640, 360,"colorbar_640x360.rgb");  


从源代码可以看出,本程序循环输出“白黄青绿品红蓝黑”8种颜色的彩条。这8种颜色的彩条的R、G、B取值如下所示。

颜色

(R, G, B)

(255, 255, 255)

(255, 255,   0)

(  0, 255, 255)

绿

(  0, 255,   0)

(255,   0, 255)

(255,   0,   0)

(  0,   0, 255)

(  0,   0,   0)


生成的图像截图如下所示。



下载


Simplest mediadata test

 

项目主页

SourceForge:https://sourceforge.net/projects/simplest-mediadata-test/

Github:https://github.com/leixiaohua1020/simplest_mediadata_test

开源中国:http://git.oschina.net/leixiaohua1020/simplest_mediadata_test

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/9422409

本项目包含如下几种视音频数据解析示例:
 (1)像素数据处理程序。包含RGB和YUV像素格式处理的函数。
 (2)音频采样数据处理程序。包含PCM音频采样格式处理的函数。
 (3)H.264码流分析程序。可以分离并解析NALU。
 (4)AAC码流分析程序。可以分离并解析ADTS帧。
 (5)FLV封装格式分析程序。可以将FLV中的MP3音频码流分离出来。

(6)UDP-RTP协议分析程序。可以将分析UDP/RTP/MPEG-TS数据包。

雷霄骅 (Lei Xiaohua)
leixiaohua1020@126.com
http://blog.csdn.NET/leixiaohua1020

转载于:https://www.cnblogs.com/sky-heaven/p/6478675.html

相关文章:

SVO(SVO: fast semi-direct monocular visual odometry)

SVO&#xff08;SVO: fast semi-direct monocular visual odometry&#xff09;翻译 文章目录SVO&#xff08;SVO: fast semi-direct monocular visual odometry&#xff09;翻译1、介绍2、系统概述3、符号4、运动估计4.1、 基于稀疏模型的图像对齐4.2、 通过特征对齐松弛4.3、…

MSER 候选车牌区域检测

#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> // Mser车牌目标检测 std::vector<cv::Rect> mserGetPlate(cv::Mat srcImage…

从HelloWorld看Knative Serving代码实现

为什么80%的码农都做不了架构师&#xff1f;>>> 摘要&#xff1a; Knative Serving以Kubernetes和Istio为基础&#xff0c;支持无服务器应用程序和函数的部署并提供服务。我们从部署一个HelloWorld示例入手来分析Knative Serving的代码细节。 概念先知 官方给出的这…

svo_note

SVO论文笔记1.frame overviews2. Motion Estimate Thread2.1 Sparse Model-based Image Alignment 基于稀疏点亮度的位姿预估2.2 Relaxation Through Feature Alignment 基于图块的特征点匹配2.3 Pose and Structure Refinement3 Mapping Thread3.1 depth-filter3.2 初始化参考…

Druid 配置 wallfilter

这个文档提供基于Spring的各种配置方式 使用缺省配置的WallFilter <bean id"dataSource" class"com.alibaba.druid.pool.DruidDataSource" init-method"init" destroy-method"close">...<property name"filters" v…

vue下的bootstrap table + jquery treegrid, treegrid无法渲染的问题

在mian.js导入的包如下&#xff1a;该bootstrap-table-treegrid.js需要去下载&#xff0c;在复制到jquery-treegrid/js/ 1 import $ from jquery 2 import bootstrap/dist/css/bootstrap.min.css 3 import bootstrap/dist/js/bootstrap.min 4 import bootstrap-table/dist/boot…

内存和缓存的区别

今天看书的时候又看到了内存和缓存&#xff0c;之所以说又&#xff0c;是因为之前遇到过查过资料&#xff0c;但是现在又忘了(图侵删)。 所以又复习一遍&#xff0c;记录一下&#xff0c;有所纰漏的地方&#xff0c;欢迎指正。 同志们&#xff0c;上图并不是内存和缓存中的任何…

【Boost】noncopyable:不可拷贝

【CSDN】&#xff1a;boost::noncopyable解析 【Effective C】&#xff1a;条款06_若不想使用编译器自动生成地函数&#xff0c;就该明确拒绝 1.example boost::noncopyable 为什么要boost::noncopyable 在c中定义一个类的时候&#xff0c;如果不明确定义拷贝构造函数和拷贝赋…

BigData NoSQL —— ApsaraDB HBase数据存储与分析平台概览

一、引言时间到了2019年&#xff0c;数据库也发展到了一个新的拐点&#xff0c;有三个明显的趋势&#xff1a; 越来越多的数据库会做云原生(CloudNative)&#xff0c;会不断利用新的硬件及云本身的优势打造CloudNative数据库&#xff0c;国内以阿里云的Cloud HBase、POLARDB为代…

ubuntu clion 创建桌面快捷方式

ubuntu clion 创建桌面快捷方式 首先在终端下输入 cd /usr/share/applications/进入applications目录下&#xff0c;建立一个clion.desktop文件 sudo touch clion.desktop然后在vim命令下编辑该文件 sudo vim clion.desktop进入vim后&#xff0c;按i插入开始编辑该文件&…

Flex 布局:语法篇

2019独角兽企业重金招聘Python工程师标准>>> 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display 属性 position 属性 float 属性。它对于那些特殊布局非常不方便&#xff0c;比如&#xff0c;垂直居中就不容易实现。 2009年&#xff0c;W3C 提…

特征运动点估计

cv::Mat getRansacMat(const std::vector<cv::DMatch>& matches, const std::vector<cv::KeyPoint>& keypoints1, const std::vector<cv::KeyPoint>& keypoints2, std::vector<cv::DMatch>& outMatches) {// 转换特征点格式std::vecto…

Vue+Element-ui+二级联动封装组件

通过父子组件传值 父组件&#xff1a; 1 <template>2 <linkage :citysList"citysList" :holder"holder" saveId"saveId"></linkage>3 </template>4 <script>5 import linkage from ./common/linkage6 export de…

MOG2 成员函数参数设定

pMOG2->setDetectShadows(true); // 背景模型影响帧数 默认为500 pMOG2->setHistory(1000); // 模型匹配阈值 pMOG2->setVarThreshold(50); // 阴影阈值 pMOG2->setShadowThreshold(0.7);前景中模型参数&#xff0c;设置为0表示背景&#xff0c;255为前景&#xff…

webpack 大法好 ---- 基础概念与配置(1)

再一次见面&#xff01; Light 还是太太太懒了&#xff0c;距离上一篇没啥营养的文章已经过去好多天。今天为大家介绍介绍 webpack 最基本的概念&#xff0c;以及简单的配置&#xff0c;让你能快速得搭建一个可用的 webpack 开发环境。 webpack的安装 webpack 运行于 node 环境…

Zookeeper迁移(扩容/缩容)

zookeeper选举原理在迁移前有必要了解zookeeper的选举原理&#xff0c;以便更科学的迁移。快速选举FastLeaderElectionzookeeper默认使用快速选举&#xff0c;在此重点了解快速选举&#xff1a;向集群中的其他zookeeper建立连接&#xff0c;并且只有myid比对方大的连接才会被接…

SVO Without ROS环境搭建

Installation: Plain CMake (No ROS) 首先&#xff0c;建立工作目录&#xff1a;workspace&#xff0c;然后把下面的需要的都在该目录下进行. mkdir workspace cd workspace Boost - c Librairies (thread and system are needed) sudo apt-get install libboost-all-dev Eige…

BackgroundSubtractorGMG 背景建模

#include <opencv2/bgsegm.hpp> #include <opencv2/video.hpp> #include <opencv2/opencv.hpp> #include <iostream> #include <sstream> using namespace cv; using namespace std; using namespace bgsegm; // GMG目标建模检测 void detectBac…

启动webpack-dev-server只能本机访问的解决办法

修改package.json的dev启动设置&#xff0c;增加--host 0.0.0.0启动后localhost更换为本机IP即可访问

TCP/IP:IP选项处理

引言 IP输入函数要对IP 进行选项处理&#xff0c;。RFC791和1122规定了IP选项和处理规则。一个IP首部可以跟40个字节的选项。 选项格式 选项的格式&#xff0c;分为两种类型&#xff0c;单字节和多字节。 ip_dooptions函数 这个函数用于判断分组转发。用常量位移访问IP选项字段…

【SVO2.0 安装编译】Ubuntu 20.04 + Noetic

ways one 链接: https://pan.baidu.com/s/1ZAkeD64wjFsDHfpCm1CB1w 提取码: kxx2 (downloads and use idirectly) ways two: [SVO2-OPEN: https://github.com/uzh-rpg/rpg_svo_pro_open](https://github.com/DEARsunshine/rpg_svo_pro_open)git挂梯子 如果各位终端无法挂梯…

人眼目标检测初始化

// 初始化摄像头读取视频流cv::VideoCapture cap(0);// 宽高设置为320*256cap.set(CV_CAP_PROP_FRAME_WIDTH, 320);cap.set(CV_CAP_PROP_FRAME_HEIGHT, 256);// 读取级联分类器// 文件存放在opencv\sources\data\haarcascades bool flagGlasses false;if(flagGlasses){face_ca…

Qt之界面换肤

简述 常用的软件基本都有换肤功能&#xff0c;例如&#xff1a;QQ、360、迅雷等。换肤其实很简单&#xff0c;并没有想象中那么难&#xff0c;利用前面分享过的QSS系列文章&#xff0c;沃我们完全可以实现各种样式的定制&#xff01; 简述实现原理效果新建QSS文件编写QSS代码加…

mongDB的常用操作总结

目录 常用查询:查询一条数据查询子元素集合:image.idgte: 大于等于,lte小于等于...查询字段不存在的数据not查询数量:常用更新更新第一条数据的一个字段:更新一条数据的多个字段:常用删除删除:常用查询: 查询一条数据 精确匹配is Query(Criteria.where("id").is(id))…

【GTSAM】GTSAM学习

1 what GTSAM ? GTSAM 是一个在机器人领域和计算机视觉领域用于平滑&#xff08;smoothing&#xff09;和建图&#xff08;mapping&#xff09;的C库。它与g2o不同的是&#xff0c;g2o采用稀疏矩阵的方式求解一个非线性优化问题&#xff0c;而GTSAM是采用因子图&#xff08;f…

人脸、人眼检测与跟踪

#include <opencv2/opencv.hpp> #include <iostream> #include <vector> using namespace cv;CascadeClassifier face_cascade; CascadeClassifier eye_cascade;// 人眼检测 int detectEye(cv::Mat& im, cv::Mat& tpl, cv::Rect& rect) {std::v…

linux下jdk简单配置记录

记录哈&#xff0c;搭建环境的时候&#xff0c;copy使用方便。 vim /etc/profile export JAVA_HOME/usr/java/jdk1.7.0_79export PATH$JAVA_HOME/bin:$PATHexport CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport JRE_HOME$JAVA_HOME/jreexport LANGzh_CN.UT…

Ubuntu中Could not get lock /var/lib/dpkg/lock解决方案

关于Ubuntu中Could not get lock /var/lib/dpkg/lock解决方案 转载于:https://www.cnblogs.com/daemonFlY/p/10916812.html

so库方法原理

动态库 So库&#xff0c;又动态名库&#xff0c;是Linux下最常见的文件之一&#xff0c;是一种ELF文件。这种so库是程序运行时&#xff0c;才会将这些需要的代码拷贝到对应的内存中。但程序运行时&#xff0c;这些地址早已经确定&#xff0c;那程序引用so库中的这些代码地址如…

上传图片,多图上传,预览功能,js原生无依赖

最近很好奇前端的文件上传功能&#xff0c;因为公司要求做一个支持图片预览的图片上传插件&#xff0c;所以自己搜了很多相关的插件&#xff0c;虽然功能很多&#xff0c;但有些地方不能根据公司的想法去修改&#xff0c;而且需要依赖jQuery或Bootstrap库&#xff0c;所以我就想…