视音频数据处理入门: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

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

本程序中的函数可以将YUV420P数据中的Y、U、V三个分量分离开来并保存成三个文件。函数的代码如下所示。
[cpp]  view plain  copy
  在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.   
  15.     unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  16.   
  17.     for(int i=0;i<num;i++){  
  18.   
  19.         fread(pic,1,w*h*3/2,fp);  
  20.         //Y  
  21.         fwrite(pic,1,w*h,fp1);  
  22.         //U  
  23.         fwrite(pic+w*h,1,w*h/4,fp2);  
  24.         //V  
  25.         fwrite(pic+w*h*5/4,1,w*h/4,fp3);  
  26.     }  
  27.   
  28.     free(pic);  
  29.     fclose(fp);  
  30.     fclose(fp1);  
  31.     fclose(fp2);  
  32.     fclose(fp3);  
  33.   
  34.     return 0;  
  35. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  16.     for(int i=0;i<num;i++){  
  17.         fread(pic,1,w*h*3,fp);  
  18.         //Y  
  19.         fwrite(pic,1,w*h,fp1);  
  20.         //U  
  21.         fwrite(pic+w*h,1,w*h,fp2);  
  22.         //V  
  23.         fwrite(pic+w*h*2,1,w*h,fp3);  
  24.     }  
  25.   
  26.     free(pic);  
  27.     fclose(fp);  
  28.     fclose(fp1);  
  29.     fclose(fp2);  
  30.     fclose(fp3);  
  31.   
  32.     return 0;  
  33. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  13.     for(int i=0;i<num;i++){  
  14.         fread(pic,1,w*h*3/2,fp);  
  15.         //Gray  
  16.         memset(pic+w*h,128,w*h/2);  
  17.         fwrite(pic,1,w*h*3/2,fp1);  
  18.     }  
  19.   
  20.     free(pic);  
  21.     fclose(fp);  
  22.     fclose(fp1);  
  23.     return 0;  
  24. }  


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

[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  12.     unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  13.   
  14.     for(int i=0;i<num;i++){  
  15.         fread(pic,1,w*h*3/2,fp);  
  16.         //Half  
  17.         for(int j=0;j<w*h;j++){  
  18.             unsigned char temp=pic[j]/2;  
  19.             //printf("%d,\n",temp);  
  20.             pic[j]=temp;  
  21.         }  
  22.         fwrite(pic,1,w*h*3/2,fp1);  
  23.     }  
  24.   
  25.     free(pic);  
  26.     fclose(fp);  
  27.     fclose(fp1);  
  28.   
  29.     return 0;  
  30. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  13.     unsigned char *pic=(unsigned char *)malloc(w*h*3/2);  
  14.   
  15.     for(int i=0;i<num;i++){  
  16.         fread(pic,1,w*h*3/2,fp);  
  17.         //Y  
  18.         for(int j=0;j<h;j++){  
  19.             for(int k=0;k<w;k++){  
  20.                 if(k<border||k>(w-border)||j<border||j>(h-border)){  
  21.                     pic[j*w+k]=255;  
  22.                     //pic[j*w+k]=0;  
  23.                 }  
  24.             }  
  25.         }  
  26.         fwrite(pic,1,w*h*3/2,fp1);  
  27.     }  
  28.   
  29.     free(pic);  
  30.     fclose(fp);  
  31.     fclose(fp1);  
  32.   
  33.     return 0;  
  34. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  21.     barwidth=width/barnum;  
  22.     lum_inc=((float)(ymax-ymin))/((float)(barnum-1));  
  23.     uv_width=width/2;  
  24.     uv_height=height/2;  
  25.   
  26.     data_y=(unsigned char *)malloc(width*height);  
  27.     data_u=(unsigned char *)malloc(uv_width*uv_height);  
  28.     data_v=(unsigned char *)malloc(uv_width*uv_height);  
  29.   
  30.     if((fp=fopen(url_out,"wb+"))==NULL){  
  31.         printf("Error: Cannot create file!");  
  32.         return -1;  
  33.     }  
  34.   
  35.     //Output Info  
  36.     printf("Y, U, V value from picture's left to right:\n");  
  37.     for(t=0;t<(width/barwidth);t++){  
  38.         lum_temp=ymin+(char)(t*lum_inc);  
  39.         printf("%3d, 128, 128\n",lum_temp);  
  40.     }  
  41.     //Gen Data  
  42.     for(j=0;j<height;j++){  
  43.         for(i=0;i<width;i++){  
  44.             t=i/barwidth;  
  45.             lum_temp=ymin+(char)(t*lum_inc);  
  46.             data_y[j*width+i]=lum_temp;  
  47.         }  
  48.     }  
  49.     for(j=0;j<uv_height;j++){  
  50.         for(i=0;i<uv_width;i++){  
  51.             data_u[j*uv_width+i]=128;  
  52.         }  
  53.     }  
  54.     for(j=0;j<uv_height;j++){  
  55.         for(i=0;i<uv_width;i++){  
  56.             data_v[j*uv_width+i]=128;  
  57.         }  
  58.     }  
  59.     fwrite(data_y,width*height,1,fp);  
  60.     fwrite(data_u,uv_width*uv_height,1,fp);  
  61.     fwrite(data_v,uv_width*uv_height,1,fp);  
  62.     fclose(fp);  
  63.     free(data_y);  
  64.     free(data_u);  
  65.     free(data_v);  
  66.     return 0;  
  67. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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 plain  copy
  在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.   
  15.     for(int i=0;i<num;i++){  
  16.         fread(pic1,1,w*h,fp1);  
  17.         fread(pic2,1,w*h,fp2);  
  18.   
  19.         double mse_sum=0,mse=0,psnr=0;  
  20.         for(int j=0;j<w*h;j++){  
  21.             mse_sum+=pow((double)(pic1[j]-pic2[j]),2);  
  22.         }  
  23.         mse=mse_sum/(w*h);  
  24.         psnr=10*log10(255.0*255.0/mse);  
  25.         printf("%5.3f\n",psnr);  
  26.   
  27.         fseek(fp1,w*h/2,SEEK_CUR);  
  28.         fseek(fp2,w*h/2,SEEK_CUR);  
  29.   
  30.     }  
  31.   
  32.     free(pic1);  
  33.     free(pic2);  
  34.     fclose(fp1);  
  35.     fclose(fp2);  
  36.     return 0;  
  37. }  

调用上面函数的方法如下所示。
[cpp]  view plain  copy
  在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的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值