ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

yuv与rgb图像格式转换

2021-03-19 13:01:00  阅读:266  来源: 互联网

标签:tmp dim 图像格式 ++ yuv rgb 256 255


一、前言

本次试验内容为将256*256,采样为4:2:0的yuv图像转为rgb格式。老师提供了rgb2yuv的源码,经过分析,发现源码已经极其优雅高效,命名方式合理,内存分配刚刚好,还运用了查找表的方法,以空间换时间负责度。故此次直接以源码为基础,稍加改动得到其逆变换。

二、公式推导

1.rgb2yuv

在电视系统中,将红绿蓝称为三基色,分别用( R e ) , ( G e ) , ( G b ) 表示。电视显示器上五颜六色的光便是由这三种色彩的荧光粉调制而成的。

亮度方程:Y=0.2990R+0.5870G+0.1140B

由此可得:

Y=0.2990R+0.5870G+0.1140B

U = B − Y = − 0.2990 R − 0.5870 G + 0.8860 B

V = R − Y = 0.7010 R − 0.5870 G − 0.1140 B

2.yuv2rgb

由以上公式做逆变换,可得:
在这里插入图片描述
变换整理后最终形式为:

R = (298 Y+411 V - 57376) / 256

G = (298 Y - 101 U - 211 V + 35168) / 256

B = (298 Y + 519 U - 71200) / 256

三、代码实现

1.读取图像

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 output buffer for a frame */
	rgbBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3);

	/* get the input buffers for a frame */
	yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
	uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
	vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
	
fread(yBuf, 1, frameWidth * frameHeight, yuvFile);
fread(uBuf, 1, frameWidth * frameHeight/4, yuvFile);
fread(vBuf, 1, frameWidth * frameHeight/4, yuvFile)

虽然这次使用的c++语言,但rgb与yuv的读取思路与上次基本一致。通过限制每次读取的尺寸,将文件存储到每个提前分配好空间的buffer。

2.yuv转rgb

注意:在使用YUVviewerPlus(见下图)打开rgb文件时,会将图像认为bmp图像,打开后会是上下翻转的形式。但在本次实验时直接使用上次的自己写的python文件来打开rgb图像,故无需再对图像进行翻转。

在这里插入图片描述

	if (!flip) {
		
	} 
	
	else {
		for (int i = 0; i < x_dim; i++)
		{
			for (int j = 0; j < y_dim; j++)
			{
				g = b + 1;
				r = b + 2;
				
				int b_tmp, g_tmp, r_tmp;

				b_tmp = ((YUVRGB298[*y] + YUVRGB519[*u] - 71200) / 256);
				g_tmp = ((YUVRGB298[*y] + YUVRGBF101[*u] + YUVRGBF211[*v] + 35168) / 256);
				r_tmp = ((YUVRGB298[*y] + YUVRGB411[*v] - 57376) / 256);

				if (b_tmp < 0) b_tmp = 0;
				if (b_tmp > 255) b_tmp = 255;
				*b = (unsigned char)b_tmp;

				if (g_tmp < 0) g_tmp = 0;
				if (g_tmp > 255) g_tmp = 255;
				*g = (unsigned char)g_tmp;

				if (r_tmp < 0) r_tmp = 0;
				if (r_tmp > 255) r_tmp = 255;
				*r = (unsigned char)r_tmp;



				b += 3;
				y++;

				if (i % 2 == 0 && j == 0) //奇数行开头
				{
					u -= y_dim / 2;
					v -= y_dim / 2;
					k -= y_dim / 2;
				}


				if (j % 2 != 0)//偶数列
				{
					u++;
					v++;
					k++;
				}
				
				
			}
			
		}
	}

代码关键解读:

a) 使用查找表
void InitLookupTable()
{
	int i;

	for (i = 0; i < 256; i++) YUVRGB298[i] = (float)298 * i;
	for (i = 0; i < 256; i++) YUVRGB411[i] = (float)411 * i;
	for (i = 0; i < 256; i++) YUVRGBF101[i] = (float)-101 * i;
	for (i = 0; i < 256; i++) YUVRGBF211[i] = (float)-211 * i;
	for (i = 0; i < 256; i++) YUVRGB519[i] = (float)519 * i;
}

在进行运算之前,提前将可能的取值(0-255)全部计算,并将结果保存在数组中。如此在实际运算中直接查找数组提取结果值就可以了,大大提升了代码效率,是一种空间换取时间的做法。
在日后编写代码中也要常记这个tip。

b) 数值溢出

若是直接将运算结果赋值给rbg指针,将会是下图结果。
在这里插入图片描述
(在运算上图时有纰漏,误写了算法的公式导致天空色彩有误)

可以看到图像出现了很多饱和度较高,色彩一致的斑点。这是由于在计算后数值可能低于0或高于255,若是直接赋值给unsigned char类型变量会导致数值溢出。故先赋值给int类型变量,做一个溢出判定。

int b_tmp, g_tmp, r_tmp;

				b_tmp = ((YUVRGB298[*y] + YUVRGB519[*u] - 71200) / 256);
				g_tmp = ((YUVRGB298[*y] + YUVRGBF101[*u] + YUVRGBF211[*v] + 35168) / 256);
				r_tmp = ((YUVRGB298[*y] + YUVRGB411[*v] - 57376) / 256);

				if (b_tmp < 0) b_tmp = 0;
				if (b_tmp > 255) b_tmp = 255;
				*b = (unsigned char)b_tmp;

				if (g_tmp < 0) g_tmp = 0;
				if (g_tmp > 255) g_tmp = 255;
				*g = (unsigned char)g_tmp;

				if (r_tmp < 0) r_tmp = 0;
				if (r_tmp > 255) r_tmp = 255;
				*r = (unsigned char)r_tmp;
c) 指针移动

此实验yuv图像为4:2:0格式,采样点如下图:
在这里插入图片描述
在rgb2yuv代码中,需要对运算后的u,v的buffer进行下采样,将256256的空间转化为128128的尺寸,代码如下:

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;
		}
	}

而在yuv2rgb时,需要将128128的u,v buffer上采样为256256。但为了代码的简洁高效,没有这样做。而是直接利用指针的移动达到此目的。

if (i % 2 == 0 && j == 0) //奇数行开头
				{
					u -= y_dim / 2;
					v -= y_dim / 2;
					k -= y_dim / 2;
				}


				if (j % 2 != 0)//偶数列
				{
					u++;
					v++;
					k++;
				}

在横向,每当偶数列时u,v向下移动一个;
在纵向,每当到达奇数行开头时,u,v往回移动128个,重新遍历一遍本行数值。

最终结果

原图转换后
在这里插入图片描述
在这里插入图片描述

四、误差分析

虽然输出图像肉眼看起来无异,但在计算过程中,有以下几个环节会产生误差:

1.采样。在rgb图像yuv图像过程中,由于4:2:0的采样格式,u,v像素值变为原来的1/4,损失了大量信息。当再由yuv图像转为rgb时,信息量已经不是完整的了。
2.在对亮度方程做逆运算时,由于小数位的舍去,得到的计算公式并不精确。
3.在浮点运算时产生误差。

标签:tmp,dim,图像格式,++,yuv,rgb,256,255
来源: https://blog.csdn.net/cppKillMe/article/details/114995641

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有