视频原始数据叫做:视频像素数据
![](https://img-blog.csdn.net/20160118002918690)
本文分别介绍如下几个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三个分量分离开来并保存成三个文件。函数的代码如下所示。
-
-
-
-
-
-
-
-
- int simplest_yuv420_split(char *url, int w, int h,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_420_y.y","wb+");
- FILE *fp2=fopen("output_420_u.y","wb+");
- FILE *fp3=fopen("output_420_v.y","wb+");
-
- unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
-
- for(int i=0;i<num;i++){
-
- fread(pic,1,w*h*3/2,fp);
-
- fwrite(pic,1,w*h,fp1);
-
- fwrite(pic+w*h,1,w*h/4,fp2);
-
- fwrite(pic+w*h*5/4,1,w*h/4,fp3);
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
- fclose(fp2);
- fclose(fp3);
-
- return 0;
- }
调用上面函数的方法如下所示。
- 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三个分量分离开来并保存成三个文件。函数的代码如下所示。
-
-
-
-
-
-
-
-
- int simplest_yuv444_split(char *url, int w, int h,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_444_y.y","wb+");
- FILE *fp2=fopen("output_444_u.y","wb+");
- FILE *fp3=fopen("output_444_v.y","wb+");
- unsigned char *pic=(unsigned char *)malloc(w*h*3);
-
- for(int i=0;i<num;i++){
- fread(pic,1,w*h*3,fp);
-
- fwrite(pic,1,w*h,fp1);
-
- fwrite(pic+w*h,1,w*h,fp2);
-
- fwrite(pic+w*h*2,1,w*h,fp3);
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
- fclose(fp2);
- fclose(fp3);
-
- return 0;
- }
调用上面函数的方法如下所示。
- 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。
输入的原图如下所示。
![](https://img-blog.csdn.net/20160117232650727)
输出的三个文件的截图如下图所示。
![](https://img-blog.csdn.net/20160117232704546)
output_444_y.y
![](https://img-blog.csdn.net/20160117232711011)
output_444_u.y
![](https://img-blog.csdn.net/20160117232719517)
output_444_v.y
(3) 将YUV420P像素数据去掉颜色(变成灰度图)
本程序中的函数可以将YUV420P格式像素数据的彩色去掉,变成纯粹的灰度图。函数的代码如下。
-
-
-
-
-
-
-
- int simplest_yuv420_gray(char *url, int w, int h,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_gray.yuv","wb+");
- unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
-
- for(int i=0;i<num;i++){
- fread(pic,1,w*h*3/2,fp);
-
- memset(pic+w*h,128,w*h/2);
- fwrite(pic,1,w*h*3/2,fp1);
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
- return 0;
- }
调用上面函数的方法如下所示。
- 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的数值减半的方法,降低图像的亮度。函数代码如下所示。
-
-
-
-
-
-
-
- int simplest_yuv420_halfy(char *url, int w, int h,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_half.yuv","wb+");
-
- unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
-
- for(int i=0;i<num;i++){
- fread(pic,1,w*h*3/2,fp);
-
- for(int j=0;j<w*h;j++){
- unsigned char temp=pic[j]/2;
-
- pic[j]=temp;
- }
- fwrite(pic,1,w*h*3/2,fp1);
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
-
- return 0;
- }
调用上面函数的方法如下所示。
- 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格式的像素数据文件。输入的原图如下所示。
![](https://img-blog.csdn.net/20160117233038584)
处理后的图像如下所示。
(5)将YUV420P像素数据的周围加上边框
本程序中的函数可以通过修改YUV数据中特定位置的亮度分量Y的数值,给图像添加一个“边框”的效果。函数代码如下所示。
-
-
-
-
-
-
-
-
- int simplest_yuv420_border(char *url, int w, int h,int border,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_border.yuv","wb+");
-
- unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
-
- for(int i=0;i<num;i++){
- fread(pic,1,w*h*3/2,fp);
-
- for(int j=0;j<h;j++){
- for(int k=0;k<w;k++){
- if(k<border||k>(w-border)||j<border||j>(h-border)){
- pic[j*w+k]=255;
-
- }
- }
- }
- fwrite(pic,1,w*h*3/2,fp1);
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
-
- return 0;
- }
调用上面函数的方法如下所示。
- 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格式的像素数据文件。输入的原图如下所示。
![](https://img-blog.csdn.net/20160117233158395)
处理后的图像如下所示。
![](https://img-blog.csdn.net/20160117233215510)
(6) 生成YUV420P格式的灰阶测试图
本程序中的函数可以生成一张YUV420P格式的灰阶测试图。函数代码如下所示。
-
-
-
-
-
-
-
-
-
- int simplest_yuv420_graybar(int width, int height,int ymin,int ymax,int barnum,char *url_out){
- int barwidth;
- float lum_inc;
- unsigned char lum_temp;
- int uv_width,uv_height;
- FILE *fp=NULL;
- unsigned char *data_y=NULL;
- unsigned char *data_u=NULL;
- unsigned char *data_v=NULL;
- int t=0,i=0,j=0;
-
- barwidth=width/barnum;
- lum_inc=((float)(ymax-ymin))/((float)(barnum-1));
- uv_width=width/2;
- uv_height=height/2;
-
- data_y=(unsigned char *)malloc(width*height);
- data_u=(unsigned char *)malloc(uv_width*uv_height);
- data_v=(unsigned char *)malloc(uv_width*uv_height);
-
- if((fp=fopen(url_out,"wb+"))==NULL){
- printf("Error: Cannot create file!");
- return -1;
- }
-
-
- printf("Y, U, V value from picture's left to right:\n");
- for(t=0;t<(width/barwidth);t++){
- lum_temp=ymin+(char)(t*lum_inc);
- printf("%3d, 128, 128\n",lum_temp);
- }
-
- for(j=0;j<height;j++){
- for(i=0;i<width;i++){
- t=i/barwidth;
- lum_temp=ymin+(char)(t*lum_inc);
- data_y[j*width+i]=lum_temp;
- }
- }
- for(j=0;j<uv_height;j++){
- for(i=0;i<uv_width;i++){
- data_u[j*uv_width+i]=128;
- }
- }
- for(j=0;j<uv_height;j++){
- for(i=0;i<uv_width;i++){
- data_v[j*uv_width+i]=128;
- }
- }
- fwrite(data_y,width*height,1,fp);
- fwrite(data_u,uv_width*uv_height,1,fp);
- fwrite(data_v,uv_width*uv_height,1,fp);
- fclose(fp);
- free(data_y);
- free(data_u);
- free(data_v);
- return 0;
- }
调用上面函数的方法如下所示。
- 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。函数的代码如下所示。
-
-
-
-
-
-
-
-
- int simplest_yuv420_psnr(char *url1,char *url2,int w,int h,int num){
- FILE *fp1=fopen(url1,"rb+");
- FILE *fp2=fopen(url2,"rb+");
- unsigned char *pic1=(unsigned char *)malloc(w*h);
- unsigned char *pic2=(unsigned char *)malloc(w*h);
-
- for(int i=0;i<num;i++){
- fread(pic1,1,w*h,fp1);
- fread(pic2,1,w*h,fp2);
-
- double mse_sum=0,mse=0,psnr=0;
- for(int j=0;j<w*h;j++){
- mse_sum+=pow((double)(pic1[j]-pic2[j]),2);
- }
- mse=mse_sum/(w*h);
- psnr=10*log10(255.0*255.0/mse);
- printf("%5.3f\n",psnr);
-
- fseek(fp1,w*h/2,SEEK_CUR);
- fseek(fp2,w*h/2,SEEK_CUR);
-
- }
-
- free(pic1);
- free(pic2);
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
调用上面函数的方法如下所示。
- simplest_yuv420_psnr("lena_256x256_yuv420p.yuv","lena_distort_256x256_yuv420p.yuv",256,256,1);
对于8bit量化的像素数据来说,PSNR的计算公式如下所示。
上述公式中mse的计算公式如下所示。
![](https://img-blog.csdn.net/20160117233543104)
其中M,N分别为图像的宽高,xij和yij分别为两张图像的每一个像素值。PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。本程序输入的两张图像的对比图如下图所示。其中左边的图像为原始图像,右边的图像为受损图像。
![](https://img-blog.csdn.net/20160117233618041)
经过程序计算后得到的PSNR取值为26.693。PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。
(8) 分离RGB24像素数据中的R、G、B分量
本程序中的函数可以将RGB24数据中的R、G、B三个分量分离开来并保存成三个文件。函数的代码如下所示。
-
-
-
-
-
-
-
-
- int simplest_rgb24_split(char *url, int w, int h,int num){
- FILE *fp=fopen(url,"rb+");
- FILE *fp1=fopen("output_r.y","wb+");
- FILE *fp2=fopen("output_g.y","wb+");
- FILE *fp3=fopen("output_b.y","wb+");
-
- unsigned char *pic=(unsigned char *)malloc(w*h*3);
-
- for(int i=0;i<num;i++){
-
- fread(pic,1,w*h*3,fp);
-
- for(int j=0;j<w*h*3;j=j+3){
-
- fwrite(pic+j,1,1,fp1);
-
- fwrite(pic+j+1,1,1,fp2);
-
- fwrite(pic+j+2,1,1,fp3);
- }
- }
-
- free(pic);
- fclose(fp);
- fclose(fp1);
- fclose(fp2);
- fclose(fp3);
-
- return 0;
- }
调用上面函数的方法如下所示。
- 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色度图。该色度图右下为红色,上方为绿色,左下为蓝色,如下所示。
![](https://img-blog.csdn.net/20160117233802920)
R数据图像如下所示。
![](https://img-blog.csdn.net/20160117233815619)
G数据图像如下所示。
![](https://img-blog.csdn.net/20160117233828210)
B数据图像如下所示。
![](https://img-blog.csdn.net/20160117233838071)
(9)将RGB24格式像素数据封装为BMP图像
BMP图像内部实际上存储的就是RGB数据。本程序实现了对RGB像素数据的封装处理。通过本程序中的函数,可以将RGB数据封装成为一张BMP图像。
-
-
-
-
-
-
-
- int simplest_rgb24_to_bmp(const char *rgb24path,int width,int height,const char *bmppath){
- typedef struct
- {
- long imageSize;
- long blank;
- long startPosition;
- }BmpHead;
-
- typedef struct
- {
- long Length;
- long width;
- long height;
- unsigned short colorPlane;
- unsigned short bitColor;
- long zipFormat;
- long realSize;
- long xPels;
- long yPels;
- long colorUse;
- long colorImportant;
- }InfoHead;
-
- int i=0,j=0;
- BmpHead m_BMPHeader={0};
- InfoHead m_BMPInfoHeader={0};
- char bfType[2]={'B','M'};
- int header_size=sizeof(bfType)+sizeof(BmpHead)+sizeof(InfoHead);
- unsigned char *rgb24_buffer=NULL;
- FILE *fp_rgb24=NULL,*fp_bmp=NULL;
-
- if((fp_rgb24=fopen(rgb24path,"rb"))==NULL){
- printf("Error: Cannot open input RGB24 file.\n");
- return -1;
- }
- if((fp_bmp=fopen(bmppath,"wb"))==NULL){
- printf("Error: Cannot open output BMP file.\n");
- return -1;
- }
-
- rgb24_buffer=(unsigned char *)malloc(width*height*3);
- fread(rgb24_buffer,1,width*height*3,fp_rgb24);
-
- m_BMPHeader.imageSize=3*width*height+header_size;
- m_BMPHeader.startPosition=header_size;
-
- m_BMPInfoHeader.Length=sizeof(InfoHead);
- m_BMPInfoHeader.width=width;
-
- m_BMPInfoHeader.height=-height;
- m_BMPInfoHeader.colorPlane=1;
- m_BMPInfoHeader.bitColor=24;
- m_BMPInfoHeader.realSize=3*width*height;
-
- fwrite(bfType,1,sizeof(bfType),fp_bmp);
- fwrite(&m_BMPHeader,1,sizeof(m_BMPHeader),fp_bmp);
- fwrite(&m_BMPInfoHeader,1,sizeof(m_BMPInfoHeader),fp_bmp);
-
-
-
-
- for(j =0;j<height;j++){
- for(i=0;i<width;i++){
- char temp=rgb24_buffer[(j*width+i)*3+2];
- rgb24_buffer[(j*width+i)*3+2]=rgb24_buffer[(j*width+i)*3+0];
- rgb24_buffer[(j*width+i)*3+0]=temp;
- }
- }
- fwrite(rgb24_buffer,3*width*height,1,fp_bmp);
- fclose(fp_rgb24);
- fclose(fp_bmp);
- free(rgb24_buffer);
- printf("Finish generate %s!\n",bmppath);
- return 0;
- return 0;
- }
调用上面函数的方法如下所示。
- 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文件头的时候给其中的每个字段赋上合适的值就可以了。
- typedef struct tagBITMAPFILEHEADER
- {
- unsigned short int bfType;
- unsigned long bfSize;
- unsigned short int bfReserverd1;
- unsigned short int bfReserverd2;
- unsigned long bfbfOffBits;
- }BITMAPFILEHEADER;
- typedef struct tagBITMAPINFOHEADER
- {
- long biSize;
- long biWidth;
- long biHeight;
- short int biPlanes;
- short int biBitcount;
- short int biCompression;
- long biSizeImage;
- long biXPelsPermeter;
- long biYPelsPermeter;
- long biClrUsed;
- long biClrImportant;
- }BITMAPINFOHEADER;
BMP采用的是小端(Little Endian)存储方式。这种存储方式中“RGB24”格式的像素的分量存储的先后顺序为B、G、R。由于RGB24格式存储的顺序是R、G、B,所以需要将“R”和“B”顺序作一个调换再进行存储。
下图为输入的RGB24格式的图像lena_256x256_rgb24.rgb。
![](https://img-blog.csdn.net/20160117234249036)
下图分封装为BMP格式后的图像output_lena.bmp。封装后的图像使用普通的看图软件就可以查看。
![](https://img-blog.csdn.net/20160117234306638)