彩色空间转换
一、实验目的
1.基本要求:编写RGB转化为YUV程序,重点掌握函数定义,部分查找表的初
始化和调用,缓冲区分配。将得到的RGB文件转换为YUV文件,用YUV Viewer播放器观
看,验证是否正确。
2.提高要求:编写将YUV转换为RGB的程序。将给定的实验数据用该程序转换
为RGB文件。并与原RGB文件进行比较,如果有误差,分析误差来自何处。
二、转换公式
1、RGB to 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;
再给UV分量各+128去掉负值,使变化范围都在[0,255]之间,最终得出公式:
Y=0.2990R+0.5870G+0.1140B;
U=-0.1684R-0.3316G+0.5B+128;
V=0.5R-0.4187G-0.0813B+128;
2、YUV to RGB
将前面的式子反解出rgb即可,且再给UV分量各-128代入计算才能得到正确范围的值:
R=Y+1.4075(V-128);
G=Y-0.3455(U-128)-0.7169(V-128);
B=Y+1.779(U-128);
注意:rgb的图像是倒像的,转换成yuv所用的数据也是反着排,所以需要反转一下得到正着的图像。
三、主要思路
首先明确程序分为两个主要部分:
- 在main文件里实现文件的读写和函数的调用;
- 在rgb2yuv函数实现空间转换的算法。
1、RGB to YUV
读入的文件,输出的文件和宽、高都以参数形式设置,可以直接fread已有rgb文件,我们给原rgb文件整体开辟一个存储空间rgbBuf,并读取其数据;yuv分别开辟三个空间,以便指针的设置。
通过计算将每个像素的rgb数据转化为yuv后,此时的yuv各为256256的大小,而真正的uv分别为128128,是计算所得的1/4,因此需要进行下采样,每四个U/V形成一个新的U/V,最后输出数据存入down256x256_converted.yuv中。
根据老师给的标准程序,形成一个思路梗概:
2、RGB to YUV
我们给原yuv文件整体开辟一个存储空间yuvBuf,再4:2:2的格式下,y为256256,uv分别为128128,因此需要进行上采样,每一个U/V的值要赋给其周围的三个点,最终也形成256*256的数据空间。此时可以利用公式对其进行rgb转化,最后输出数据存入down256x256_converted.rgb中。
四、查找表的使用
所有需要计算的数r、g、b、y、u、v都在[0,255]取值,因此可以利用查找表将所有可能的计算结果全部算出来,并声明,然后直接使用:
1、RGB to YUV
static float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
static float RGBYUV01684[256], RGBYUV03316[256];
static float RGBYUV04187[256], RGBYUV00813[256];
算出所有可能值并作为查找表:
void InitLookupTable()
{
int i;
for (i = 0; i < 256; i++) RGBYUV02990[i] = (float)0.2990 * i;
for (i = 0; i < 256; i++) RGBYUV05870[i] = (float)0.5870 * i;
for (i = 0; i < 256; i++) RGBYUV01140[i] = (float)0.1140 * i;
for (i = 0; i < 256; i++) RGBYUV01684[i] = (float)0.1684 * i;
for (i = 0; i < 256; i++) RGBYUV03316[i] = (float)0.3316 * i;
for (i = 0; i < 256; i++) RGBYUV04187[i] = (float)0.4187 * i;
for (i = 0; i < 256; i++) RGBYUV00813[i] = (float)0.0813 * i;
}
这使得程序效率大大提高:
*y = (unsigned char)( RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128);
*v = (unsigned char)( (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
2、YUV to RGB
static float YUVRGB14075[256];
static float YUVRGB03455[256], YUVRGB07169[256];
static float YUVRGB17790[256];
查找表:
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.7790 * (i-128);
}
五、程序实现
1、RGB to YUV
老师已经给出完整程序,此处给出我认为关键的地方。
转换函数:
// convert RGB to YUV
if (!flip) {
for (j = 0; j < y_dim; j ++)
{
y = y_buffer + (y_dim - j - 1) * x_dim;
u = u_buffer + (y_dim - j - 1) * x_dim;
v = v_buffer + (y_dim - j - 1) * x_dim;
for (i = 0; i < x_dim; i ++) {
g = b + 1;
r = b + 2;
*y = (unsigned char)( RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128);
*v = (unsigned char)( (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
b += 3;
y ++;
u ++;
v ++;
}
}
} else {
for (i = 0; i < size; i++)
{
g = b + 1;
r = b + 2;
*y = (unsigned char)( RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128);
*v = (unsigned char)( (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
b += 3;
y ++;
u ++;
v ++;
}
}
下采样:
// subsample UV
for (j = 0; j < y_dim/2; j ++)
{
psu = sub_u_buf + j * x_dim / 2;
psv = sub_v_buf + j * x_dim / 2;
pu1 = u_buffer + 2 * j * x_dim;
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 ++)
{
*psu = (*pu1 + *(pu1+1) + *pu2 + *(pu2+1)) / 4;
*psv = (*pv1 + *(pv1+1) + *pv2 + *(pv2+1)) / 4;
psu ++;
psv ++;
pu1 += 2;
pu2 += 2;
pv1 += 2;
pv2 += 2;
}
}
2、YUV to RGB
main函数
分别为准备生成的rgb文件和需要处理的yuv文件分配buffer——rgbBuf、yuvBuf。yBuf用于代表y的数据部分,这256x256个数据不需要后续处理可以直接用。
// internal variables
char* rgbFileName = NULL;
char* yuvFileName = NULL;
FILE* rgbFile = NULL;
FILE* yuvFile = NULL;
unsigned char* rgbBuf = NULL;
unsigned char* yuvBuf = NULL;
unsigned char* yBuf = NULL;
u_int32_t videoFramesWritten = 0;
yuvBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3/2);
rgbBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3);
yBuf = (unsigned char*)malloc(frameWidth * frameHeight);
根据示例,打开文件时进行一些基于程序严谨性的周全的限制:
// 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);
}
考虑buffer为空的情况:
if (yuvBuf == NULL || rgbBuf == NULL || yBuf == NULL)
{
printf("no enought memory\n");
exit(1);
}
YUV2RGB(frameWidth, frameHeight, yBuf,rgbBuf);
if (rgbBuf==NULL)
{
printf("wrong change\n");
}
读写关文件:
fread(yuvBuf, sizeof(unsigned char), frameWidth * frameHeight * 3/2, yuvFile);
fwrite(rgbBuf, sizeof(unsigned char), frameWidth * frameHeight * 3, rgbFile);
fclose(rgbFile);
fclose(yuvFile);
头文件yuv2rgb.h处的声明
int YUV2RGB (int x_dim, int y_dim, unsigned char *ymp,unsigned char *rgb_out);
void InitLookupTable();
上采样:
原理如图:
设置psu psv指向yuvBuf的128*128的UV两个数据区间的首数据地址,pu1、pu2和pv1、pv2分别指向为上采样建立的数据空间up_u_buf和up_v_buf的首数据地址。
yuvBuf在函数部分表示为ymp指针指向的256x256x2/3的区域;rgbBuf在函数部分表示为rgb_out指针指向的256x256x3的区域。
long i, j, size;//size为x_dim * y_dim;
unsigned char *y, *u, *v;
unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;
unsigned char *up_u_buf, *up_v_buf;
unsigned char *rgb_buffer;
int r, g, b;
up_u_buf = (unsigned char *)malloc(size * sizeof(unsigned char));
up_v_buf = (unsigned char *)malloc(size * sizeof(unsigned char));
y = (unsigned char *)ymp;
u = ymp+size;
v = ymp+size+size/4;
rgb_buffer=rgb_out;
下面进行上采样:
// upsample UV
for(i = 0; i < y_dim/2; j ++)
{
psu = u + i * x_dim / 2;//psu psv分别指向原u v
psv = v + i * x_dim / 2;
pu1 = up_u_buf + 2 * i *x_dim;//pu1 pu2 /pv1 pv2分别指向u/v的奇数 偶数行
pu2 = up_u_buf + (2 * i + 1) * x_dim;
pv1 = up_v_buf + 2 * i *x_dim;
pv2 = up_v_buf + (2 * i + 1) * x_dim;
for (j = 0; j < x_dim/2; j ++)
{
*pu1=*psu;//赋值给周围4宫格
*(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;//指向下一个4宫格
}
}
转换函数:
// convert YUV to RGB
for (j = 0; j < y_dim; j ++)
{
for (i = 0; i < x_dim; i ++)
{
r = *y + YUVRGB14075[*up_v_buf];
g = *y - YUVRGB03455[*up_u_buf]- YUVRGB07169[*up_v_buf];
b = *y + YUVRGB17790[*up_u_buf];
//防溢出
if(r>255) r=255; if(r<0) r=0;
if(g>255) g=255; if(g<0) g=0;
if(b>255) b=255; if(b<0) b=0;
*rgb_buffer = unsigned char(b);
*(rgb_buffer + 1) = unsigned char(g);
*(rgb_buffer + 2) = unsigned char(r);
y ++;//转到下一个yuv数据组
up_u_buf ++;
up_v_buf ++;
rgb_buffer +=3;//后移bgr共三位
}
}
六、实验结果
原rgb:
转换为yuv:
原yuv:
转换为rgb: