一.基本原理
(1)RGB转YUV的公式
Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R -0.5870G -0.1140B B-Y=-0.2990R -0.5870G+0.8860B
为了使色差信号的动态范围控制在0.5之间,需要进行归一化,对色差信号引入压缩系数:
U=-0.1684R -0.3316G+0.5B V=0.5R -0.4187G -0.0813B
(2)YUV转RGB的公式
R=Y+1.4075(V-128)
G=Y-0.3455(U-128)-0.7169(V-128)
B=Y+1.779(U-128)
(3)分析
RGB文件中的数据存储方式:B1G1R1B2G2R2B3G3R3…
4:2:0YUV文件中数据的存储方式:Y1Y2…Y(256x256)U1U2…U(256x256x0.25)V1V2…V(256x256x0.25)
RGB转YUV格式时需要下采样,每2x2个像素单元公用一个U一个V;而YUV转RGB格式时需要上采样,每2x2个像素单元中,需要将共同的U和V分别复制给另外三个像素。
二.实验流程
(1)读入待转换的文件名,文件的宽高,设置输出的文件名
初始化main函数参数:项目名右键->属性->配置属性->调试->设置命令参数和工作目录
(2)初始化参数
FILE *rgbFile
FILE *yuvFile
int width,height
unsigned char *rgbBuffer
unsigned char *yBuffer,*uBuffer,*vBuffer
(3)打开两个文件(fopen),变量赋值,开buffer(malloc())
(4)将YUV文件的数据读入yBuffer,uBuffer,vBuffer,写rgbBuffer
(5)输出rgb文件
三.关键代码及分析
yuv2rgb.h
#ifndef YUV2RGB_H_ #define YUV2RGB_H_ int YUV2RGB (int x_dim, int y_dim, void *rgb_out, void *y_in, void *u_in, void *v_in, int flip); void InitLookupTable(); #endif main.cpp #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include "yuv2rgb.h" #define u_int8_t unsigned __int8 #define u_int unsigned __int32 #define u_int32_t unsigned __int32 #define FALSE false #define TRUE true int main(int argc, char** argv) { /* variables controlable from command line */ u_int frameWidth = 352; /* --width=<uint> */ u_int frameHeight = 240; /* --height=<uint> */ bool flip = TRUE; /* --flip */ /* internal variables */ char* yuvFileName = NULL;//初始化参数 char* rgbFileName = NULL; FILE* yuvFile = NULL; FILE* rgbFile = NULL; u_int8_t* rgbBuf = NULL; u_int8_t* yBuf = NULL; u_int8_t* uBuf = NULL; u_int8_t* vBuf = NULL; u_int32_t videoFramesWritten = 0; /* begin process command line */ /* point to the specified file names */ yuvFileName = argv[1]; rgbFileName = argv[2]; frameWidth = atoi(argv[3]);//字符串转整型 frameHeight = atoi(argv[4]); /* open the YUV file */ yuvFile = fopen(yuvFileName, "rb"); if (yuvFile == NULL) { printf("cannot find yuv file\n"); exit(1); } else { printf("The input yuv file is %s\n", yuvFileName); } /* open the RAW file */ rgbFile = fopen(rgbFileName, "wb"); if (rgbFile == NULL) { printf("cannot find rgb file\n"); exit(1); } else { printf("The output rgb file is %s\n", rgbFileName); } /* get an input buffer for a frame */ rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3); /* get the output buffers for a frame */ yBuf = (u_int8_t*)malloc(frameWidth * frameHeight); uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);//4:2:0取样,UV分量都只有Y分量的1/4 vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4); if (rgbBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL) { printf("no enought memory\n"); exit(1); } while(fread(yBuf, 1, frameWidth * frameHeight, yuvFile) &&fread(uBuf, 1, frameWidth * frameHeight/4, yuvFile) &&fread(vBuf, 1, frameWidth * frameHeight/4, yuvFile)) { if(YUV2RGB(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip)) { printf("error"); return 0; } fwrite(rgbBuf, 1, frameWidth * frameHeight*3, rgbFile); printf("\r...%d", ++videoFramesWritten); } printf("\n%u %ux%u video frames written\n", videoFramesWritten, frameWidth, frameHeight); /* cleanup */ if(rgbFile) fclose(rgbFile);//判断后再关闭文件或释放空间 if(yuvFile) fclose(yuvFile); if (yBuf) free(yBuf); if (uBuf) free(uBuf); if (vBuf) free(vBuf); if (rgbBuf) free(rgbBuf); getchar();//让界面卡住,显示在那里,等待输入任意字符后,程序才会结束退出,方便查看结果。 return(0); } yuv2rgb.cpp
#include "stdlib.h" #include "yuv2rgb.h" static float YUVRGB14075[256],YUVRGB03455[256]; static float YUVRGB07169[256],YUVRGB17790[256]; int YUV2RGB (int x_dim, int y_dim, void *rgb_out, void *y_in, void *u_in, void *v_in, int flip) { static int init_done = 0; long i, j, size; unsigned char *r, *g, *b; unsigned char *y, *u, *v; unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv; unsigned char *y_buffer, *u_buffer, *v_buffer; unsigned char *sub_u_buf, *sub_v_buf; short tr,tg,tb;//为防止数据溢出定义中间变量(中间变量存储的数值范围要大于256) if (init_done == 0) { InitLookupTable(); init_done = 1; } // check to see if x_dim and y_dim are divisible by 2 if ((x_dim % 2) || (y_dim % 2)) return 1; size = x_dim * y_dim; // allocate memory b= (unsigned char *)rgb_out; y_buffer = (unsigned char *)y_in; sub_u_buf = (unsigned char *)u_in; sub_v_buf = (unsigned char *)v_in; u_buffer = (unsigned char *)malloc(size * sizeof(unsigned char)); v_buffer = (unsigned char *)malloc(size * sizeof(unsigned char)); y= y_buffer; u= u_buffer; v= v_buffer; if (!(u_buffer&&v_buffer)) { if (u_buffer) free(u_buffer); if (v_buffer) free(v_buffer); return 2; } //上采样 for (j = 0; j < y_dim/2; j ++) { psu = sub_u_buf + j * x_dim / 2;//psu、psv指向已经下采样的YUV文件中的U、V首地址 psv = sub_v_buf + j * x_dim / 2; pu1 = u_buffer + 2 * j * x_dim;//在上采样中,pu1、pv1指向偶数行第一个元素,pu2、pv2指向奇数行第一个元素 pu2 = u_buffer + (2 * j + 1) * x_dim; pv1 = v_buffer + 2 * j * x_dim; pv2 = v_buffer + (2 * j + 1) * x_dim; for (i = 0; i < x_dim/2; i ++) { *pu1=*psu;//将psu复制给2x2的像素单元 *(pu1+1)=*psu; *pu2=*psu; *(pu2+1)=*psu; *pv1=*psv; *(pv1+1)=*psv; *pv2=*psv; *(pv2+1)=*psv; psu ++; psv ++; pu1 += 2; pu2 += 2; pv1 += 2; pv2 += 2; } } // convert YUV to RGB if (flip) { for (i = 0; i < size; i++) { g = b + 1; r = b + 2; tr=*y + YUVRGB14075[*v]; tg=*y - YUVRGB03455[*u] - YUVRGB07169[*v]; tb=*y + YUVRGB17790[*u];//先将计算出的RGB存入中间变量 if(tr<0) *r=0; else if(tr>255) *r=255; else *r = (unsigned char)(tr); if(tg<0) *g=0; else if(tg>255) *g=255; else *g = (unsigned char)(tg); if(tb<0) *b=0; else if(tb>255) *b=255; else *b = (unsigned char)(tb);//加判断防止数据溢出 b += 3; y ++; u ++; v ++; } } free(u_buffer); free(v_buffer); return 0; } void InitLookupTable()//事先计算数据 { int i; for (i = 0; i < 256; i++) YUVRGB14075[i] = (float)1.4075 * (i-128); for (i = 0; i < 256; i++) YUVRGB03455[i] = (float)0.3455 * (i-128); for (i = 0; i < 256; i++) YUVRGB07169[i] = (float)0.7169 * (i-128); for (i = 0; i < 256; i++) YUVRGB17790[i] = (float)1.779 * (i-128);//提前减去128保证UV的在-128~127之间 }
四.实验结果及分析
(1)未添加中间变量防止数据溢出时的结果
溢出的地方会变成红色
(2)加了中间变量后
误差部分的得到了修正
(3)处理前的YUV文件与处理后的YUV文件对比
因为程序中用到了一些数据类型转换并且做了防止溢出的处理,所以处理前后的文件在具体数值上会有少许差异,但是这种差别肉眼是无法分辨的。