数据压缩原理与应用 彩色空间转换 实验报告

时间:2022-08-13 17:05:54

一、实验原理

1.图像文件的存储格式

图像在文件中均以数据流的形式存储,RGB格式图像在文件中按扫描行顺序,依次存储每一像素的B、G、R值;而YUV格式图像在文件中各分量独立保存,先存储一帧图像中所有像素的Y分量值,按扫描行顺序排列,紧接着再存储一帧图像所有像素的U分量值,最后是一帧图像所有像素的V分量值。

2.彩色空间转换的转换公式及分析

(1)由RGB转换为YUV公式:Y=0.2990R+0.5870G+0.1140B   U=-0.1684R-0.3316G+0.5B    V=0.5R-0.4187G-0.0813B

其中,为了使色差信号U、V的动态范围控制在0.5之间,对其做归一化处理,将色差信号R-Y、B-Y分别乘以压缩系数。

(2)相应地可计算出由YUV转换为RGB公式:R=Y+1.4020V   G=Y-0.3440U-0.7140V   B=Y+1.7720U

3.码电平分配及数字表达式

(1)亮电平信号量化后码电平分配

在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。

(2)色差信号量化后码电平分配

色差信号经过归一化处理后,动态范围为-0.5-0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。

4.色度格式

4:2:0格式是指色差信号U、V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。

 

二、实验流程分析

1.创建工程、头文件、2个源程序文件;

2.在工程-设置-调试中设置程序文件所在的工作目录和主函数所需的命令参数,读入待转换的文件名、输出文件名、图像的宽、高尺寸;

数据压缩原理与应用 彩色空间转换 实验报告

 

3.在主程序中设置初始化参数:定义输入、输出文件指针,定义宽、高变量并赋值,定义四个缓冲区指针:rgbBuf、yBuf、uBuf、vBuf;

4.用fopen函数打开文件,用malloc函数为四个缓冲区指针分配相应大小的动态内存,用fread函数将yuvFile的数据依次读入yBuf、uBuf、vBuf;

5.调用自定义的YUV2RGB函数,在yuv2rgb.cpp中定义YUV2RGB函数,先对U、V分量进行上采样,再调用查找表计算相应像素的R、G、B值,输出到rgbBuf;

6.在主程序中用fwrite函数将rgbBuf的数据写入rgbFile;

7.释放缓冲区,关闭文件。

 

三、关键代码及分析

yuv2rgb.h:

头文件里包含所用函数的申明:

#ifndef YUV2RGB_H_
#define YUV2RGB_H_

int YUV2RGB(int x_dim,int y_dim,void *y_in,void *u_in,void *v_in,void *rgb_out,int flip);

void InitLookupTable();

#endif

main.cpp:

1.为四个缓冲区指针分配相应大小的动态内存:因为YUV色度格式按4:2:0采样,所以uBuf、vBuf大小为YBuf的四分之一,即(frameWidth * frameHeight) / 4)。

rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3);
yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);


2.用fread函数将yuvFile的数据依次读入yBuf、uBuf、vBuf,文件指针yuvFile随着数据块的读出自动移位到所操作的位置;调用YUV2RGB函数后,再将rgbBuf的数据写入rgbFile。

while (fread(yBuf,1,frameWidth*frameHeight,yuvFile))
{
while (fread(uBuf,1,(frameWidth*frameHeight)/4,yuvFile))
{
while (fread(vBuf,1,(frameWidth*frameHeight)/4,yuvFile))
{
if(YUV2RGB(frameWidth, frameHeight, yBuf, uBuf, vBuf,rgbBuf))
{
printf("error");
return 0;
}

fwrite(rgbBuf, 1, frameWidth * frameHeight*3, rgbFile);

}
}
}

yuv2rgb.cpp:

#include "stdlib.h"
#include "yuv2rgb.h"

static float YUVRGB1402[256],YUVRGB1772[256];
static float YUVRGB0344[256],YUVRGB0714[256];////////定义查找表所用公式系数的浮点型数组,数组大小为量化级范围256


int YUV2RGB (int x_dim,int y_dim,void *y_in,void *u_in,void *v_in,void *rgb_out)////////函数的定义,参数包括宽、高、四个缓冲区指针
{

static int init_done = 0;/////定义调用查找表时的控制变量,初始化为0

long i, j, size;///////定义循环变量和尺寸变量
float r_tmp,g_tmp,b_tmp;///////为防止r、g、b值溢出,定义浮点型中间变量来限定溢出值
unsigned char *r, *g, *b;
unsigned char *y, *u, *v;

unsigned char *y_buffer, *u_buffer, *v_buffer,*rgb_buffer;//////定义buffer指针
unsigned char *add_u_buffer, *add_v_buffer;///////定义上采样后的U、V缓冲区指针
unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;///////定义上采样时缓冲区的移位指针


if (init_done == 0)
{
InitLookupTable();///////调用查找表,计算各量化级的公式系数
init_done = 1;
}


if ((x_dim % 2) || (y_dim % 2)) return 1;//////判断图像宽、高是否为偶数,以确定上采样格式
size = x_dim * y_dim;////////尺寸变量赋值


y_buffer = (unsigned char *)y_in;
u_buffer = (unsigned char *)u_in;
v_buffer = (unsigned char *)v_in;
rgb_buffer=(unsigned char *)rgb_out;////////将YUV2RGB函数的void型形参转换成无符号字符型指针
add_u_buffer=(unsigned char *)malloc(size);
add_v_buffer=(unsigned char *)malloc(size);//////为上采样后的U、V缓冲区指针分配动态内存

b = rgb_buffer;
y = y_buffer;
u = add_u_buffer;
v = add_v_buffer;

for (j = 0; j < y_dim/2; j ++)//////////////////////////j分量控制行数循环
{
psu = u_buffer + j * x_dim / 2;////////////psu指针指向原始u_buffer的起始位置,通过变量j控制其移位,每操作完一行,psu加x_dim/2指向u_buffer的下一行
psv = v_buffer + j * x_dim / 2;////////////v分量缓冲区的相应移位指针操作同u
pu1 = add_u_buffer + 2 * j * x_dim;////////pu1指针指向上采样后add_u_buffer的偶数行起始位,每操作完一行,pu1加x_dim指向add_u_buffer的下一偶数行
pu2 = add_u_buffer + (2 * j + 1) * x_dim;//////pu2指针指向上采样后add_u_buffer的奇数行起始位,每操作完一行,pu2加x_dim指向add_u_buffer的下一奇数行
pv1 = add_v_buffer + 2 * j * x_dim;
pv2 = add_v_buffer + (2 * j + 1) * x_dim;
for (i = 0; i < x_dim/2; i ++)//////////////////////////i分量控制列数循环
{
*pu1=*psu;
*(pu1+1)=*psu;
*pu2=*psu;
*(pu2+1)=*psu;///////////////将u_buffer中的u值分别赋给add_u_buffer中相应邻域的4个像素
*pv1=*psv;
*(pv1+1)=*psv;
*pv2=*psv;
*(pv2+1)=*psv;
psu ++;///////////////////每操作一次psu向后移一位
psv ++;
pu1 += 2;
pu2 += 2;//////////////每操作一次pu1、pu2向后移2位
pv1 += 2;
pv2 += 2;
}
}

for (i = 0; i < size; i++)////////////////将上采样后缓冲区的Y、U、V值计算得到相应像素的R、G、B值
{
g = b + 1;
r = b + 2;////////////RGB文件中图像每一像素按B、G、R依次排列
r_tmp= (*y)+YUVRGB1402[*v];/////////////为中间变量赋值
*r=(r_tmp>255?255:(r_tmp<0?0:(unsigned char)r_tmp));//////////若计算的r值大于255(即溢出),将相应r值赋值为255;若计算的r值小于0,将相应r值赋值为0;
g_tmp=(*y)-YUVRGB0344[*u]-YUVRGB0714[*v];
*g=(g_tmp>255?255:(g_tmp<0?0:(unsigned char)g_tmp));
b_tmp=(*y)+YUVRGB1772[*u];
*b=(b_tmp>255?255:(b_tmp<0?0:(unsigned char)b_tmp));
b += 3;
y ++;
u ++;
v ++;
}
if(add_u_buffer!=NULL)
{
free(add_u_buffer);
}
if(add_v_buffer!=NULL)
{
free(add_v_buffer);
}

////////////////////////////////////////////释放缓冲区add_u_buffer、add_v_buffer,为避免重复释放,先进行判断

return 0;
}


void InitLookupTable()//////////////定义查找表函数
{
int i;

for (i = 0; i < 256; i++) YUVRGB1402[i] = (float)1.402 * (i-128);
for (i = 0; i < 256; i++) YUVRGB0344[i] = (float)0.344 * (i-128);
for (i = 0; i < 256; i++) YUVRGB0714[i] = (float)0.714 * (i-128);
for (i = 0; i < 256; i++) YUVRGB1772[i] = (float)1.772 * (i-128);//////////计算各量化级的公式系数,由于在RGB2YUV程序中,为了使色差零电平对应128,U、V值均加了128,所以这里要相应地减去128
}


 

四、实验结果分析

将down.rgb文件通过RGB2YUV程序后得到down.yuv文件,再将down.yuv通过YUV2RGB程序转换为test.rgb文件,再通过RGB2YUV程序得到test.yuv。将down.yuv和test.yuv文件用YUVplayer查看,图像如下:

数据压缩原理与应用 彩色空间转换 实验报告

数据压缩原理与应用 彩色空间转换 实验报告

数据压缩原理与应用 彩色空间转换 实验报告

 

可以看到,两张YUV图像虽然存在一定的色度变化,但是用肉眼难以分辨出差异,图像转换失真度小。

若不限定溢出,直接将浮点型中间变量类型转换,转换后的图像会出现噪点和颜色失真的像素点,如下图:

*r=(unsigned char )r_tmp;

*g=(unsigned char )g_tmp;

*b=(unsigned char )b_tmp;

数据压缩原理与应用 彩色空间转换 实验报告

 

另外给出3个YUV图像文件与其通过YUV2RGB和RGB2YUV转换后得到的test.yuv的对比:

数据压缩原理与应用 彩色空间转换 实验报告

数据压缩原理与应用 彩色空间转换 实验报告

数据压缩原理与应用 彩色空间转换 实验报告

可以看到,原图与转换后的图像基本相同,以此证明编写的YUV2RGB程序是正确的。

 

五、实验结论:

RGB图像文件与YUV图像文件可以进行彩色空间互换,虽然在下采样过程中,通过邻域像素U、V值取平均的方法会丢失一部分数据,产生色度失真,且无法在后续恢复原始色度值,但是这种失真度较小,人眼难以分辨。