一、实验原理
R、G、B三基色信号转化成Y、R-Y、B-Y的公式如下:
Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R-0.5870G-0.1140B
B-Y=-0.2990R-0.5870G+0.8860B
(一)RGB转换为YUV的公式
为使R-Y、B-Y的信号动态范围控制在0.5之间,对色差信号进行归一化,引入了幅度压缩系数,得到模拟视频分量CR、CB ,用V表示CR,U表示CB:
CR=0.7130(R-Y) CB=0.5640(B-Y)
V=0.5000R-0.4187G-0.0813B
U=-0.1684R-0.3316G+0.5000B
为了便于数字化处理,再将CR、CB引入128的偏置,得到的C'R、C'B也用V和U表示:
V=0.7130(R-Y)+128
U=0.5640(B-Y)+128
(二)YUV转换为RGB的公式
将YUV转换成RGB时,根据上式可以得出运算公式:
R=Y+1.4025*(V-128)
G=Y-0.7143*(V-128)-0.3443*(U-128)
B=Y+1.7730*(U-128)
(三)取样格式及空间分配
取样格式是4:2:0,意味着色差信号V、U的取样频率是亮度信号Y的四分之一,水平方向和垂直方向的点数均为Y的一半。所以RGB转换为YUV时进行下采样,即在包含4个样点的2×2的方格中,计算得到4个Y值,并对4个RGB值进行V和U的运算,再求均值得到最终值。YUV转换为RGB时要进行上采样,将U、V恢复成与Y相同的个数,再进行运算。所以在一帧图像中,给RGB开辟的空间大小应为(宽×高×3)字节,给Y开辟的空间大小应为(宽×高)字节,给U和V分别开辟的空间大小应为(宽×高×0.25)字节。(默认量化比特数为8bit,即每个像素占一字节)
二、实验的流程分析
1.先读入待转换的文件名、宽高以及输出文件名。
2.在程序开始设置初始化参数:定义宽高等变量,用malloc函数为缓冲区开内存,注意buffer是无符号整型。
3.打开两个文件,给变量赋值,读数据到y_Buffer、u_Buffer、v_Buffer中,并从中取数据逐步计算,结果放入rgb_Buffer中。
4.输出结果到外部文件中,用YUVviewer验证是否正确。
5.程序编译成功后,未运行前,点击工程->设置->调试,需要输入工作目录和程序变量,否则会因找不到图片而得不到结果。
6.用YUVviewer验证结果时,图像尺寸别忘记选择,否则不能出现正确图像(如左下图)。
三、实验的相关代码
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);//函数参数要保持一致
void InitLookupTable();
#endif
yuv2rgb.cpp
#include "stdlib.h"
#include "yuv2rgb.h"
static float YUVRGB14025[256], YUVRGB07143[256], YUVRGB03443[256],YUVRGB17730[256];
int YUV2RGB (int x_dim,int y_dim,void *rgb_out,void *y_in,void *u_in,void *v_in)
{
static int init_done = 0;
long i, j, size;
unsigned char *r, *g, *b, *rgb;
unsigned char *y, *u, *v , *up_u, *up_v;
unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;//定义相关的指针
unsigned char *y_buffer, *u_buffer, *v_buffer,*rgb_buffer;//定义原rgb,y,u,v的缓冲空间
unsigned char *up_u_buffer, *up_v_buffer;//定义上采样后的u,v的缓冲空间
float r_,g_,b_;//定义中间变量,判断强制类型转换后是否越界
if (init_done == 0)
{
InitLookupTable();
init_done = 1;
}//初始化查找表
if ((x_dim % 2) || (y_dim % 2)) return 1;
size = x_dim * y_dim;
rgb_buffer = (unsigned char *)rgb_out;
y_buffer = (unsigned char *)y_in;
u_buffer = (unsigned char *)u_in;
v_buffer = (unsigned char *)v_in;
up_u_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
up_v_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));//为y,u,v,rgb,up_u,up_v开辟缓冲空间
if (up_u_buffer == NULL || up_v_buffer == NULL) return 2;
rgb = rgb_buffer;
y = y_buffer;
u = u_buffer;
v = v_buffer;
up_u = up_u_buffer;
up_v = up_v_buffer;
//对u,v上采样
for (j = 0; j < y_dim/2; j ++)
{
psu = u + j*x_dim/2;
psv = v + j*x_dim/2;
pu1 = up_u + 2 * j * x_dim;//pu1指向偶数行
pu2 = up_u + (2 * j + 1)*x_dim;//pu2指向奇数行
pv1 = up_v + 2 * j * x_dim;
pv2 = up_v + (2 * j + 1)*x_dim;
for (i = 0; i < x_dim/2; i ++)
{
*pu1 = *psu ;
*(pu1 + 1) = *psu;
*pu2 = *psu;
*(pu2 + 1) = *psu;
*pv1 = *psv ;
*(pv1 + 1) = *psv;
*pv1 = *psv;
*(pv2 + 1) = *psv;
psu++;
psv++;
pu1 += 2;
pu2 += 2;
pv1 += 2;
pv2 += 2;
}
}
//YUV转换为RGB
for(j = 0; j< y_dim;j++)
{
for (i = 0; i < x_dim; i ++)
{
b = rgb;
g = rgb+ 1;
r = rgb + 2;//一定注意:RGB图像按B、G、R顺序排列
r_ = (*y + YUVRGB14025[*v]);//r
g_ = (*y - YUVRGB03443[*u] - YUVRGB07143[*v]);//g
b_ = (*y + YUVRGB17730[*u]);//b
//对超出范围的值进行处理
*b=(b_<0?0:(b_>255?255:(unsigned char)b_));
*g=(g_<0?0:(g_>255?255:(unsigned char)g_));
*r=(r_<0?0:(r_>255?255:(unsigned char)r_));
rgb += 3;
y++;
up_u++;
up_v++;
}
}
if (up_u_buffer != NULL)
{
free(up_u_buffer);
}
if (up_v_buffer != NULL)
{
free(up_v_buffer);
}//释放缓冲区,其他缓冲区前面已释放,不要重复释放
return 0;
}
void InitLookupTable()
{
int i;
for(i=0;i<256;i++) YUVRGB14025[i]=(float)1.4025*(i-128);
for(i=0;i<256;i++) YUVRGB07143[i]=(float)0.7143*(i-128);
for(i=0;i<256;i++) YUVRGB03443[i]=(float)0.3443*(i-128);
for(i=0;i<256;i++) YUVRGB17730[i]=(float)1.7730*(i-128);
} //查找表的函数体,方便计算
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
int main(int argc,char** argv)
{
u_int frameWidth = 352;
u_int frameHeight = 240;
char* yuvFileName = NULL;
char* rgbFileName = NULL;
FILE* yuvFile = NULL;
FILE* rgbFile = NULL;
u_int8_t* yBuf = NULL;
u_int8_t* uBuf = NULL;
u_int8_t* vBuf = NULL;
u_int8_t* rgbBuf = NULL;//开4个缓冲区,分别存yuv和rgb
u_int32_t videoFramesWritten = 0;
yuvFileName = argv[1];
rgbFileName = argv[2];
frameWidth = atoi(argv[3]);//atoi是转整型
frameHeight = atoi(argv[4]);
/*打开yuv文件*/
yuvFile = fopen(yuvFileName,"rb");//注意这里的打开模式是读“rb”
if(yuvFile == NULL)
{
printf("Cannot find yuv file!\n");
exit(1);
}
else
{
printf("The input yuv file is %s\n",yuvFileName);
}
/*打开rgb文件*/
rgbFile = fopen(rgbFileName,"wb");//这里的打开模式是写“wb”
if(rgbFile == NULL)
{
printf("Cannot find rgb file!\n");
exit(1);
}
else
{
printf("The output rgb file is %s\n",rgbFileName);
}
/* 开辟缓冲区 */
yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3 );
/*判断缓冲区是否存在*/
if (yBuf == NULL || uBuf == NULL || vBuf == NULL || rgbBuf == 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))
{
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);
/*释放缓冲区,关闭文件*/
if(rgbFile!=NULL) fclose(rgbFile);
if(yuvFile!=NULL) fclose(yuvFile);
if(rgbBuf!=NULL) free(rgbBuf);
if(yBuf!=NULL) free(yBuf);
if(uBuf!=NULL) free(uBuf);
if(vBuf!=NULL) free(vBuf);
return(0);
}
在实验过程中,遇到了一些问题。
1.强制类型转换时,如果超出0-255的范围,图像可能会出现噪点。
对超出范围的值进行了处理,原始代码如下:
g = b + 1;
r = b + 2;
r_ = (*y + YUVRGB14025[*v]);//r
g_ = (*y - YUVRGB03443[*u] - YUVRGB07143[*v]);//g
b_ = (*y + YUVRGB17730[*u]);//b
//对超出范围值的处理
if (b_ < 0) (unsigned char *)b = 0;
if (b_ > 255) (unsigned char *)b = 255;
if (g_ < 0) (unsigned char *)g = 0;
if (g_ > 255) (unsigned char *)g = 255;
if (r_ < 0) (unsigned char *)r = 0;
if (r_ > 255) (unsigned char *)r = 255;
出错:error C2440: '=' :cannot convert from 'const int' to 'unsignedchar*'
这里的r,g,b都是常量整型,不能给它强制转换成无符号的指针。
所以修改了一下:
b = rgb;
g = rgb+ 1;
r = rgb + 2;
r_ = (unsigned char)(*y + YUVRGB14025[*v]);//r
g_ = (unsigned char)(*y - YUVRGB03443[*u] - YUVRGB07143[*v]);//g
b_ = (unsigned char)(*y + YUVRGB17730[*u]);//b
//对超出范围值的处理
if (b_ < 0) b_= 0;
if (b_ > 255) b_= 255;
if (g_ < 0) g_= 0;
if (g_ > 255) g_= 255;
if (r_ < 0) r_= 0;
if (r_ > 255) r_= 255;
发现虽然编译没问题了,但在计算的时候就进行强制类型转换,很容易丢掉一些数据,所以对代码继续进行改进,并优化了一些语句,使其更简洁明了。
g = b+ 1;
r = b + 2;//一定注意:RGB图像按B、G、R顺序排列
r_ = (*y + YUVRGB14025[*v]);//r
g_ = (*y - YUVRGB03443[*u] - YUVRGB07143[*v]);//g
b_ = (*y + YUVRGB17730[*u]);//b
//对超出范围的值进行处理,要注意强制类型转换的对象
*b=(b_<0?0:(b_>255?255:(unsigned char)b_));
*g=(g_<0?0:(g_>255?255:(unsigned char)g_));
*r=(r_<0?0:(r_>255?255:(unsigned char)r_));
2.编译成功后,运行的时候程序发生中断。在VisualStudio环境下进行调试,F9打断点后,显示76CF919F处push有问题。
EBP在这里表示的是pu1栈帧的底部,它的值一直报错。经过查看pu1的算法,发现给up_u_buffer缓冲区定义错误,将它和u_buffer的定义混淆了。
错误定义是:
up_u_buffer = (unsigned char *)u_in;
u_buffer = (unsigned char *)malloc(size*sizeof(unsigned char));
两者交换后,得到正确代码:
u_buffer = (unsigned char *)u_in;
up_u_buffer = (unsigned char *)malloc(size*sizeof(unsigned char));
四、实验结果及分析
左图down.yuv是从down.rgb转化而来,中图test.yuv经历了down.yuv->test.rgb->test.yuv,右图是二者的差值图像。
两个图像有色差,是因为rgb转yuv进行下采样时丢掉了一部分数据,yuv再转rgb时上采样,数据已有一些损失。
以下三个例子,左边是原图YUV,右边是转换后的YUV。
五、结论
1.malloc和free是一对函数,为指针开辟和释放内存。开辟后不要忘记释放,也不能重复释放,在释放前用if语句进行判断,否则程序可能会中断运行。fopen和fclose用法也是这样。
2.指针作为函数的参数传递,其作用是可以改变指针指向的区域变量。
3.强制类型转换时,如果对超出范围的值不进行处理,图像可能会出现噪点。
4.给程序查错的时候,在Visual Studio的调试环境下先找到出错的地方,F9打断点,一步一步运行。如果是push出了问题,很可能是指针所指区域不正确,根据前后代码的对应关系进行修改。检查代码的时候一定要细心,有时是因为漏写或错写了某个符号,就会导致结果出错。